///|
/// Curated reservation catalog with provenance-friendly explanations.
pub(all) struct ReservedRule {
  platform : String
  shortcut : String
  rationale : String
  source : String
  severity : Severity
} derive(Eq, @debug.Debug)

///|
pub(all) struct CatalogReport {
  keymap : Keymap
  matched : Array[ReservedRule]
  unmatched : Array[ReservedRule]
  findings : Array[Finding]
} derive(Eq, @debug.Debug)

///|
fn reserved_rule(
  platform : String,
  shortcut : String,
  rationale : String,
  source : String,
) -> ReservedRule {
  { platform, shortcut, rationale, source, severity: Error }
}

///|
/// The catalog is intentionally conservative: it flags well-known system
/// controls, but the profile can always be customized for a product shell.
pub fn default_reserved_rules() -> Array[ReservedRule] {
  [
    reserved_rule(
      "all", "Ctrl+Alt+Delete", "system security screen on Windows", "Microsoft Windows documentation",
    ),
    reserved_rule(
      "windows", "Alt+F4", "close active window", "Microsoft Windows documentation",
    ),
    reserved_rule(
      "windows", "Win+L", "lock workstation", "Microsoft Windows documentation",
    ),
    reserved_rule(
      "windows", "Win+D", "show desktop", "Microsoft Windows documentation",
    ),
    reserved_rule(
      "windows", "Ctrl+Alt+Tab", "window switcher", "Microsoft Windows documentation",
    ),
    reserved_rule(
      "mac", "Cmd+Q", "quit foreground application", "Apple Human Interface Guidelines",
    ),
    reserved_rule(
      "mac", "Cmd+W", "close window or tab", "Apple Human Interface Guidelines",
    ),
    reserved_rule(
      "mac", "Cmd+Tab", "application switcher", "Apple Human Interface Guidelines",
    ),
    reserved_rule(
      "mac", "Cmd+Space", "system search", "Apple Human Interface Guidelines",
    ),
    reserved_rule(
      "mac", "Ctrl+Cmd+Q", "lock screen", "Apple Human Interface Guidelines",
    ),
    reserved_rule(
      "linux", "Alt+F4", "window manager close action", "freedesktop desktop conventions",
    ),
    reserved_rule(
      "linux", "Alt+Tab", "window manager switcher", "freedesktop desktop conventions",
    ),
    reserved_rule(
      "linux", "Ctrl+Alt+T", "terminal launcher convention", "freedesktop desktop conventions",
    ),
    reserved_rule(
      "linux", "Ctrl+Alt+L", "lock screen convention", "freedesktop desktop conventions",
    ),
    reserved_rule(
      "all", "Ctrl+C", "interrupt in terminal contexts", "POSIX terminal conventions",
    ),
    reserved_rule(
      "all", "Ctrl+D", "end-of-input in terminal contexts", "POSIX terminal conventions",
    ),
    reserved_rule(
      "all", "Ctrl+Z", "suspend foreground process", "POSIX terminal conventions",
    ),
    reserved_rule(
      "all", "Ctrl+\\", "quit foreground process", "POSIX terminal conventions",
    ),
    reserved_rule(
      "all", "Escape", "cancel or dismiss active interaction", "cross-platform interaction convention",
    ),
    reserved_rule(
      "all", "Tab", "focus traversal", "WCAG keyboard interaction guidance",
    ),
    reserved_rule(
      "all", "Shift+Tab", "reverse focus traversal", "WCAG keyboard interaction guidance",
    ),
    reserved_rule(
      "all", "Enter", "activate focused control", "WCAG keyboard interaction guidance",
    ),
    reserved_rule(
      "all", "Space", "activate focused control", "WCAG keyboard interaction guidance",
    ),
    reserved_rule(
      "all", "ArrowLeft", "spatial navigation", "WCAG keyboard interaction guidance",
    ),
    reserved_rule(
      "all", "ArrowRight", "spatial navigation", "WCAG keyboard interaction guidance",
    ),
    reserved_rule(
      "all", "ArrowUp", "spatial navigation", "WCAG keyboard interaction guidance",
    ),
    reserved_rule(
      "all", "ArrowDown", "spatial navigation", "WCAG keyboard interaction guidance",
    ),
    reserved_rule(
      "all", "F1", "help key convention", "desktop application conventions",
    ),
    reserved_rule(
      "all", "F10", "menu focus convention", "desktop application conventions",
    ),
    reserved_rule(
      "all", "F11", "fullscreen convention", "desktop application conventions",
    ),
  ]
}

///|
fn rule_matches(binding : Binding, rule : ReservedRule) -> Bool {
  binding.keys.canonical == canonical_keys(rule.shortcut) &&
  platforms_overlap(binding.platform, rule.platform)
}

///|
/// Find catalog rules that explain why a binding is reserved.
pub fn explain_reserved(
  binding : Binding,
  rules : Array[ReservedRule],
) -> Array[ReservedRule] {
  rules.filter(rule => rule_matches(binding, rule))
}

///|
fn catalog_finding(binding : Binding, rule : ReservedRule) -> Finding {
  {
    code: "MK410",
    kind: ReservedShortcut,
    severity: rule.severity,
    message: "catalog reserves " +
    rule.shortcut +
    " on " +
    rule.platform +
    ": " +
    rule.rationale,
    primary_id: binding.id,
    secondary_id: "",
    shortcut: binding.keys.canonical,
    context: binding.context,
    source: binding.source,
    line: binding.line,
    suggestion: "choose another chord or explicitly override this policy with a documented profile",
  }
}

///|
/// Audit against an explicit reservation catalog and preserve matched rules.
pub fn audit_with_catalog(
  keymap : Keymap,
  rules : Array[ReservedRule],
) -> CatalogReport {
  let matched : Array[ReservedRule] = []
  let findings : Array[Finding] = []
  for binding in keymap.bindings {
    for rule in explain_reserved(binding, rules) {
      if !array_contains(
          matched.map(item => item.platform + ":" + item.shortcut),
          rule.platform + ":" + rule.shortcut,
        ) {
        matched.push(rule)
      }
      findings.push(catalog_finding(binding, rule))
    }
  }
  let unmatched : Array[ReservedRule] = []
  for rule in rules {
    if !array_contains(
        matched.map(item => item.platform + ":" + item.shortcut),
        rule.platform + ":" + rule.shortcut,
      ) {
      unmatched.push(rule)
    }
  }
  { keymap, matched, unmatched, findings }
}

///|
/// Add the catalog to a keymap's reserved markers without introducing dupes.
pub fn apply_reserved_catalog(
  keymap : Keymap,
  rules : Array[ReservedRule],
) -> Keymap {
  let result = keymap.reserved.copy()
  for rule in rules {
    let marker = lower_ascii(rule.platform) +
      ":" +
      canonical_keys(rule.shortcut)
    if !array_contains(result, marker) {
      result.push(marker)
    }
  }
  { ..keymap, reserved: result }
}

///|
pub fn catalog_report_to_markdown(report : CatalogReport) -> String {
  let lines : Array[String] = [
    "## Reservation catalog report",
    "",
    "Matched rules: " + report.matched.length().to_string(),
    "Unmatched catalog rules: " + report.unmatched.length().to_string(),
    "",
    "| Binding | Shortcut | Reason | Source |",
    "| --- | --- | --- | --- |",
  ]
  for finding in report.findings {
    let reason = finding.message.replace(old="|", new="/")
    lines.push(
      "| `" +
      finding.primary_id +
      "` | `" +
      finding.shortcut +
      "` | " +
      reason +
      " | " +
      finding.source +
      " |",
    )
  }
  lines.join("\n")
}

///|
pub fn catalog_to_json(rules : Array[ReservedRule]) -> String {
  let rows : Array[String] = []
  for rule in rules {
    rows.push(
      "{\"platform\":" +
      json_string(rule.platform) +
      ",\"shortcut\":" +
      json_string(rule.shortcut) +
      ",\"rationale\":" +
      json_string(rule.rationale) +
      ",\"source\":" +
      json_string(rule.source) +
      "}",
    )
  }
  "[" + rows.join(",") + "]"
}

///|
/// Validate that every catalog entry itself uses a parseable shortcut.
pub fn validate_catalog(rules : Array[ReservedRule]) -> Array[String] {
  let errors : Array[String] = []
  for rule in rules {
    match parse_keys(rule.shortcut) {
      Ok(_) => ()
      Err(message) =>
        errors.push(rule.platform + ":" + rule.shortcut + " — " + message)
    }
  }
  errors
}