# frozen_string_literal: true

module GraphQL
  class Schema
    module HasSingleInputArgument
      def resolve_with_support(**inputs)
        input_kwargs = flatten_arguments(inputs)
        if !input_kwargs.empty?
          super(**input_kwargs)
        else
          super()
        end
      end

      def self.included(base)
        base.extend(ClassMethods)
      end

      def call
        @prepared_arguments = flatten_arguments(@prepared_arguments)
        super
      end

      private

      def flatten_arguments(inputs)
        input = if inputs[:input].is_a?(InputObject)
          inputs[:input].to_kwargs
        else
          inputs[:input]
        end

        new_extras = field ? field.extras : []
        all_extras = self.class.extras + new_extras

        # Transfer these from the top-level hash to the
        # shortcutted `input:` object
        all_extras.each do |ext|
          # It's possible that the `extra` was not passed along by this point,
          # don't re-add it if it wasn't given here.
          if inputs.key?(ext)
            input[ext] = inputs[ext]
          end
        end

        if input
          input_kwargs = input.to_h
        else
          # Relay Classic Mutations with no `argument`s
          # don't require `input:`
          input_kwargs = {}
        end

        input_kwargs
      end

      module ClassMethods
        def dummy
          @dummy ||= begin
            d = Class.new(GraphQL::Schema::Resolver)
            d.graphql_name "#{self.graphql_name}DummyResolver"
            d.argument_class(self.argument_class)
            # TODO make this lazier?
            d.argument(:input, input_type, description: "Parameters for #{self.graphql_name}")
            d
          end
        end

        def field_arguments(context = GraphQL::Query::NullContext.instance)
          dummy.arguments(context)
        end

        def get_field_argument(name, context = GraphQL::Query::NullContext.instance)
          dummy.get_argument(name, context)
        end

        def own_field_arguments
          dummy.own_arguments
        end

        def any_field_arguments?
          dummy.any_arguments?
        end

        def all_field_argument_definitions
          dummy.all_argument_definitions
        end

        # Also apply this argument to the input type:
        def argument(*args, own_argument: false, **kwargs, &block)
          it = input_type # make sure any inherited arguments are already added to it
          arg = super(*args, **kwargs, &block)

          # This definition might be overriding something inherited;
          # if it is, remove the inherited definition so it's not confused at runtime as having multiple definitions
          prev_args = it.own_arguments[arg.graphql_name]
          case prev_args
          when GraphQL::Schema::Argument
            if prev_args.owner != self
              it.own_arguments.delete(arg.graphql_name)
            end
          when Array
            prev_args.reject! { |a| a.owner != self }
            if prev_args.empty?
              it.own_arguments.delete(arg.graphql_name)
            end
          end

          it.add_argument(arg)
          arg
        end

        # The base class for generated input object types
        # @param new_class [Class] The base class to use for generating input object definitions
        # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject})
        def input_object_class(new_class = nil)
          if new_class
            @input_object_class = new_class
          end
          @input_object_class || (superclass.respond_to?(:input_object_class) ? superclass.input_object_class : GraphQL::Schema::InputObject)
        end

        # @param new_input_type [Class, nil] If provided, it configures this mutation to accept `new_input_type` instead of generating an input type
        # @return [Class] The generated {Schema::InputObject} class for this mutation's `input`
        def input_type(new_input_type = nil)
          if new_input_type
            @input_type = new_input_type
          end
          @input_type ||= generate_input_type
        end

        private

        # Generate the input type for the `input:` argument
        # To customize how input objects are generated, override this method
        # @return [Class] a subclass of {.input_object_class}
        def generate_input_type
          mutation_args = all_argument_definitions
          mutation_class = self
          Class.new(input_object_class) do
            class << self
              def default_graphql_name
                "#{self.mutation.graphql_name}Input"
              end

              def description(new_desc = nil)
                super || "Autogenerated input type of #{self.mutation.graphql_name}"
              end
            end
            # For compatibility, in case no arguments are defined:
            has_no_arguments(true)
            mutation(mutation_class)
            # these might be inherited:
            mutation_args.each do |arg|
              add_argument(arg)
            end
          end
        end
      end

      private

      def authorize_arguments(args, values)
        # remove the `input` wrapper to match values
        input_type = args.find { |a| a.graphql_name == "input" }.type.unwrap
        input_args = context.types.arguments(input_type)
        super(input_args, values)
      end
    end
  end
end
