module GraphQL
  class Query
    class SerialExecution
      module ValueResolution
        def self.get_strategy_for_kind(kind)
          TYPE_KIND_STRATEGIES[kind] || raise("No value resolution strategy for #{kind}!")
        end

        class BaseResolution
          attr_reader :value, :field_type, :target, :parent_type,
            :irep_node, :execution_context
          def initialize(value, field_type, target, parent_type, irep_node, execution_context)
            @value = value
            @field_type = field_type
            @target = target
            @parent_type = parent_type
            @irep_node = irep_node
            @execution_context = execution_context
          end

          def result
            return nil if value.nil? || value.is_a?(GraphQL::ExecutionError)
            non_null_result
          end

          def non_null_result
            raise NotImplementedError, "Should return a value based on initialization params"
          end

          def get_strategy_for_kind(*args)
            GraphQL::Query::SerialExecution::ValueResolution.get_strategy_for_kind(*args)
          end
        end

        class ScalarResolution < BaseResolution
          # Apply the scalar's defined `coerce_result` method to the value
          def non_null_result
            field_type.coerce_result(value)
          end
        end

        class ListResolution < BaseResolution
          # For each item in the list,
          # Resolve it with the "wrapped" type of this list
          def non_null_result
            wrapped_type = field_type.of_type
            strategy_class = get_strategy_for_kind(wrapped_type.kind)
            value.map do |item|
              inner_strategy = strategy_class.new(item, wrapped_type, target, parent_type, irep_node, execution_context)
              inner_strategy.result
            end
          end
        end

        class HasPossibleTypeResolution < BaseResolution
          def non_null_result
            # When deprecations are removed:
            # resolved_type = execution_context.schema.resolve_type(value)
            resolved_type = field_type.legacy_resolve_type(value, execution_context)

            unless resolved_type.is_a?(GraphQL::ObjectType)
              raise GraphQL::ObjectType::UnresolvedTypeError.new(irep_node.definition_name, field_type, parent_type)
            end

            strategy_class = get_strategy_for_kind(resolved_type.kind)
            inner_strategy = strategy_class.new(value, resolved_type, target, parent_type, irep_node, execution_context)
            inner_strategy.result
          end
        end

        class ObjectResolution < BaseResolution
          # Resolve the selections on this object
          def non_null_result
            execution_context.strategy.selection_resolution.new(
              value,
              field_type,
              irep_node,
              execution_context
            ).result
          end
        end

        class NonNullResolution < BaseResolution
          # Get the "wrapped" type and resolve the value according to that type
          def result
            if value.nil? || value.is_a?(GraphQL::ExecutionError)
              raise GraphQL::InvalidNullError.new(irep_node.definition_name, value)
            else
              wrapped_type = field_type.of_type
              strategy_class = get_strategy_for_kind(wrapped_type.kind)
              inner_strategy = strategy_class.new(value, wrapped_type, target, parent_type, irep_node, execution_context)
              inner_strategy.result
            end
          end
        end

        TYPE_KIND_STRATEGIES = {
          GraphQL::TypeKinds::SCALAR =>     ScalarResolution,
          GraphQL::TypeKinds::LIST =>       ListResolution,
          GraphQL::TypeKinds::OBJECT =>     ObjectResolution,
          GraphQL::TypeKinds::ENUM =>       ScalarResolution,
          GraphQL::TypeKinds::NON_NULL =>   NonNullResolution,
          GraphQL::TypeKinds::INTERFACE =>  HasPossibleTypeResolution,
          GraphQL::TypeKinds::UNION =>      HasPossibleTypeResolution,
        }
      end
    end
  end
end