module GraphQL
  class Query
    class SerialExecution
      class FieldResolution
        attr_reader :ast_node, :parent_type, :target, :execution_context, :field, :arguments

        def initialize(ast_node, parent_type, target, execution_context)
          @ast_node = ast_node
          @parent_type = parent_type
          @target = target
          @execution_context = execution_context
          @field =  execution_context.get_field(parent_type, ast_node.name)
          if @field.nil?
            raise("No field found on #{parent_type.name} '#{parent_type}' for '#{ast_node.name}'")
          end
          @arguments = GraphQL::Query::LiteralInput.from_arguments(
            ast_node.arguments,
            field.arguments,
            execution_context.query.variables
          )
        end

        def result
          result_name = ast_node.alias || ast_node.name
          raw_value = get_raw_value
          { result_name => get_finished_value(raw_value) }
        end

        private

        # After getting the value from the field's resolve method,
        # continue by "finishing" the value, eg. executing sub-fields or coercing values
        def get_finished_value(raw_value)
          if raw_value.is_a?(GraphQL::ExecutionError)
            raw_value.ast_node = ast_node
            execution_context.add_error(raw_value)
          end

          strategy_class = GraphQL::Query::SerialExecution::ValueResolution.get_strategy_for_kind(field.type.kind)
          result_strategy = strategy_class.new(raw_value, field.type, target, parent_type, ast_node, execution_context)
          result_strategy.result
        end


        # Get the result of:
        # - Any middleware on this schema
        # - The field's resolve method
        # If the middleware chain returns a GraphQL::ExecutionError, its message
        # is added to the "errors" key.
        def get_raw_value
          steps = execution_context.query.schema.middleware + [get_middleware_proc_from_field_resolve]
          chain = GraphQL::Schema::MiddlewareChain.new(
            steps: steps,
            arguments: [parent_type, target, field, arguments, execution_context.query.context]
          )
          chain.call
        rescue GraphQL::ExecutionError => err
          err
        end


        # Execute the field's resolve method
        # @return [Proc] suitable to be the last step in a middleware chain
        def get_middleware_proc_from_field_resolve
          -> (_parent_type, parent_object, field_definition, field_args, context, _next) {
            context.ast_node = ast_node
            value = field_definition.resolve(parent_object, field_args, context)
            context.ast_node = nil
            value
          }
        end
      end
    end
  end
end