# frozen_string_literal: true
module GraphQL
  # A reusable container for field logic, including arguments, resolve, return type, and documentation.
  #
  # Class-level values defined with the DSL will be inherited,
  # so {GraphQL::Function}s can extend one another.
  #
  # It's OK to override the instance methods here in order to customize behavior of instances.
  #
  # @example A reusable GraphQL::Function attached as a field
  #   class FindRecord < GraphQL::Function
  #     attr_reader :type
  #
  #     def initialize(model:, type:)
  #       @model = model
  #       @type = type
  #     end
  #
  #     argument :id, GraphQL::ID_TYPE
  #
  #     def call(obj, args, ctx)
  #        @model.find(args.id)
  #     end
  #   end
  #
  #   QueryType = GraphQL::ObjectType.define do
  #     name "Query"
  #     field :post, function: FindRecord.new(model: Post, type: PostType)
  #     field :comment, function: FindRecord.new(model: Comment, type: CommentType)
  #   end
  #
  # @see {GraphQL::Schema::Resolver} for a replacement for `GraphQL::Function`
  class Function
    # @return [Hash<String => GraphQL::Argument>] Arguments, keyed by name
    def arguments
      self.class.arguments
    end

    # @return [GraphQL::BaseType] Return type
    def type
      self.class.type
    end

    # @return [Object] This function's resolver
    def call(obj, args, ctx)
      raise NotImplementedError
    end

    # @return [String, nil]
    def description
      self.class.description
    end

    # @return [String, nil]
    def deprecation_reason
      self.class.deprecation_reason
    end

    # @return [Integer, Proc]
    def complexity
      self.class.complexity || 1
    end

    class << self
      # Define an argument for this function & its subclasses
      # @see {GraphQL::Field} same arguments as the `argument` definition helper
      # @return [void]
      def argument(*args, **kwargs, &block)
        argument = GraphQL::Argument.from_dsl(*args, **kwargs, &block)
        own_arguments[argument.name] = argument
        nil
      end

      # @return [Hash<String => GraphQL::Argument>] Arguments for this function class, including inherited arguments
      def arguments
        if parent_function?
          own_arguments.merge(superclass.arguments)
        else
          own_arguments.dup
        end
      end

      # Provides shorthand access to GraphQL's built-in types
      def types
        GraphQL::Define::TypeDefiner.instance
      end

      # Get or set the return type for this function class & descendants
      # @return [GraphQL::BaseType]
      def type(premade_type = nil, &block)
        if block_given?
          @type = GraphQL::ObjectType.define(&block)
        elsif premade_type
          @type = premade_type
        elsif parent_function?
          @type || superclass.type
        else
          @type
        end
      end

      def build_field(function)
        GraphQL::Field.define(
          arguments: function.arguments,
          complexity: function.complexity,
          type: function.type,
          resolve: function,
          description: function.description,
          function: function,
          deprecation_reason: function.deprecation_reason,
        )
      end

      # Class-level reader/writer which is inherited
      # @api private
      def self.inherited_value(name)
        self.class_eval <<-RUBY
          def #{name}(new_value = nil)
            if new_value
              @#{name} = new_value
            elsif parent_function?
              @#{name} || superclass.#{name}
            else
              @#{name}
            end
          end
        RUBY
      end

      # @!method description(new_value = nil)
      #   Get or set this class's description
      inherited_value(:description)
      # @!method deprecation_reason(new_value = nil)
      #   Get or set this class's deprecation_reason
      inherited_value(:deprecation_reason)
      # @!method complexity(new_value = nil)
      #   Get or set this class's complexity
      inherited_value(:complexity)

      private

      # Does this function inherit from another function?
      def parent_function?
        superclass <= GraphQL::Function
      end

      # Arguments defined on this class (not superclasses)
      def own_arguments
        @own_arguments ||= {}
      end
    end
  end
end
