// Server-authoritative permission matrix. Roles form an ordered rank
// (ADMIN > MANAGER > STAFF > VIEWER — this is the "level" concept); every
// write action carries a minimum rank. VIEWER holds rank 0 and is the
// read-only role. Reads are not listed here — they stay on AuthenticatedGuard
// alone, so any logged-in user (including VIEWER) can read.
//
// This is the single source of truth: the API enforces it via AbilityGuard and
// ships the resolved per-user map to the web through /auth/me, so the UI never
// keeps its own copy of the rules.

export type Role = "ADMIN" | "MANAGER" | "STAFF" | "VIEWER";

export const ROLE_RANK: Record<Role, number> = {
  VIEWER: 0,
  STAFF: 1,
  MANAGER: 2,
  ADMIN: 3,
};

export type Ability =
  | "customer:create"
  | "customer:update"
  | "customer:delete"
  | "policy:create"
  | "policy:update"
  | "policy:delete"
  | "policy:ingest"
  | "policy:ocr-review"
  | "renewal:send"
  | "property:create"
  | "property:update"
  | "property:delete"
  | "ledger:create"
  | "ledger:void"
  | "bank:create"
  | "bank:void"
  | "bank:manage-accounts"
  | "statement:ingest"
  | "statement:review"
  | "lookup:manage"
  | "user:manage"
  | "db:manage"
  | "notification:send"
  | "setting:manage";

/** Minimum role required for each ability. */
export const ABILITY_MIN: Record<Ability, Role> = {
  "customer:create": "STAFF",
  "customer:update": "STAFF",
  "customer:delete": "ADMIN",
  "policy:create": "STAFF",
  "policy:update": "STAFF",
  "policy:delete": "MANAGER",
  // Insurance OCR intake is the same trust tier as statement OCR: STAFF can
  // upload + confirm, nothing reaches the books unconfirmed.
  "policy:ingest": "STAFF",
  "policy:ocr-review": "STAFF",
  "renewal:send": "MANAGER",
  "property:create": "STAFF",
  "property:update": "STAFF",
  "property:delete": "MANAGER",
  "ledger:create": "STAFF",
  "ledger:void": "MANAGER",
  "bank:create": "STAFF",
  "bank:void": "MANAGER",
  // Opening or renaming a chequera is rarer and higher-stakes than posting a
  // movement into one — a wrong account silently mixes two sets of books.
  "bank:manage-accounts": "MANAGER",
  // Uploading a stack of scans and reviewing what the OCR read are both
  // "capturing a receipt" — the same trust tier as ledger:create, since
  // confirming a statement *is* capturing it. The review step is what makes
  // this safe at STAFF level: nothing reaches the ledger unconfirmed.
  "statement:ingest": "STAFF",
  "statement:review": "STAFF",
  "lookup:manage": "MANAGER",
  "user:manage": "ADMIN",
  "db:manage": "ADMIN",
  // Mass email notifications — fires mail to customers on the office's
  // behalf, with no per-row review. Same trust tier as `renewal:send`:
  // a STAFF user typing one customer receipt is fine; a STAFF user firing
  // 260 mail merges on the customer base is not.
  "notification:send": "MANAGER",
  // Editing operator configuration. Above `notification:send` on purpose:
  // firing a sweep is the day job, but changing WHERE the audit summaries
  // land is how someone would quietly stop them being read.
  "setting:manage": "ADMIN",
};

export const ALL_ABILITIES = Object.keys(ABILITY_MIN) as Ability[];

export function can(role: Role, ability: Ability): boolean {
  return ROLE_RANK[role] >= ROLE_RANK[ABILITY_MIN[ability]];
}

/** Resolved {ability: boolean} map for a role — sent to the web via /auth/me. */
export function abilitiesFor(role: Role): Record<Ability, boolean> {
  return Object.fromEntries(
    ALL_ABILITIES.map((a) => [a, can(role, a)]),
  ) as Record<Ability, boolean>;
}
