module GraphQL
  module Language
    module Nodes
      # AbstractNode creates classes who:
      # - require their keyword arguments, throw ArgumentError if they don't match
      # - expose accessors for keyword arguments
      class AbstractNode
        attr_accessor :line, :col

        # @param options [Hash] Must contain all attributes defined by {required_attrs}, may also include `position_source`
        def initialize(options={})
          if options.key?(:position_source)
            position_source = options.delete(:position_source)
            @line, @col = position_source.line_and_column
          elsif options.key?(:line)
            @line = options.delete(:line)
            @col = options.delete(:col)
          end

          initialize_node(options)
        end

        # This is called with node-specific options
        def initialize_node(options={})
          raise NotImplementedError
        end

        # @return [GraphQL::Language::Nodes::AbstractNode] all nodes in the tree below this one
        def children
          self.class.child_attributes
            .map { |attr_name| public_send(attr_name) }
            .flatten
        end

        class << self
          def child_attributes(*attr_names)
            @child_attributes ||= []
            @child_attributes += attr_names
          end
        end

        def position
          [line, col]
        end

        def to_query_string
          Generation.generate(self)
        end
      end

      class WrapperType < AbstractNode
        attr_accessor :of_type
        def initialize_node(of_type: nil)
          @of_type = of_type
        end

        def children
          [].freeze
        end
      end

      class NameOnlyNode < AbstractNode
        attr_accessor :name
        def initialize_node(name: nil)
          @name = name
        end

        def children
          [].freeze
        end
      end


      class Argument < AbstractNode
        attr_accessor :name, :value

        def initialize_node(name: nil, value: nil)
          @name = name
          @value = value
        end

        def children
          [value].flatten.select { |v| v.is_a?(AbstractNode) }
        end
      end

      class Directive < AbstractNode
        attr_accessor :name, :arguments
        child_attributes :arguments

        def initialize_node(name: nil, arguments: [])
          @name = name
          @arguments = arguments
        end
      end

      class Document < AbstractNode
        attr_accessor :definitions
        child_attributes :definitions

        def initialize_node(definitions: [])
          @definitions = definitions
        end
      end

      class Enum < NameOnlyNode; end

      class Field < AbstractNode
        attr_accessor :name, :alias, :arguments, :directives, :selections
        child_attributes :arguments, :directives, :selections

        def initialize_node(name: nil, arguments: [], directives: [], selections: [], **kwargs)
          @name = name
          # oops, alias is a keyword:
          @alias = kwargs.fetch(:alias, nil)
          @arguments = arguments
          @directives = directives
          @selections = selections
        end
      end

      class FragmentDefinition < AbstractNode
        attr_accessor :name, :type, :directives, :selections
        child_attributes :directives, :selections

        def initialize_node(name: nil, type: nil, directives: [], selections: [])
          @name = name
          @type = type
          @directives = directives
          @selections = selections
        end
      end

      class FragmentSpread < AbstractNode
        attr_accessor :name, :directives
        child_attributes :directives

        def initialize_node(name: nil, directives: [])
          @name = name
          @directives = directives
        end
      end

      class InlineFragment < AbstractNode
        attr_accessor :type, :directives, :selections
        child_attributes :directives, :selections

        def initialize_node(type: nil, directives: [], selections: [])
          @type = type
          @directives = directives
          @selections = selections
        end
      end

      class InputObject < AbstractNode
        attr_accessor :arguments
        child_attributes :arguments

        def initialize_node(arguments: [])
          @arguments = arguments
        end

        def to_h(options={})
          arguments.inject({}) do |memo, pair|
            v = pair.value
            memo[pair.name] = v.is_a?(InputObject) ? v.to_h : v
            memo
          end
        end
      end



      class ListType < WrapperType; end
      class NonNullType < WrapperType; end

      class OperationDefinition < AbstractNode
        attr_accessor :operation_type, :name, :variables, :directives, :selections
        child_attributes :variables, :directives, :selections

        def initialize_node(operation_type: nil, name: nil, variables: [], directives: [], selections: [])
          @operation_type = operation_type
          @name = name
          @variables = variables
          @directives = directives
          @selections = selections
        end
      end

      class TypeName < NameOnlyNode; end

      class VariableDefinition < AbstractNode
        attr_accessor :name, :type, :default_value
        def initialize_node(name: nil, type: nil, default_value: nil)
          @name = name
          @type = type
          @default_value = default_value
        end
      end

      class VariableIdentifier < NameOnlyNode; end


      class SchemaDefinition < AbstractNode
        attr_accessor :query, :mutation, :subscription
        def initialize_node(query: nil, mutation: nil, subscription: nil)
          @query = query
          @mutation = mutation
          @subscription = subscription
        end
      end

      class ScalarTypeDefinition < NameOnlyNode; end

      class ObjectTypeDefinition < AbstractNode
        attr_accessor :name, :interfaces, :fields
        child_attributes :fields
        def initialize_node(name:, interfaces:, fields:)
          @name = name
          @interfaces = interfaces || []
          @fields = fields
        end
      end

      class InputValueDefinition < AbstractNode
        attr_accessor :name, :type, :default_value
        def initialize_node(name:, type:, default_value: nil)
          @name = name
          @type = type
          @default_value = default_value
        end
      end

      class FieldDefinition < AbstractNode
        attr_accessor :name, :arguments, :type
        child_attributes :arguments
        def initialize_node(name:, arguments:, type:)
          @name = name
          @arguments = arguments
          @type = type
        end
      end

      class InterfaceTypeDefinition < AbstractNode
        attr_accessor :name, :fields
        child_attributes :fields
        def initialize_node(name:, fields:)
          @name = name
          @fields = fields
        end
      end

      class UnionTypeDefinition < AbstractNode
        attr_accessor :name, :types
        def initialize_node(name:, types:)
          @name = name
          @types = types
        end
      end

      class EnumTypeDefinition < AbstractNode
        attr_accessor :name, :values
        def initialize_node(name:, values:)
          @name = name
          @values = values
        end
      end

      class InputObjectTypeDefinition < AbstractNode
        attr_accessor :name, :fields
        child_attributes :fields
        def initialize_node(name:, fields:)
          @name = name
          @fields = fields
        end
      end
    end
  end
end