///|
pub(all) enum ActionPriority {
  Observe
  Review
  Fix
  Block
} derive(Debug, Eq, Compare)

///|
pub(all) struct Remediation {
  rule_id : String
  risk : RiskKind
  severity : Severity
  priority : ActionPriority
  title : String
  why : String
  immediate : String
  safe_default : String
  long_term : String
} derive(Debug, Eq)

///|
pub(all) struct RemediationPlan {
  summary : String
  gate : String
  blocked : Bool
  actions : Array[Remediation]
  next_steps : Array[String]
} derive(Debug, Eq)

///|
pub fn ActionPriority::name(self : ActionPriority) -> String {
  match self {
    Observe => "observe"
    Review => "review"
    Fix => "fix"
    Block => "block"
  }
}

///|
pub fn ActionPriority::rank(self : ActionPriority) -> Int {
  match self {
    Observe => 0
    Review => 1
    Fix => 2
    Block => 3
  }
}

///|
pub fn action_priority_name(priority : ActionPriority) -> String {
  priority.name()
}

///|
fn priority_for_severity(severity : Severity) -> ActionPriority {
  match severity {
    Info => Observe
    Warning => Review
    Dangerous => Fix
    Critical => Block
  }
}

///|
fn stronger_priority(
  left : ActionPriority,
  right : ActionPriority,
) -> ActionPriority {
  if left.rank() >= right.rank() {
    left
  } else {
    right
  }
}

///|
fn policy_hint_for_decision(decision : Decision) -> String {
  match decision {
    Keep =>
      "the selected policy kept this sequence, so downstream renderers must still be trusted"
    Drop =>
      "the selected policy removed this sequence from the safe terminal output"
    Mark =>
      "the selected policy replaced this sequence with an explicit audit marker"
  }
}

///|
fn make_remediation(
  finding : Finding,
  priority : ActionPriority,
  title : String,
  why : String,
  immediate : String,
  safe_default : String,
  long_term : String,
) -> Remediation {
  {
    rule_id: finding.rule_id,
    risk: finding.risk,
    severity: finding.severity,
    priority: stronger_priority(
      priority_for_severity(finding.severity),
      priority,
    ),
    title,
    why,
    immediate,
    safe_default,
    long_term,
  }
}

///|
fn styling_remediation(finding : Finding) -> Remediation {
  make_remediation(
    finding,
    Observe,
    "Normalize terminal styling",
    "SGR colors are usually harmless, but they make artifacts less deterministic and can hide text in some themes.",
    "Strip color/style controls when producing README snippets, release notes, or issue comments.",
    "Use the plain policy for permanent artifacts and the ci policy only when colored CI output is expected.",
    "Keep style handling isolated from security decisions so future renderers can opt in safely.",
  )
}

///|
fn cursor_remediation(finding : Finding) -> Remediation {
  make_remediation(
    finding,
    Fix,
    "Remove cursor movement before display",
    "Cursor movement can make later text appear before or over trusted text, which breaks log integrity.",
    "Block or mark the affected log segment and inspect the command that produced it.",
    "Use strict policy for public logs and reject artifacts with dangerous cursor movement.",
    "If progress bars are expected, capture a non-interactive log mode that disables cursor rewrites.",
  )
}

///|
fn rewrite_remediation(finding : Finding) -> Remediation {
  make_remediation(
    finding,
    Fix,
    "Preserve rewritten output as explicit text",
    "Backspace, carriage return, scrolling, and saved-cursor operations can hide the actual execution history.",
    "Keep the sanitized plain text and compare it with the raw source before publishing.",
    "Treat screen rewrite findings as release-review items even when no critical sequence is present.",
    "Prefer tools that emit line-oriented logs under CI instead of interactive terminal updates.",
  )
}

///|
fn erase_remediation(finding : Finding) -> Remediation {
  make_remediation(
    finding,
    Block,
    "Reject screen erase behavior",
    "Erase commands can clear visible output or terminal scrollback and are high risk in untrusted logs.",
    "Regenerate the log with terminal features disabled or sanitize before any human review.",
    "Block publication when erase scrollback or full-screen clearing appears in release artifacts.",
    "Add CI capture flags such as no-color, no-progress, or dumb terminal mode where the producer supports them.",
  )
}

///|
fn query_remediation(finding : Finding) -> Remediation {
  make_remediation(
    finding,
    Review,
    "Drop terminal queries",
    "Device-status and identity queries can solicit terminal responses and should not be replayed from logs.",
    "Remove the query and verify the remaining text still explains the build or test result.",
    "Drop terminal queries in CI and documentation outputs.",
    "Audit any tool that emits terminal queries outside an interactive session.",
  )
}

///|
fn mode_remediation(finding : Finding) -> Remediation {
  make_remediation(
    finding,
    Fix,
    "Disable terminal mode changes",
    "Mode changes can alter paste, alternate screen, cursor visibility, or terminal behavior for later output.",
    "Sanitize the artifact and review whether the producer was running in an interactive mode.",
    "Reject mode changes from untrusted sources unless a narrow allow-list explains the exact sequence.",
    "Document allowed terminal integrations if a trusted internal producer needs them.",
  )
}

///|
fn title_remediation(finding : Finding) -> Remediation {
  make_remediation(
    finding,
    Fix,
    "Remove deceptive title changes",
    "Window and tab title updates can make an unrelated terminal session look like a trusted task.",
    "Remove title controls and keep a visible audit note if the source is external.",
    "Never preserve title changes in copied logs, issue comments, or generated documentation.",
    "Prefer explicit metadata fields over terminal-title side channels.",
  )
}

///|
fn hyperlink_remediation(finding : Finding) -> Remediation {
  let priority = if finding.severity == Critical { Block } else { Fix }
  make_remediation(
    finding, priority, "Expand or remove terminal hyperlinks", "OSC 8 can make benign visible text navigate to a different destination, and risky URI schemes can execute unsafe handlers.",
    "Replace terminal hyperlinks with visible plain URLs or remove them before publishing.",
    "Block javascript, data, file, vbscript, and ssh schemes; keep only reviewed https links when policy allows it.",
    "For trusted producers, emit Markdown links after URL validation instead of raw OSC 8 sequences.",
  )
}

///|
fn clipboard_remediation(finding : Finding) -> Remediation {
  make_remediation(
    finding,
    Block,
    "Block clipboard writes",
    "OSC 52 can replace a user's clipboard when replayed in compatible terminals.",
    "Delete the raw sequence and inspect the source command before sharing the log.",
    "Treat clipboard writes as critical and fail CI or release publication.",
    "If clipboard integration is needed, expose it as an explicit user action outside logs.",
  )
}

///|
fn notification_remediation(finding : Finding) -> Remediation {
  make_remediation(
    finding,
    Review,
    "Suppress terminal notifications",
    "BEL and desktop-notification extensions can create misleading attention signals.",
    "Drop the notification sequence and preserve the surrounding text.",
    "Allow notifications only in trusted interactive sessions, never in archived logs.",
    "Move notification behavior into application-level hooks with clear user consent.",
  )
}

///|
fn working_directory_remediation(finding : Finding) -> Remediation {
  make_remediation(
    finding,
    Review,
    "Strip working-directory metadata",
    "Working-directory metadata leaks environment details and is not needed for plain log review.",
    "Remove the metadata and keep explicit paths only when they are intentionally disclosed.",
    "Do not publish OSC 7 data from personal machines or private build agents.",
    "Prefer structured build metadata with redaction rules for reproducible diagnostics.",
  )
}

///|
fn file_transfer_remediation(finding : Finding) -> Remediation {
  make_remediation(
    finding,
    Block,
    "Reject terminal file transfer extensions",
    "Terminal-specific file transfer commands can embed or move content outside the visible log stream.",
    "Block the artifact and regenerate it without terminal inline-file features.",
    "Fail CI when iTerm-style file transfer or unknown inline content extensions are detected.",
    "Store attachments as explicit files with checksums, licenses, and reviewable provenance.",
  )
}

///|
fn device_control_remediation(finding : Finding) -> Remediation {
  make_remediation(
    finding,
    Block,
    "Reject device-control strings",
    "DCS, SOS, PM, and APC strings can carry device-specific commands or opaque payloads.",
    "Remove the sequence and inspect whether it came from a terminal multiplexer or untrusted input.",
    "Block opaque control strings from public artifacts unless a documented allow-list covers them.",
    "Add focused tests for any intentionally supported terminal extension before enabling it.",
  )
}

///|
fn bidi_remediation(finding : Finding) -> Remediation {
  make_remediation(
    finding,
    Fix,
    "Remove bidirectional text controls",
    "Unicode bidirectional controls can disguise filenames or source-code order while leaving bytes unchanged.",
    "Strip the control character and review the visible filename or source fragment.",
    "Reject bidi controls in generated logs unless the content is an explicitly reviewed natural-language sample.",
    "Add source and artifact scanning for Trojan Source style controls.",
  )
}

///|
fn unknown_remediation(finding : Finding) -> Remediation {
  make_remediation(
    finding,
    Review,
    "Review unknown terminal controls",
    "Unknown controls are not safe to replay because their behavior depends on the terminal emulator.",
    "Drop the sequence and capture the visible context for manual review.",
    "Keep unknown controls out of public artifacts until their semantics are documented.",
    "Promote recurring unknown controls into explicit parser rules with tests.",
  )
}

///|
fn malformed_remediation(finding : Finding) -> Remediation {
  make_remediation(
    finding,
    Fix,
    "Treat malformed sequences as suspicious",
    "Invalid or truncated sequences often indicate corrupted logs or intentionally evasive terminal data.",
    "Keep the sanitized text and inspect the raw bytes around the reported offset.",
    "Fail release artifacts when malformed controls appear near executable commands, URLs, or filenames.",
    "Add ingestion checks that preserve raw input separately from the sanitized display form.",
  )
}

///|
pub fn remediation_for_finding(finding : Finding) -> Remediation {
  match finding.risk {
    Styling => styling_remediation(finding)
    CursorMovement => cursor_remediation(finding)
    ScreenRewrite => rewrite_remediation(finding)
    ScreenErase => erase_remediation(finding)
    TerminalQuery => query_remediation(finding)
    ModeChange => mode_remediation(finding)
    TitleChange => title_remediation(finding)
    Hyperlink => hyperlink_remediation(finding)
    ClipboardWrite => clipboard_remediation(finding)
    Notification => notification_remediation(finding)
    WorkingDirectory => working_directory_remediation(finding)
    FileTransfer => file_transfer_remediation(finding)
    DeviceControl => device_control_remediation(finding)
    BidirectionalText => bidi_remediation(finding)
    UnknownControl => unknown_remediation(finding)
    MalformedSequence => malformed_remediation(finding)
  }
}

///|
fn same_action(left : Remediation, right : Remediation) -> Bool {
  left.rule_id == right.rule_id && left.risk == right.risk
}

///|
fn action_index(actions : Array[Remediation], action : Remediation) -> Int {
  let mut i = 0
  while i < actions.length() {
    if same_action(actions[i], action) {
      return i
    }
    i += 1
  }
  -1
}

///|
fn merge_action(existing : Remediation, incoming : Remediation) -> Remediation {
  if incoming.priority.rank() > existing.priority.rank() ||
    severity_rank(incoming.severity) > severity_rank(existing.severity) {
    incoming
  } else {
    existing
  }
}

///|
fn add_unique_action(
  actions : Array[Remediation],
  action : Remediation,
) -> Unit {
  let index = action_index(actions, action)
  if index < 0 {
    actions.push(action)
  } else {
    actions[index] = merge_action(actions[index], action)
  }
}

///|
fn has_priority(
  actions : Array[Remediation],
  priority : ActionPriority,
) -> Bool {
  for action in actions {
    if action.priority == priority {
      return true
    }
  }
  false
}

///|
fn strongest_action_priority(actions : Array[Remediation]) -> ActionPriority {
  let mut priority = Observe
  for action in actions {
    priority = stronger_priority(priority, action.priority)
  }
  priority
}

///|
fn plan_gate(profile : AuditProfile, actions : Array[Remediation]) -> String {
  if profile.is_clean() {
    "pass"
  } else if profile.has_critical() || has_priority(actions, Block) {
    "block"
  } else if has_priority(actions, Fix) {
    "fix"
  } else if has_priority(actions, Review) {
    "review"
  } else {
    "observe"
  }
}

///|
fn plan_summary(profile : AuditProfile, gate : String) -> String {
  match gate {
    "pass" => "No remediation is required for this input."
    "observe" =>
      "Low-risk terminal controls were found; keep sanitized output for reproducibility."
    "review" => "Review terminal-control findings before sharing this artifact."
    "fix" =>
      "Dangerous terminal-control behavior must be removed or regenerated before publication."
    "block" =>
      "Critical terminal-control behavior should block release until the source is understood."
    _ => profile.summary()
  }
}

///|
fn push_default_next_steps(steps : Array[String], gate : String) -> Unit {
  match gate {
    "pass" => {
      steps.push("Keep the sanitized output as the canonical artifact.")
      steps.push("Run TermProof in CI for future generated logs.")
    }
    "observe" => {
      steps.push("Publish sanitized text instead of raw terminal output.")
      steps.push("Keep the audit report next to the artifact for traceability.")
    }
    "review" => {
      steps.push(
        "Open the affected lines and confirm the producer is expected.",
      )
      steps.push(
        "Prefer plain output for issue trackers, release notes, and documentation.",
      )
    }
    "fix" => {
      steps.push(
        "Regenerate the log with no-color, no-progress, or dumb terminal settings.",
      )
      steps.push(
        "Block direct paste of the raw artifact into terminals or web comments.",
      )
    }
    "block" => {
      steps.push("Do not publish or replay the raw log.")
      steps.push(
        "Inspect the command or upstream data source that emitted the critical sequence.",
      )
      steps.push("Regenerate from a trusted source and re-run the audit.")
    }
    _ => steps.push("Review the audit result.")
  }
}

///|
fn push_risk_specific_steps(
  steps : Array[String],
  profile : AuditProfile,
) -> Unit {
  if profile_has_risk(profile, ClipboardWrite) {
    steps.push(
      "Search for OSC 52 producers and disable clipboard integration in non-interactive jobs.",
    )
  }
  if profile_has_risk(profile, Hyperlink) {
    steps.push(
      "Expand hidden terminal hyperlinks into visible, validated URLs.",
    )
  }
  if profile_has_risk(profile, BidirectionalText) {
    steps.push(
      "Review filenames and source snippets around Unicode bidi controls.",
    )
  }
  if profile_has_risk(profile, ScreenErase) {
    steps.push(
      "Reject logs that attempt to erase visible output or scrollback.",
    )
  }
  if profile_has_risk(profile, DeviceControl) {
    steps.push(
      "Treat opaque control strings as unsupported terminal extensions.",
    )
  }
}

///|
pub fn remediation_plan(result : AuditResult) -> RemediationPlan {
  let profile = profile_audit(result)
  let actions : Array[Remediation] = []
  for finding in result.findings {
    add_unique_action(actions, remediation_for_finding(finding))
  }
  let gate = plan_gate(profile, actions)
  let steps : Array[String] = []
  push_default_next_steps(steps, gate)
  push_risk_specific_steps(steps, profile)
  {
    summary: plan_summary(profile, gate),
    gate,
    blocked: gate == "block",
    actions,
    next_steps: steps,
  }
}

///|
pub fn AuditResult::remediation(self : AuditResult) -> RemediationPlan {
  remediation_plan(self)
}

///|
pub fn Remediation::one_line(self : Remediation) -> String {
  "\{self.priority.name()} \{self.rule_id}: \{self.title}"
}

///|
pub fn Remediation::detail(self : Remediation) -> String {
  self.one_line() +
  "\nwhy: " +
  self.why +
  "\nnow: " +
  self.immediate +
  "\ndefault: " +
  self.safe_default +
  "\nlong-term: " +
  self.long_term
}

///|
pub fn RemediationPlan::strongest_priority(
  self : RemediationPlan,
) -> ActionPriority {
  strongest_action_priority(self.actions)
}

///|
pub fn RemediationPlan::action_count(self : RemediationPlan) -> Int {
  self.actions.length()
}

///|
pub fn RemediationPlan::has_action_for_rule(
  self : RemediationPlan,
  rule_id : String,
) -> Bool {
  for action in self.actions {
    if action.rule_id == rule_id {
      return true
    }
  }
  false
}

///|
pub fn RemediationPlan::has_action_for_risk(
  self : RemediationPlan,
  risk : RiskKind,
) -> Bool {
  for action in self.actions {
    if action.risk == risk {
      return true
    }
  }
  false
}

///|
pub fn remediation_text(plan : RemediationPlan) -> String {
  let out = StringBuilder::new()
  out.write_string("gate: ")
  out.write_string(plan.gate)
  out.write_char('\n')
  out.write_string(plan.summary)
  out.write_char('\n')
  if plan.actions.length() > 0 {
    out.write_string("actions:\n")
    for action in plan.actions {
      out.write_string("- ")
      out.write_string(action.one_line())
      out.write_char('\n')
      out.write_string("  ")
      out.write_string(action.immediate)
      out.write_char('\n')
    }
  }
  if plan.next_steps.length() > 0 {
    out.write_string("next steps:\n")
    for step in plan.next_steps {
      out.write_string("- ")
      out.write_string(step)
      out.write_char('\n')
    }
  }
  out.to_string()
}

///|
fn write_action_markdown(out : StringBuilder, action : Remediation) -> Unit {
  out.write_string("### ")
  out.write_string(markdown_escape(action.title))
  out.write_char('\n')
  out.write_char('\n')
  out.write_string("- Rule: `")
  out.write_string(markdown_escape(action.rule_id))
  out.write_string("`\n")
  out.write_string("- Risk: ")
  out.write_string(markdown_escape(risk_name(action.risk)))
  out.write_char('\n')
  out.write_string("- Severity: ")
  out.write_string(markdown_escape(severity_name(action.severity)))
  out.write_char('\n')
  out.write_string("- Priority: ")
  out.write_string(markdown_escape(action.priority.name()))
  out.write_char('\n')
  out.write_string("- Why: ")
  out.write_string(markdown_escape(action.why))
  out.write_char('\n')
  out.write_string("- Immediate action: ")
  out.write_string(markdown_escape(action.immediate))
  out.write_char('\n')
  out.write_string("- Safe default: ")
  out.write_string(markdown_escape(action.safe_default))
  out.write_char('\n')
  out.write_string("- Long-term fix: ")
  out.write_string(markdown_escape(action.long_term))
  out.write_string("\n\n")
}

///|
pub fn remediation_markdown(plan : RemediationPlan) -> String {
  let out = StringBuilder::new()
  out.write_string("## Remediation plan\n\n")
  out.write_string("| Field | Value |\n|---|---|\n")
  out.write_string("| Gate | ")
  out.write_string(markdown_escape(plan.gate))
  out.write_string(" |\n")
  out.write_string("| Blocked | ")
  out.write_string(if plan.blocked { "yes" } else { "no" })
  out.write_string(" |\n")
  out.write_string("| Summary | ")
  out.write_string(markdown_escape(plan.summary))
  out.write_string(" |\n\n")
  if plan.actions.length() == 0 {
    out.write_string("No remediation actions are required.\n\n")
  } else {
    for action in plan.actions {
      write_action_markdown(out, action)
    }
  }
  if plan.next_steps.length() > 0 {
    out.write_string("### Next steps\n\n")
    for step in plan.next_steps {
      out.write_string("- ")
      out.write_string(markdown_escape(step))
      out.write_char('\n')
    }
  }
  out.to_string()
}

///|
fn write_action_json(out : StringBuilder, action : Remediation) -> Unit {
  out.write_string("{\"rule_id\":")
  out.write_string(json_quote(action.rule_id))
  out.write_string(",\"risk\":")
  out.write_string(json_quote(risk_name(action.risk)))
  out.write_string(",\"severity\":")
  out.write_string(json_quote(severity_name(action.severity)))
  out.write_string(",\"priority\":")
  out.write_string(json_quote(action.priority.name()))
  out.write_string(",\"title\":")
  out.write_string(json_quote(action.title))
  out.write_string(",\"why\":")
  out.write_string(json_quote(action.why))
  out.write_string(",\"immediate\":")
  out.write_string(json_quote(action.immediate))
  out.write_string(",\"safe_default\":")
  out.write_string(json_quote(action.safe_default))
  out.write_string(",\"long_term\":")
  out.write_string(json_quote(action.long_term))
  out.write_char('}')
}

///|
pub fn remediation_json(plan : RemediationPlan) -> String {
  let out = StringBuilder::new()
  out.write_string("{\"schema\":\"urn:termproof:remediation-v1\"")
  out.write_string(",\"gate\":")
  out.write_string(json_quote(plan.gate))
  out.write_string(",\"blocked\":")
  out.write_string(if plan.blocked { "true" } else { "false" })
  out.write_string(",\"summary\":")
  out.write_string(json_quote(plan.summary))
  out.write_string(",\"actions\":[")
  let mut first = true
  for action in plan.actions {
    if !first {
      out.write_char(',')
    }
    first = false
    write_action_json(out, action)
  }
  out.write_string("],\"next_steps\":[")
  first = true
  for step in plan.next_steps {
    if !first {
      out.write_char(',')
    }
    first = false
    out.write_string(json_quote(step))
  }
  out.write_string("]}")
  out.to_string()
}

///|
pub fn finding_policy_note(finding : Finding) -> String {
  "\{finding.rule_id}: " + policy_hint_for_decision(finding.decision)
}

///|
pub fn finding_remediation_text(finding : Finding) -> String {
  remediation_for_finding(finding).detail()
}

///|
pub fn should_block_release(result : AuditResult) -> Bool {
  remediation_plan(result).blocked
}

///|
pub fn remediation_gate(result : AuditResult) -> String {
  remediation_plan(result).gate
}

///|
pub fn remediation_summary(result : AuditResult) -> String {
  remediation_plan(result).summary
}