module ActiveGraph
  module Core
    class Element
      attr_reader :name
      delegate :version?, to: ActiveGraph::Base

      def initialize(name)
        @name = name
      end

      def create_index(*properties, **options)
        validate_index_options!(options)
        properties = properties.map { |p| "l.#{p}" }
        schema_query("CREATE INDEX FOR (l:`#{@name}`) ON (#{properties.join('.')})")
      end

      def drop_index(property, options = {})
        validate_index_options!(options)
        schema_query("SHOW INDEXES YIELD * WHERE labelsOrTypes = $labels AND properties = $properties",
                     labels: [@name], properties: [property]).each do |record|
          schema_query("DROP INDEX #{record[:name]}")
        end
      end

      # Creates a neo4j constraint on a property
      # See http://docs.neo4j.org/chunked/stable/query-constraints.html
      # @example
      #   label = ActiveGraph::Label.create(:person)
      #   label.create_constraint(:name, {type: :unique})
      #
      def create_constraint(property, type: :key)
        schema_query(
          "CREATE CONSTRAINT #{constraint_name(property, type:)} FOR #{pattern("n:`#{name}`")} REQUIRE n.`#{property}` IS #{constraint_type(type:)}"
        )
      end

      # Drops a neo4j constraint on a property
      # See http://docs.neo4j.org/chunked/stable/query-constraints.html
      # @example
      #   label = ActiveGraph::Label.create(:person)
      #   label.create_constraint(:name, {type: :unique})
      #   label.drop_constraint(:name, {type: :unique})
      #
      def drop_constraint(property, type: :key)
        schema_query("DROP CONSTRAINT #{constraint_name(property, type:)} IF EXISTS")
      end

      def drop_uniqueness_constraint(property, options = {})
        drop_constraint(property, type: :unique)
      end

      def indexes
        self.class.indexes.select do |definition|
          definition[:label] == @name.to_sym
        end
      end

      def drop_indexes
        self.class.drop_indexes
      end

      def index?(property)
        indexes.any? { |definition| definition[:properties] == [property.to_sym] }
      end

      def constraints(_options = {})
        ActiveGraph::Base.constraints.select do |definition|
          definition[:label] == @name.to_sym
        end
      end

      def uniqueness_constraints(_options = {})
        constraints.select do |definition|
          definition[:type] == :uniqueness
        end
      end

      def constraint?(property)
        constraints.any? { |definition| definition[:properties] == [property.to_sym] }
      end

      def uniqueness_constraint?(property)
        uniqueness_constraints.include?([property])
      end

      private

      class << self
        def indexes
          ActiveGraph::Base.indexes
        end

        def drop_indexes
          indexes.each do |definition|
            ActiveGraph::Base.query("DROP INDEX #{definition[:name]}") unless definition[:owningConstraint]
          end
        end

        def drop_constraints
          result = ActiveGraph::Base.read_transaction do |tx|
            tx.run('SHOW CONSTRAINTS YIELD *').to_a
          end
          ActiveGraph::Base.write_transaction do |tx|
            result.each do |record|
              tx.run("DROP CONSTRAINT `#{record[:name]}`")
            end
          end
        end
      end

      def schema_query(cypher, **params)
        ActiveGraph::Base.query(cypher, params)
      end

      def validate_index_options!(options)
        return unless options[:type] && options[:type] != :exact
        fail "Type #{options[:type]} is not supported"
      end

      def constraint_type(type:)
        case symnolic_type(type:)
        when :key
          "#{element_type} KEY"
        when :unique
          "UNIQUE"
        when :not_null
          "UNIQUE"
        else
          ":: #{type.to_s.upcase}"
        end
      end

      def symnolic_type(type:)
        type == :key && !ActiveGraph::Base.enterprise? ? :unique : type
      end

      def constraint_name(property, type:)
        "`#{element_type}_#{name}##{property}_#{symnolic_type(type:)}`"
      end
    end
  end
end