# frozen_string_literal: true

require 'pundit'

module GraphQL
  module Pundit
    module Instrumenters
      # Instrumenter that supplies `scope`
      class Scope
        # Applies the scoping to the passed object
        class ScopeResolver
          attr_reader :current_user, :scope, :old_resolver

          def initialize(current_user, scope, old_resolver)
            @current_user = current_user
            @old_resolver = old_resolver

            unless valid_value?(scope)
              raise ArgumentError, 'Invalid value passed to `scope`'
            end

            @scope = new_scope(scope)
          end

          def call(root, arguments, context)
            new_scope = scope.call(root, arguments, context)
            old_resolver.call(new_scope, arguments, context)
          end

          private

          def new_scope(scope)
            return scope if proc?(scope)

            lambda do |root, _arguments, context|
              unless inferred?(scope)
                root.define_singleton_method(:policy_class) { scope }
              end

              ::Pundit.policy_scope!(context[current_user], root)
            end
          end

          def valid_value?(value)
            value.is_a?(Class) || inferred?(value) || proc?(value)
          end

          def proc?(value)
            value.respond_to?(:call)
          end

          def inferred?(value)
            value == :infer_scope
          end
        end

        attr_reader :current_user

        def initialize(current_user = :current_user)
          @current_user = current_user
        end

        def instrument(_type, field)
          scope = field.metadata[:scope]
          return field unless scope

          old_resolver = field.resolve_proc
          resolver = ScopeResolver.new(current_user, scope, old_resolver)

          field.redefine do
            resolve resolver
          end
        end
      end
    end
  end
end