///|
pub(all) struct ChangeGroup {
  principal_tenant : String
  resource_tenant : String
  action : String
  new_grants : Int
  revocations : Int
  unchanged : Int
  inconclusive : Int
} derive(Debug)

///|
fn group_index(
  groups : Array[ChangeGroup],
  principal_tenant : String,
  resource_tenant : String,
  action : String,
) -> Int {
  for index, group in groups {
    if group.principal_tenant == principal_tenant &&
      group.resource_tenant == resource_tenant &&
      group.action == action {
      return index
    }
  }
  -1
}

///|
/// Groups decisions by subject tenant, resource tenant, and action.
/// This is an aggregate over the explicit sample, not a population estimate.
pub fn group_changes(
  report : AuditReport,
  universe : RequestUniverse,
) -> Array[ChangeGroup] {
  let groups : Array[ChangeGroup] = []
  if !report.complete {
    return groups
  }
  for change in report.changes {
    let source_tenant = principal_tenant(universe, change.request.principal_id)
    let target_tenant = resource_tenant(universe, change.request.resource_id)
    let index = group_index(
      groups,
      source_tenant,
      target_tenant,
      change.request.action,
    )
    let current = if index >= 0 {
      groups[index]
    } else {
      {
        principal_tenant: source_tenant,
        resource_tenant: target_tenant,
        action: change.request.action,
        new_grants: 0,
        revocations: 0,
        unchanged: 0,
        inconclusive: 0,
      }
    }
    let mut next = current
    match change.kind {
      NewGrant => next = { ..next, new_grants: next.new_grants + 1, }
      RevokedGrant => next = { ..next, revocations: next.revocations + 1, }
      UnchangedAllow | UnchangedDeny =>
        next = { ..next, unchanged: next.unchanged + 1, }
      Inconclusive => next = { ..next, inconclusive: next.inconclusive + 1, }
    }
    if index >= 0 {
      groups[index] = next
    } else {
      groups.push(next)
    }
  }
  groups
}

///|
pub fn render_groups(groups : Array[ChangeGroup]) -> String {
  let out = StringBuilder()
  out.write_string("Tenant/action groups: \{groups.length()}\n")
  for group in groups {
    out.write_string(
      "\{group.principal_tenant} -> \{group.resource_tenant} / \{group.action}: +\{group.new_grants} -\{group.revocations} =\{group.unchanged} ?\{group.inconclusive}\n",
    )
  }
  out.to_string()
}

///|
pub fn cross_tenant_change_count(groups : Array[ChangeGroup]) -> Int {
  let mut count = 0
  for group in groups {
    if group.principal_tenant != group.resource_tenant {
      count += group.new_grants + group.revocations
    }
  }
  count
}