class GraphQL::Query::FieldResolutionStrategy
  attr_reader :result, :result_value

  def initialize(ast_field, parent_type, target, operation_resolver)
    arguments = GraphQL::Query::Arguments.new(ast_field.arguments, operation_resolver.variables).to_h
    field_name = ast_field.name
    field = parent_type.fields[field_name] || raise("No field found on #{parent_type.name} '#{parent_type}' for '#{field_name}'")
    value = field.resolve(target, arguments, operation_resolver.context)
    if value.nil?
      @result_value = value
    else
      if value == GraphQL::Query::DEFAULT_RESOLVE
        begin
          value = target.send(field_name)
        rescue NoMethodError => err
          raise("Couldn't resolve field '#{field_name}' to #{target.class} '#{target}' (resulted in #{err})")
        end
      end
      resolved_type = field.type.kind.resolve(field.type, value)
      strategy_class = self.class.get_strategy_for_kind(resolved_type.kind)
      result_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_field, operation_resolver)
      @result_value = result_strategy.result
    end
    result_name = ast_field.alias || ast_field.name
    @result = { result_name => @result_value}
  end

  def self.get_strategy_for_kind(kind)
    FIELD_TYPE_KIND_STRATEGIES[kind] || raise("No strategy for #{kind}")
  end

  class ScalarResolutionStrategy
    attr_reader :result
    def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
      @result = field_type.coerce(value)
    end
  end

  class ListResolutionStrategy
    attr_reader :result
    def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
      wrapped_type = field_type.of_type
      resolved_type = wrapped_type.kind.resolve(wrapped_type, value)
      strategy_class = GraphQL::Query::FieldResolutionStrategy.get_strategy_for_kind(resolved_type.kind)
      @result = value.map do |item|
        inner_strategy = strategy_class.new(item, resolved_type, target, parent_type, ast_field, operation_resolver)
        inner_strategy.result
      end
    end
  end

  class ObjectResolutionStrategy
    attr_reader :result
    def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
      resolver = GraphQL::Query::SelectionResolver.new(value, field_type, ast_field.selections, operation_resolver)
      @result = resolver.result
    end
  end

  class EnumResolutionStrategy
    attr_reader :result
    def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
      @result = value.to_s
    end
  end

  class NonNullResolutionStrategy
    attr_reader :result
    def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
      wrapped_type = field_type.of_type
      resolved_type = wrapped_type.kind.resolve(wrapped_type, value)
      strategy_class = GraphQL::Query::FieldResolutionStrategy.get_strategy_for_kind(resolved_type.kind)
      inner_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_field, operation_resolver)
      @result = inner_strategy.result
    end
  end

  private

  FIELD_TYPE_KIND_STRATEGIES = {
    GraphQL::TypeKinds::SCALAR =>     ScalarResolutionStrategy,
    GraphQL::TypeKinds::LIST =>       ListResolutionStrategy,
    GraphQL::TypeKinds::OBJECT =>     ObjectResolutionStrategy,
    GraphQL::TypeKinds::ENUM =>       EnumResolutionStrategy,
    GraphQL::TypeKinds::NON_NULL =>   NonNullResolutionStrategy,
  }
end
