///|
pub(all) struct ProcessingManifest {
  document_id : String
  input_checksum : String
  output_checksum : String
  rule_pack_checksum : String
  policy_name : String
  finding_count : Int
  applied_count : Int
  generated_at : String
} derive(Debug, Eq)

///|
pub(all) struct RetentionPolicy {
  keep_original : Bool
  keep_audit : Bool
  keep_offsets : Bool
  redact_logs : Bool
} derive(Debug, Eq)

///|
pub fn RetentionPolicy::safe_default() -> RetentionPolicy {
  {
    keep_original: false,
    keep_audit: true,
    keep_offsets: true,
    redact_logs: true,
  }
}

///|
pub fn RetentionPolicy::minimal() -> RetentionPolicy {
  {
    keep_original: false,
    keep_audit: false,
    keep_offsets: false,
    redact_logs: true,
  }
}

///|
pub fn retention_summary(policy : RetentionPolicy) -> String {
  [
    "keep_original=\{policy.keep_original}",
    "keep_audit=\{policy.keep_audit}",
    "keep_offsets=\{policy.keep_offsets}",
    "redact_logs=\{policy.redact_logs}",
  ].join("\n")
}

///|
pub fn make_manifest(
  document_id : String,
  input : String,
  result : DeidResult,
  pack : RulePack,
  generated_at : String,
) -> ProcessingManifest {
  {
    document_id,
    input_checksum: stable_hash(input),
    output_checksum: stable_hash(result.text),
    rule_pack_checksum: pack_checksum(pack),
    policy_name: "default",
    finding_count: result.findings.length(),
    applied_count: result.offsets.length(),
    generated_at,
  }
}

///|
pub fn manifest_to_json(manifest : ProcessingManifest) -> String {
  "{" +
  "\"document_id\":\{json_string(manifest.document_id)}," +
  "\"input_checksum\":\{json_string(manifest.input_checksum)}," +
  "\"output_checksum\":\{json_string(manifest.output_checksum)}," +
  "\"rule_pack_checksum\":\{json_string(manifest.rule_pack_checksum)}," +
  "\"policy_name\":\{json_string(manifest.policy_name)}," +
  "\"finding_count\":\{manifest.finding_count}," +
  "\"applied_count\":\{manifest.applied_count}," +
  "\"generated_at\":\{json_string(manifest.generated_at)}" +
  "}"
}

///|
pub fn manifest_checksum(manifest : ProcessingManifest) -> String {
  stable_hash(manifest_to_json(manifest))
}

///|
pub fn manifest_is_consistent(
  manifest : ProcessingManifest,
  result : DeidResult,
) -> Bool {
  manifest.output_checksum == stable_hash(result.text) &&
  manifest.finding_count == result.findings.length() &&
  manifest.applied_count == result.offsets.length()
}

///|
pub fn redact_with_manifest(
  document_id : String,
  input : String,
  config : RedactionConfig,
  generated_at : String,
) -> (DeidResult, ProcessingManifest) raise DeidError {
  let result = redact_with_config(input, config)
  let manifest = make_manifest(
    document_id,
    input,
    result,
    default_rule_pack(),
    generated_at,
  )
  (result, manifest)
}

///|
pub fn safe_audit_export(
  report : AuditReport,
  retention : RetentionPolicy,
) -> String {
  if retention.keep_audit {
    if retention.keep_offsets {
      audit_to_json(report)
    } else {
      audit_plain_text(report)
    }
  } else {
    audit_summary(report)
  }
}

///|
pub fn safe_finding_export(
  findings : Array[Finding],
  retention : RetentionPolicy,
) -> String {
  if retention.keep_audit {
    if retention.keep_offsets {
      findings_csv(findings)
    } else {
      findings
      .map(fn(item) {
        "\{item.id},\{phi_kind_name(item.kind)},\{item.confidence}"
      })
      .join("\n")
    }
  } else {
    "findings=\{findings.length()}"
  }
}

///|
pub fn safe_text_export(
  result : DeidResult,
  retention : RetentionPolicy,
) -> String {
  if retention.redact_logs {
    result.text
  } else {
    result.text
  }
}

///|
pub fn sanitize_log_message(
  message : String,
  config : RedactionConfig,
) -> String raise DeidError {
  redact_with_config(message, config).text
}

///|
pub fn sanitize_log_batch(
  messages : Array[String],
  config : RedactionConfig,
) -> Array[String] raise DeidError {
  messages.map(message => sanitize_log_message(message, config))
}

///|
pub fn data_inventory(input : String, result : DeidResult) -> Map[String, Int] {
  let inventory = privacy_class_counts(result.findings)
  inventory["documents"] = 1
  inventory["input_characters"] = input.char_length()
  inventory["output_characters"] = result.text.char_length()
  inventory
}

///|
pub fn data_inventory_text(input : String, result : DeidResult) -> String {
  let inventory = data_inventory(input, result)
  let lines = []
  for key, value in inventory {
    lines.push("\{key}=\{value}")
  }
  lines.sort()
  lines.join("\n")
}

///|
pub fn audit_for_external_review(input : String, result : DeidResult) -> String {
  [
    "contract_passes=\{output_contract_passes(input, result)}",
    "risk=\{audit_risk_level(result.audit)}",
    "inventory:",
    data_inventory_text(input, result),
    "report_checksum=\{report_checksum(result.audit)}",
  ].join("\n")
}

///|
pub fn compliance_boundary_notice() -> String {
  "Heuristic text detection supports engineering workflows; institutional review, validation, and policy controls remain required."
}

///|
pub fn safe_export_contract(
  input : String,
  result : DeidResult,
) -> Array[String] {
  let issues = output_contract_issues(input, result)
  if issues.is_empty() {
    ["pass"]
  } else {
    issues
  }
}

///|
pub fn manifest_csv(manifests : Array[ProcessingManifest]) -> String {
  let lines = [
    "document_id,input_checksum,output_checksum,finding_count,applied_count,generated_at",
  ]
  for item in manifests {
    lines.push(
      [
        csv_cell(item.document_id),
        item.input_checksum,
        item.output_checksum,
        "\{item.finding_count}",
        "\{item.applied_count}",
        csv_cell(item.generated_at),
      ].join(","),
    )
  }
  lines.join("\n")
}