class ViewModel::AccessControl::Composed

# edit_valid_unless!(“user is on fire”) { … }
# edit_valid_if!(“logged in as specified user”) { … }
# if at least one ‘if` check and no `unless` checks succeed. For example:
# for each access check (visible, editable, edit_valid). An action is permitted
# Provides access control as a combination of `x_if!` and `x_unless!` checks

def check_delegates(env, ifs, unlesses)

def check_delegates(env, ifs, unlesses)
  vetoed_checker = unlesses.detect { |checker| checker.check(env) }
  veto = vetoed_checker.present?
  if veto
    veto_error = vetoed_checker.error_type.new('Action not permitted because: ' +
                                               vetoed_checker.reason,
                                               env.view.blame_reference)
  end
  allow = ifs.any? { |checker| checker.check(env) }
  unless allow
    allow_error = NoRequiredConditionsError.new(env.view.blame_reference,
                                                ifs.map(&:name))
  end
  ComposedResult.new(allow, veto, allow_error, veto_error)
end

def each_check(check_name, include_ancestor = nil)

def each_check(check_name, include_ancestor = nil)
  return enum_for(:each_check, check_name, include_ancestor) unless block_given?
  self.public_send(check_name).each { |x| yield x }
  visited = Set.new
  @included_checkers.each do |ancestor|
    next unless visited.add?(ancestor)
    next if include_ancestor && !include_ancestor.call(ancestor)
    ancestor.each_check(check_name) { |x| yield x }
  end
end

def edit_valid_if!(reason, &block)

def edit_valid_if!(reason, &block)
  @edit_valid_ifs      << new_permission_check(reason, &block)
end

def edit_valid_unless!(reason, &block)

def edit_valid_unless!(reason, &block)
  @edit_valid_unlesses << new_permission_check(reason, &block)
end

def editable_check(traversal_env)

final
def editable_check(traversal_env)
  check_delegates(traversal_env, self.class.each_check(:editable_ifs), self.class.each_check(:editable_unlesses))
end

def editable_if!(reason, &block)

def editable_if!(reason, &block)
  @editable_ifs        << new_permission_check(reason, &block)
end

def editable_unless!(reason, &block)

def editable_unless!(reason, &block)
  @editable_unlesses   << new_permission_check(reason, &block)
end

def include_from(ancestor)

# Configuration API
def include_from(ancestor)
  unless ancestor < ViewModel::AccessControl::Composed
    raise ArgumentError.new("Invalid ancestor: #{ancestor}")
  end
  @included_checkers << ancestor
end

def inherited(subclass)

def inherited(subclass)
  super
  subclass.initialize_as_composed_access_control
end

def initialize_as_composed_access_control

def initialize_as_composed_access_control
  @included_checkers = []
  @edit_valid_ifs      = []
  @edit_valid_unlesses = []
  @editable_ifs        = []
  @editable_unlesses   = []
  @visible_ifs         = []
  @visible_unlesses    = []
end

def inspect

def inspect
  s = super + '('
  s += inspect_checks.join(', ')
  s += " includes checkers: #{@included_checkers.inspect}" if @included_checkers.present?
  s += ')'
  s
end

def inspect_checks

def inspect_checks
  checks = []
  checks << "visible_if: #{@visible_ifs.map(&:reason)}"                if @visible_ifs.present?
  checks << "visible_unless: #{@visible_unlesses.map(&:reason)}"       if @visible_unlesses.present?
  checks << "editable_if: #{@editable_ifs.map(&:reason)}"              if @editable_ifs.present?
  checks << "editable_unless: #{@editable_unlesses.map(&:reason)}"     if @editable_unlesses.present?
  checks << "edit_valid_if: #{@edit_valid_ifs.map(&:reason)}"          if @edit_valid_ifs.present?
  checks << "edit_valid_unless: #{@edit_valid_unlesses.map(&:reason)}" if @edit_valid_unlesses.present?
  checks
end

def new_permission_check(reason, error_type: ViewModel::AccessControlError, &block)

def new_permission_check(reason, error_type: ViewModel::AccessControlError, &block)
  PermissionsCheck.new(self.name&.demodulize, reason, error_type, block)
end

def valid_edit_check(traversal_env)

final
def valid_edit_check(traversal_env)
  check_delegates(traversal_env, self.class.each_check(:edit_valid_ifs), self.class.each_check(:edit_valid_unlesses))
end

def visible_check(traversal_env)

final
def visible_check(traversal_env)
  check_delegates(traversal_env, self.class.each_check(:visible_ifs), self.class.each_check(:visible_unlesses))
end

def visible_if!(reason, &block)

def visible_if!(reason, &block)
  @visible_ifs         << new_permission_check(reason, &block)
end

def visible_unless!(reason, &block)

def visible_unless!(reason, &block)
  @visible_unlesses    << new_permission_check(reason, &block)
end