module GraphQL::Schema::BuildFromDefinition::Builder

def self.inherited(child_class)

def self.inherited(child_class)
  child_class.definition_default_resolve = self.definition_default_resolve
end

def self.resolve_type(*args)

def self.resolve_type(*args)
  self.definition_default_resolve.resolve_type(*args)
end

def self.resolve_type(*args)

def self.resolve_type(*args)
  NullResolveType.call(*args)
end

def build(document, default_resolve:, using: {}, interpreter: true, relay:)

def build(document, default_resolve:, using: {}, interpreter: true, relay:)
  raise InvalidDocumentError.new('Must provide a document ast.') if !document || !document.is_a?(GraphQL::Language::Nodes::Document)
  if default_resolve.is_a?(Hash)
    default_resolve = ResolveMap.new(default_resolve)
  end
  schema_defns = document.definitions.select { |d| d.is_a?(GraphQL::Language::Nodes::SchemaDefinition) }
  if schema_defns.size > 1
    raise InvalidDocumentError.new('Must provide only one schema definition.')
  end
  schema_definition = schema_defns.first
  types = {}
  directives = {}
  type_resolver = ->(type) { resolve_type(types, type)  }
  document.definitions.each do |definition|
    case definition
    when GraphQL::Language::Nodes::SchemaDefinition
      nil # already handled
    when GraphQL::Language::Nodes::EnumTypeDefinition
      types[definition.name] = build_enum_type(definition, type_resolver)
    when GraphQL::Language::Nodes::ObjectTypeDefinition
      types[definition.name] = build_object_type(definition, type_resolver)
    when GraphQL::Language::Nodes::InterfaceTypeDefinition
      types[definition.name] = build_interface_type(definition, type_resolver)
    when GraphQL::Language::Nodes::UnionTypeDefinition
      types[definition.name] = build_union_type(definition, type_resolver)
    when GraphQL::Language::Nodes::ScalarTypeDefinition
      types[definition.name] = build_scalar_type(definition, type_resolver, default_resolve: default_resolve)
    when GraphQL::Language::Nodes::InputObjectTypeDefinition
      types[definition.name] = build_input_object_type(definition, type_resolver)
    when GraphQL::Language::Nodes::DirectiveDefinition
      directives[definition.name] = build_directive(definition, type_resolver)
    end
  end
  # At this point, if types named by the built in types are _late-bound_ types,
  # that means they were referenced in the schema but not defined in the schema.
  # That's supported for built-in types. (Eg, you can use `String` without defining it.)
  # In that case, insert the concrete type definition now.
  #
  # However, if the type in `types` is a _concrete_ type definition, that means that
  # the document contained an explicit definition of the scalar type.
  # Don't override it in this case.
  GraphQL::Schema::BUILT_IN_TYPES.each do |scalar_name, built_in_scalar|
    existing_type = types[scalar_name]
    if existing_type.is_a?(GraphQL::Schema::LateBoundType)
      types[scalar_name] = built_in_scalar
    end
  end
  directives = GraphQL::Schema.default_directives.merge(directives)
  if schema_definition
    if schema_definition.query
      raise InvalidDocumentError.new("Specified query type \"#{schema_definition.query}\" not found in document.") unless types[schema_definition.query]
      query_root_type = types[schema_definition.query]
    end
    if schema_definition.mutation
      raise InvalidDocumentError.new("Specified mutation type \"#{schema_definition.mutation}\" not found in document.") unless types[schema_definition.mutation]
      mutation_root_type = types[schema_definition.mutation]
    end
    if schema_definition.subscription
      raise InvalidDocumentError.new("Specified subscription type \"#{schema_definition.subscription}\" not found in document.") unless types[schema_definition.subscription]
      subscription_root_type = types[schema_definition.subscription]
    end
  else
    query_root_type = types['Query']
    mutation_root_type = types['Mutation']
    subscription_root_type = types['Subscription']
  end
  raise InvalidDocumentError.new('Must provide schema definition with query type or a type named Query.') unless query_root_type
  Class.new(GraphQL::Schema) do
    begin
      # Add these first so that there's some chance of resolving late-bound types
      orphan_types types.values
      query query_root_type
      mutation mutation_root_type
      subscription subscription_root_type
    rescue Schema::UnresolvedLateBoundTypeError  => err
      type_name = err.type.name
      err_backtrace =  err.backtrace
      raise InvalidDocumentError, "Type \"#{type_name}\" not found in document.", err_backtrace
    end
    if default_resolve.respond_to?(:resolve_type)
      def self.resolve_type(*args)
        self.definition_default_resolve.resolve_type(*args)
      end
    else
      def self.resolve_type(*args)
        NullResolveType.call(*args)
      end
    end
    directives directives.values
    if schema_definition
      ast_node(schema_definition)
    end
    if interpreter
      use GraphQL::Execution::Interpreter
      use GraphQL::Analysis::AST
    end
    using.each do |plugin, options|
      if options
        use(plugin, **options)
      else
        use(plugin)
      end
    end
    # Empty `orphan_types` -- this will make unreachable types ... unreachable.
    own_orphan_types.clear
    class << self
      attr_accessor :definition_default_resolve
    end
    self.definition_default_resolve = default_resolve
    def definition_default_resolve
      self.class.definition_default_resolve
    end
    def self.inherited(child_class)
      child_class.definition_default_resolve = self.definition_default_resolve
    end
  end
end

def build_arguments(type_class, arguments, type_resolver)

def build_arguments(type_class, arguments, type_resolver)
  builder = self
  arguments.each do |argument_defn|
    default_value_kwargs = if !argument_defn.default_value.nil?
      { default_value: builder.build_default_value(argument_defn.default_value) }
    else
      NO_DEFAULT_VALUE
    end
    type_class.argument(
      argument_defn.name,
      type: type_resolver.call(argument_defn.type),
      required: false,
      description: argument_defn.description,
      deprecation_reason: builder.build_deprecation_reason(argument_defn.directives),
      ast_node: argument_defn,
      camelize: false,
      method_access: false,
      **default_value_kwargs
    )
  end
end

def build_default_value(default_value)

def build_default_value(default_value)
  case default_value
  when GraphQL::Language::Nodes::Enum
    default_value.name
  when GraphQL::Language::Nodes::NullValue
    nil
  when GraphQL::Language::Nodes::InputObject
    default_value.to_h
  when Array
    default_value.map { |v| build_default_value(v) }
  else
    default_value
  end
end

def build_deprecation_reason(directives)

def build_deprecation_reason(directives)
  deprecated_directive = directives.find{ |d| d.name == 'deprecated' }
  return unless deprecated_directive
  reason = deprecated_directive.arguments.find{ |a| a.name == 'reason' }
  return GraphQL::Schema::Directive::DEFAULT_DEPRECATION_REASON unless reason
  reason.value
end

def build_directive(directive_definition, type_resolver)

def build_directive(directive_definition, type_resolver)
  builder = self
  Class.new(GraphQL::Schema::Directive) do
    graphql_name(directive_definition.name)
    description(directive_definition.description)
    locations(*directive_definition.locations.map { |location| location.name.to_sym })
    ast_node(directive_definition)
    builder.build_arguments(self, directive_definition.arguments, type_resolver)
  end
end

def build_enum_type(enum_type_definition, type_resolver)

def build_enum_type(enum_type_definition, type_resolver)
  builder = self
  Class.new(GraphQL::Schema::Enum) do
    graphql_name(enum_type_definition.name)
    description(enum_type_definition.description)
    ast_node(enum_type_definition)
    enum_type_definition.values.each do |enum_value_definition|
      value(enum_value_definition.name,
        value: enum_value_definition.name,
        deprecation_reason: builder.build_deprecation_reason(enum_value_definition.directives),
        description: enum_value_definition.description,
        ast_node: enum_value_definition,
      )
    end
  end
end

def build_fields(owner, field_definitions, type_resolver, default_resolve:)

def build_fields(owner, field_definitions, type_resolver, default_resolve:)
  builder = self
  field_definitions.each do |field_definition|
    type_name = resolve_type_name(field_definition.type)
    resolve_method_name = -"resolve_field_#{field_definition.name}"
    schema_field_defn = owner.field(
      field_definition.name,
      description: field_definition.description,
      type: type_resolver.call(field_definition.type),
      null: true,
      connection: type_name.end_with?("Connection"),
      connection_extension: nil,
      deprecation_reason: build_deprecation_reason(field_definition.directives),
      ast_node: field_definition,
      method_conflict_warning: false,
      camelize: false,
      resolver_method: resolve_method_name,
    )
    builder.build_arguments(schema_field_defn, field_definition.arguments, type_resolver)
    # Don't do this for interfaces
    if default_resolve
      owner.class_eval <<-RUBY, __FILE__, __LINE__
        # frozen_string_literal: true
        def #{resolve_method_name}(**args)
          field_instance = self.class.get_field("#{field_definition.name}")
          context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
        end
      RUBY
    end
  end
end

def build_input_object_type(input_object_type_definition, type_resolver)

def build_input_object_type(input_object_type_definition, type_resolver)
  builder = self
  Class.new(GraphQL::Schema::InputObject) do
    graphql_name(input_object_type_definition.name)
    description(input_object_type_definition.description)
    ast_node(input_object_type_definition)
    builder.build_arguments(self, input_object_type_definition.fields, type_resolver)
  end
end

def build_interface_type(interface_type_definition, type_resolver)

def build_interface_type(interface_type_definition, type_resolver)
  builder = self
  Module.new do
    include GraphQL::Schema::Interface
    graphql_name(interface_type_definition.name)
    description(interface_type_definition.description)
    ast_node(interface_type_definition)
    builder.build_fields(self, interface_type_definition.fields, type_resolver, default_resolve: nil)
  end
end

def build_object_type(object_type_definition, type_resolver)

def build_object_type(object_type_definition, type_resolver)
  builder = self
  Class.new(GraphQL::Schema::Object) do
    graphql_name(object_type_definition.name)
    description(object_type_definition.description)
    ast_node(object_type_definition)
    object_type_definition.interfaces.each do |interface_name|
      interface_defn = type_resolver.call(interface_name)
      implements(interface_defn)
    end
    builder.build_fields(self, object_type_definition.fields, type_resolver, default_resolve: true)
  end
end

def build_scalar_type(scalar_type_definition, type_resolver, default_resolve:)

def build_scalar_type(scalar_type_definition, type_resolver, default_resolve:)
  builder = self
  Class.new(GraphQL::Schema::Scalar) do
    graphql_name(scalar_type_definition.name)
    description(scalar_type_definition.description)
    ast_node(scalar_type_definition)
    if default_resolve.respond_to?(:coerce_input)
      # Put these method definitions in another method to avoid retaining `type_resolve`
      # from this method's bindiing
      builder.build_scalar_type_coerce_method(self, :coerce_input, default_resolve)
      builder.build_scalar_type_coerce_method(self, :coerce_result, default_resolve)
    end
  end
end

def build_scalar_type_coerce_method(scalar_class, method_name, default_definition_resolve)

def build_scalar_type_coerce_method(scalar_class, method_name, default_definition_resolve)
  scalar_class.define_singleton_method(method_name) do |val, ctx|
    default_definition_resolve.public_send(method_name, self, val, ctx)
  end
end

def build_union_type(union_type_definition, type_resolver)

def build_union_type(union_type_definition, type_resolver)
  Class.new(GraphQL::Schema::Union) do
    graphql_name(union_type_definition.name)
    description(union_type_definition.description)
    possible_types(*union_type_definition.types.map { |type_name| type_resolver.call(type_name) })
    ast_node(union_type_definition)
  end
end

def definition_default_resolve

def definition_default_resolve
  self.class.definition_default_resolve
end

def resolve_type(types, ast_node)

def resolve_type(types, ast_node)
  case ast_node
  when GraphQL::Language::Nodes::TypeName
    type_name = ast_node.name
    types[type_name] ||= GraphQL::Schema::LateBoundType.new(type_name)
  when GraphQL::Language::Nodes::NonNullType
    resolve_type(types, ast_node.of_type).to_non_null_type
  when GraphQL::Language::Nodes::ListType
    resolve_type(types, ast_node.of_type).to_list_type
  else
    raise "Unexpected ast_node: #{ast_node.inspect}"
  end
end

def resolve_type_name(type)

def resolve_type_name(type)
  case type
  when GraphQL::Language::Nodes::TypeName
    return type.name
  else
    resolve_type_name(type.of_type)
  end
end