///|
/// The two possible effects of a policy rule. A deny rule overrides permits.
pub(all) enum RuleEffect {
  Permit
  Deny
} derive(Eq, Debug)

///|
pub(all) enum TenantRelation {
  AnyTenant
  SameTenant
  OtherTenant
} derive(Eq, Debug)

///|
pub(all) enum AttributeSource {
  PrincipalAttribute
  ResourceAttribute
  RequestAttribute
} derive(Eq, Debug)

///|
pub(all) enum AttributeOperator {
  Equals
  NotEquals
  Exists
  Missing
} derive(Eq, Debug)

///|
pub(all) struct AttributeCondition {
  source : AttributeSource
  key : String
  operator : AttributeOperator
  value : String
} derive(Eq, Debug)

///|
/// A role name may inherit another role. Inheritance is directed: a holder
/// of `child` receives all permissions granted to `parent`.
pub(all) struct RoleInheritance {
  child : String
  parent : String
} derive(Eq, Debug)

///|
pub(all) struct Principal {
  id : String
  tenant : String
  roles : Array[String]
  attributes : Array[(String, String)]
} derive(Eq, Debug)

///|
pub(all) struct AccessResource {
  id : String
  kind : String
  tenant : String
  attributes : Array[(String, String)]
} derive(Eq, Debug)

///|
pub(all) struct AccessRequest {
  principal_id : String
  action : String
  resource_id : String
  attributes : Array[(String, String)]
} derive(Eq, Debug)

///|
/// Empty selector arrays mean "any". An explicit `*` has the same meaning
/// and is accepted by the text parser for readable policy files.
pub(all) struct AccessRule {
  id : String
  effect : RuleEffect
  roles : Array[String]
  actions : Array[String]
  resource_kinds : Array[String]
  resource_ids : Array[String]
  tenant_relation : TenantRelation
  conditions : Array[AttributeCondition]
} derive(Eq, Debug)

///|
pub(all) struct AccessPolicy {
  name : String
  role_inheritance : Array[RoleInheritance]
  rules : Array[AccessRule]
} derive(Eq, Debug)

///|
/// The universe is explicit so a finite analysis never silently claims
/// coverage of identities or resources that were not provided.
pub(all) struct RequestUniverse {
  principals : Array[Principal]
  resources : Array[AccessResource]
  requests : Array[AccessRequest]
} derive(Eq, Debug)

///|
pub(all) enum DecisionKind {
  Allowed
  Denied
  Unknown
} derive(Eq, Debug)

///|
pub(all) struct RuleTrace {
  rule_id : String
  effect : RuleEffect
  matched : Bool
  reason : String
} derive(Eq, Debug)

///|
pub(all) struct Decision {
  kind : DecisionKind
  decisive_rules : Array[String]
  traces : Array[RuleTrace]
  explanation : String
} derive(Eq, Debug)

///|
pub(all) enum ChangeKind {
  NewGrant
  RevokedGrant
  UnchangedAllow
  UnchangedDeny
  Inconclusive
} derive(Eq, Debug)

///|
pub(all) struct DecisionChange {
  request : AccessRequest
  before : Decision
  after : Decision
  kind : ChangeKind
} derive(Eq, Debug)

///|
pub(all) enum DiagnosticSeverity {
  Info
  Warning
  Error
} derive(Eq, Debug)

///|
pub(all) struct Diagnostic {
  code : String
  severity : DiagnosticSeverity
  message : String
} derive(Eq, Debug)

///|
pub(all) struct AuditReport {
  before_name : String
  after_name : String
  requests_considered : Int
  changes : Array[DecisionChange]
  diagnostics : Array[Diagnostic]
  complete : Bool
} derive(Eq, Debug)

///|
pub fn empty_policy(name : String) -> AccessPolicy {
  { name, role_inheritance: [], rules: [], }
}

///|
pub fn empty_universe() -> RequestUniverse {
  { principals: [], resources: [], requests: [], }
}

///|
pub fn default_deny() -> Decision {
  {
    kind: Denied,
    decisive_rules: [],
    traces: [],
    explanation: "No permit matched; default deny",
  }
}

///|
pub fn unknown_decision(message : String) -> Decision {
  { kind: Unknown, decisive_rules: [], traces: [], explanation: message, }
}

///|
pub fn is_valid_identifier(value : String) -> Bool {
  if value == "" {
    return false
  }
  for char in value.to_array() {
    if !((char >= 'a' && char <= 'z') ||
      (char >= 'A' && char <= 'Z') ||
      (char >= '0' && char <= '9') ||
      char == '_' ||
      char == '-' ||
      char == '.' ||
      char == ':' ||
      char == '/') {
      return false
    }
  }
  true
}

///|
pub fn lookup_attribute(
  attrs : Array[(String, String)],
  key : String,
) -> String? {
  for pair in attrs {
    if pair.0 == key {
      return Some(pair.1)
    }
  }
  None
}