///|
pub(all) enum DecisionChangeKind {
  NewlyAllowed
  NewlyDenied
  Unchanged
} derive(Debug, Eq)

///|
pub(all) struct DecisionChange {
  request : CheckRequest
  kind : DecisionChangeKind
  before : CheckResult
  after : CheckResult
} derive(Debug)

///|
pub(all) struct PolicyImpactReport {
  changes : Array[DecisionChange]
  newly_allowed : Int
  newly_denied : Int
  unchanged : Int
} derive(Debug)

///|
/// Compares two policy engines over an explicit, deterministic request set.
///
/// Newly allowed decisions represent privilege expansion and should normally
/// receive security review before deployment.
pub fn analyze_policy_impact(
  before : PolicyEngine,
  after : PolicyEngine,
  requests : Array[CheckRequest],
) -> PolicyImpactReport {
  let changes : Array[DecisionChange] = []
  let mut newly_allowed = 0
  let mut newly_denied = 0
  let mut unchanged = 0
  for request in requests {
    let old_result = before.check(
      request.object,
      request.permission,
      request.principal,
    )
    let new_result = after.check(
      request.object,
      request.permission,
      request.principal,
    )
    let kind = if !old_result.allowed && new_result.allowed {
      newly_allowed = newly_allowed + 1
      NewlyAllowed
    } else if old_result.allowed && !new_result.allowed {
      newly_denied = newly_denied + 1
      NewlyDenied
    } else {
      unchanged = unchanged + 1
      Unchanged
    }
    changes.push({ request, kind, before: old_result, after: new_result })
  }
  { changes, newly_allowed, newly_denied, unchanged }
}

///|
pub fn PolicyImpactReport::has_privilege_expansion(
  self : PolicyImpactReport,
) -> Bool {
  self.newly_allowed > 0
}

///|
pub fn PolicyImpactReport::to_json(self : PolicyImpactReport) -> String {
  "{\"requests\":\{self.changes.length()},\"newly_allowed\":\{self.newly_allowed},\"newly_denied\":\{self.newly_denied},\"unchanged\":\{self.unchanged},\"privilege_expansion\":\{self.has_privilege_expansion()}}"
}