///|
fn masked_last4(text : String, finding : Finding) -> String {
  let length = finding.end - finding.start
  if length <= 4 {
    return "[REDACTED:" + finding.kind.to_string() + "]"
  }
  "[REDACTED:" +
  finding.kind.to_string() +
  ":*" +
  text[finding.end - 4:finding.end].to_owned() +
  "]"
}

///|
fn replacement(
  text : String,
  finding : Finding,
  style : RedactionStyle,
) -> String {
  match style {
    Marker => "[REDACTED]"
    Typed => "[REDACTED:" + finding.kind.to_string() + "]"
    PreserveLast4 => masked_last4(text, finding)
  }
}

///|
/// Redact all enabled findings. Already-produced markers contain no detector
/// pattern, so applying the same policy again is idempotent.
pub fn redact(
  text : String,
  style? : RedactionStyle = Typed,
  policy? : ScanPolicy = ScanPolicy::standard(),
) -> RedactionResult {
  let findings = scan(text, policy~)
  if findings.length() == 0 {
    return { text, findings, changed: false, }
  }
  let mut out = ""
  let mut cursor = 0
  for finding in findings {
    out += text[cursor:finding.start].to_owned()
    out += replacement(text, finding, style)
    cursor = finding.end
  }
  out += text[cursor:].to_owned()
  { text: out, findings, changed: true, }
}

///|
/// Return true only when a fresh scan finds no enabled sensitive values.
pub fn verify_clean(
  text : String,
  policy? : ScanPolicy = ScanPolicy::standard(),
) -> Bool {
  scan(text, policy~).length() == 0
}