module Scram
  using SymbolExtensions

  # Base class to represent a Holder of permissions through policies.
  # @note Implementing classes must implement #policies and #scram_compare_value
  module Holder
    extend ActiveSupport::Concern

    # @return [Array] list of policies
    def policies
      raise NotImplementedError
    end

    # @return [Object] a value to compare {Holder} in the database. For example, an ObjectID would be suitable.
    def scram_compare_value
      raise NotImplementedError
    end

    # Checks if this holder can perform some action on an object by checking the Holder's policies
    # @param action [String] What the user is trying to do to obj
    # @param obj [Object] The receiver of the action
    # @return [Boolean] Whether or not holder can action to object. We define a full abstainment as a failure to perform the action.
    def can? action, target
      target = target.to_s if target.is_a? Symbol
      action = action.to_s

      # Checks policies in priority order for explicit allow or deny.
      policies.sort_by(&:priority).reverse.each do |policy|
        opinion = policy.can?(self, action, target)
        return opinion.to_bool if %i[allow deny].include? opinion
      end

      return false
    end

    # Helper method to enhance readability of permission checks
    def cannot? *args
      !can?(*args)
    end

  end
end