///|
pub(all) struct PlaceholderRegistry {
  values : Map[String, String]
} derive(Debug, Eq)

///|
pub(all) struct PseudonymRegistry {
  salt : String
  values : Map[String, String]
} derive(Debug, Eq)

///|
pub fn PlaceholderRegistry::default() -> PlaceholderRegistry {
  { values: Map([]) }
}

///|
pub fn PseudonymRegistry::new(salt : String) -> PseudonymRegistry {
  { salt, values: Map([]) }
}

///|
pub fn PlaceholderRegistry::set(
  self : PlaceholderRegistry,
  kind : PhiKind,
  value : String,
) -> Unit {
  self.values[phi_kind_name(kind)] = value
}

///|
pub fn PlaceholderRegistry::get(
  self : PlaceholderRegistry,
  kind : PhiKind,
) -> String {
  self.values.get_or_default(phi_kind_name(kind), "[\{phi_kind_name(kind)}]")
}

///|
pub fn PlaceholderRegistry::contains(
  self : PlaceholderRegistry,
  kind : PhiKind,
) -> Bool {
  self.values.contains(phi_kind_name(kind))
}

///|
pub fn default_placeholders() -> PlaceholderRegistry {
  let registry = PlaceholderRegistry::default()
  registry.set(PersonName, "[NAME]")
  registry.set(IdNumber, "[ID]")
  registry.set(Phone, "[PHONE]")
  registry.set(Date, "[DATE]")
  registry.set(Email, "[EMAIL]")
  registry.set(Address, "[ADDRESS]")
  registry.set(MedicalRecord, "[MRN]")
  registry.set(Insurance, "[INSURANCE]")
  registry.set(Organization, "[ORG]")
  registry
}

///|
pub fn registry_placeholders(
  registry : PlaceholderRegistry,
  kinds : Array[PhiKind],
) -> Map[String, String] {
  let result : Map[String, String] = Map([])
  for kind in kinds {
    result[phi_kind_name(kind)] = registry.get(kind)
  }
  result
}

///|
pub fn PseudonymRegistry::token(
  self : PseudonymRegistry,
  value : String,
  kind : PhiKind,
) -> String {
  let key = phi_kind_name(kind) + "\u{1f}" + value
  match self.values.get(key) {
    Some(token) => token
    None => {
      let token = "\{phi_kind_name(kind)}_\{stable_hash(self.salt + key)}"
      self.values[key] = token
      token
    }
  }
}

///|
pub fn PseudonymRegistry::size(self : PseudonymRegistry) -> Int {
  self.values.length()
}

///|
pub fn PseudonymRegistry::checksum(self : PseudonymRegistry) -> String {
  let lines = []
  for key, value in self.values {
    lines.push(key + "=" + value)
  }
  lines.sort()
  stable_hash(lines.join("\n"))
}

///|
pub fn findings_with_placeholders(
  findings : Array[Finding],
  registry : PlaceholderRegistry,
) -> Array[Finding] {
  findings.map(fn(item) { { ..item, replacement: registry.get(item.kind) } })
}

///|
pub fn redact_with_placeholders(
  input : String,
  registry : PlaceholderRegistry,
) -> DeidResult raise DeidError {
  let findings = findings_with_placeholders(scan(input), registry)
  let (text, offsets) = apply_findings(input, findings)
  {
    text,
    findings,
    offsets,
    audit: build_audit(input, text, findings, offsets),
  }
}

///|
pub fn redact_with_pseudonyms(
  input : String,
  salt : String,
) -> DeidResult raise DeidError {
  let registry = PseudonymRegistry::new(salt)
  let findings = scan(input).map(fn(item) {
    { ..item, replacement: registry.token(item.text, item.kind) }
  })
  let (text, offsets) = apply_findings(input, findings)
  {
    text,
    findings,
    offsets,
    audit: build_audit(input, text, findings, offsets),
  }
}

///|
pub fn redact_with_kind_prefixes(
  input : String,
  prefix : String,
) -> DeidResult raise DeidError {
  let findings = scan(input).map(fn(item) {
    { ..item, replacement: prefix + "[" + phi_kind_name(item.kind) + "]" }
  })
  let (text, offsets) = apply_findings(input, findings)
  {
    text,
    findings,
    offsets,
    audit: build_audit(input, text, findings, offsets),
  }
}

///|
pub fn replacement_dictionary(findings : Array[Finding]) -> Map[String, String] {
  let result : Map[String, String] = Map([])
  for item in findings {
    result[item.text] = item.replacement
  }
  result
}

///|
pub fn replacement_dictionary_checksum(findings : Array[Finding]) -> String {
  let dictionary = replacement_dictionary(findings)
  let lines = []
  for key, value in dictionary {
    lines.push(key + "=" + value)
  }
  lines.sort()
  stable_hash(lines.join("\n"))
}