///|
/// Control class used to describe how a field may leave a trust boundary.
pub(all) enum ComplianceClass {
  Public
  Internal
  Sensitive
  Restricted
  Prohibited
} derive(Debug, Eq, Compare)

///|
pub(all) enum RetentionDecision {
  RetainUntil
  DeleteNow
  LegalHold
  UnknownRetention
} derive(Debug, Eq)

///|
pub(all) struct ComplianceRetentionPolicy {
  name : String
  days : Int
  decision : RetentionDecision
  legal_hold : Bool
  reason : String
} derive(Debug, Eq)

///|
pub(all) struct AccessEvent {
  actor : String
  operation : String
  document_id : String
  scope : String
  allowed : Bool
  reason : String
  sequence : Int
} derive(Debug, Eq)

///|
pub(all) struct ComplianceControl {
  control_id : String
  description : String
  passed : Bool
  class : ComplianceClass
  evidence : String
} derive(Debug, Eq)

///|
pub(all) struct ComplianceReport {
  document_id : String
  controls : Array[ComplianceControl]
  access_events : Array[AccessEvent]
  passed : Bool
  checksum : String
} derive(Debug)

///|
pub fn compliance_class_name(value : ComplianceClass) -> String {
  match value {
    Public => "public"
    Internal => "internal"
    Sensitive => "sensitive"
    Restricted => "restricted"
    Prohibited => "prohibited"
  }
}

///|
pub fn retention_decision_name(value : RetentionDecision) -> String {
  match value {
    RetainUntil => "retain_until"
    DeleteNow => "delete_now"
    LegalHold => "legal_hold"
    UnknownRetention => "unknown"
  }
}

///|
pub fn compliance_class_for_kind(kind : PhiKind) -> ComplianceClass {
  match kind {
    IdNumber | MedicalRecord | Insurance => Restricted
    PersonName | Phone | Email | Address | Date => Sensitive
    Organization => Internal
    Custom(_) => Internal
  }
}

///|
pub fn compliance_class_for_path(path : String) -> ComplianceClass {
  let value = path.to_lower()
  if value.contains("password") ||
    value.contains("secret") ||
    value.contains("token") {
    Prohibited
  } else if value.contains("identifier") ||
    value.contains("medical") ||
    value.contains("insurance") {
    Restricted
  } else if value.contains("name") ||
    value.contains("phone") ||
    value.contains("email") ||
    value.contains("address") {
    Sensitive
  } else if value.contains("organization") || value.contains("department") {
    Internal
  } else {
    Public
  }
}

///|
pub fn compliance_retention_policy(
  name : String,
  days : Int,
  reason : String,
) -> ComplianceRetentionPolicy {
  {
    name,
    days,
    decision: if days <= 0 {
      DeleteNow
    } else {
      RetainUntil
    },
    legal_hold: false,
    reason,
  }
}

///|
pub fn compliance_retention_legal_hold(
  policy : ComplianceRetentionPolicy,
) -> ComplianceRetentionPolicy {
  { ..policy, legal_hold: true, decision: LegalHold }
}

///|
pub fn compliance_retention_is_safe(policy : ComplianceRetentionPolicy) -> Bool {
  policy.days >= 0 &&
  policy.reason.trim().length() > 0 &&
  !(policy.decision == DeleteNow && policy.legal_hold)
}

///|
pub fn retention_bucket(days : Int) -> String {
  if days <= 0 {
    "immediate"
  } else if days <= 7 {
    "one-week"
  } else if days <= 30 {
    "one-month"
  } else if days <= 365 {
    "one-year"
  } else {
    "long-term"
  }
}

///|
pub fn compliance_retention_summary(
  policy : ComplianceRetentionPolicy,
) -> String {
  [
    "name=\{policy.name}",
    "days=\{policy.days}",
    "bucket=\{retention_bucket(policy.days)}",
    "decision=\{retention_decision_name(policy.decision)}",
    "legal_hold=\{policy.legal_hold}",
    "safe=\{compliance_retention_is_safe(policy)}",
    "reason=\{policy.reason}",
  ].join("\n")
}

///|
pub fn access_event(
  actor : String,
  operation : String,
  document_id : String,
  scope : String,
  allowed : Bool,
  reason : String,
  sequence : Int,
) -> AccessEvent {
  { actor, operation, document_id, scope, allowed, reason, sequence }
}

///|
pub fn access_event_is_valid(event : AccessEvent) -> Bool {
  event.actor.trim().length() > 0 &&
  event.operation.trim().length() > 0 &&
  event.document_id.trim().length() > 0 &&
  event.sequence >= 0
}

///|
pub fn access_event_summary(event : AccessEvent) -> String {
  [
    "actor=\{event.actor}",
    "operation=\{event.operation}",
    "document=\{event.document_id}",
    "scope=\{event.scope}",
    "allowed=\{event.allowed}",
    "sequence=\{event.sequence}",
    "reason=\{event.reason}",
  ].join(" ")
}

///|
pub fn compliance_control(
  control_id : String,
  description : String,
  passed : Bool,
  class : ComplianceClass,
  evidence : String,
) -> ComplianceControl {
  { control_id, description, passed, class, evidence }
}

///|
pub fn ComplianceReport::failed_controls(
  self : ComplianceReport,
) -> Array[ComplianceControl] {
  self.controls.filter(fn(control) { !control.passed })
}

///|
pub fn ComplianceReport::restricted_failures(
  self : ComplianceReport,
) -> Array[ComplianceControl] {
  self.failed_controls().filter(fn(control) { control.class >= Restricted })
}

///|
pub fn ComplianceReport::access_denials(
  self : ComplianceReport,
) -> Array[AccessEvent] {
  self.access_events.filter(fn(event) { !event.allowed })
}

///|
pub fn ComplianceReport::is_ready(self : ComplianceReport) -> Bool {
  self.passed &&
  self.failed_controls().is_empty() &&
  self.access_denials().is_empty()
}

///|
pub fn ComplianceReport::summary(self : ComplianceReport) -> String {
  [
    "document=\{self.document_id}",
    "controls=\{self.controls.length()}",
    "failed=\{self.failed_controls().length()}",
    "restricted_failures=\{self.restricted_failures().length()}",
    "access_events=\{self.access_events.length()}",
    "denials=\{self.access_denials().length()}",
    "passed=\{self.passed}",
    "checksum=\{self.checksum}",
  ].join("\n")
}

///|
pub fn control_for_field(field : ResourceField) -> ComplianceControl {
  let class = compliance_class_for_kind(field.kind)
  let passed = (field.action == Retain && class <= Internal) ||
    (field.action != Retain && class >= Sensitive)
  compliance_control(
    "FIELD_" + field.path,
    "structured field handling",
    passed,
    class,
    "action=\{resource_action_name(field.action)}",
  )
}

///|
pub fn controls_for_resource(
  document : ResourceDocument,
) -> Array[ComplianceControl] {
  document.fields.map(control_for_field)
}

///|
pub fn compliance_check_resource(
  document : ResourceDocument,
  report : ResourceReport,
) -> Array[ComplianceControl] {
  let controls = controls_for_resource(document)
  controls.push(
    compliance_control(
      "RESOURCE_REPORT",
      "resource report has no error-level issues",
      !report.issues.any(fn(issue) { issue.severity == Error }),
      Internal,
      "issues=\{report.issues.length()}",
    ),
  )
  controls
}

///|
pub fn compliance_check_output(
  input : String,
  result : DeidResult,
) -> Array[ComplianceControl] {
  [
    compliance_control(
      "OUTPUT_NO_ORIGINAL",
      "detected values are not present in output",
      quality_no_original_phi(result.text, result.findings),
      Restricted,
      "findings=\{result.findings.length()}",
    ),
    compliance_control(
      "OUTPUT_CONTRACT",
      "output contract is internally consistent",
      output_contract_passes(input, result),
      Restricted,
      "output_length=\{result.text.length()}",
    ),
    compliance_control(
      "AUDIT_PRESENT",
      "audit contains the selected findings",
      result.audit.finding_count == result.findings.length(),
      Internal,
      "audit_findings=\{result.audit.finding_count}",
    ),
  ]
}

///|
pub fn build_compliance_report(
  document_id : String,
  controls : Array[ComplianceControl],
  access_events : Array[AccessEvent],
) -> ComplianceReport {
  let passed = controls.all(fn(control) { control.passed }) &&
    access_events.all(fn(event) { event.allowed })
  {
    document_id,
    controls,
    access_events,
    passed,
    checksum: stable_hash(
      controls
      .map(fn(control) {
        control.control_id + ":\{control.passed}:" + control.evidence
      })
      .join("\n"),
    ),
  }
}

///|
pub fn compliance_control_json(control : ComplianceControl) -> String {
  "{" +
  "\"id\":\{json_escape(control.control_id)}," +
  "\"description\":\{json_escape(control.description)}," +
  "\"passed\":\{control.passed}," +
  "\"class\":\{json_escape(compliance_class_name(control.class))}," +
  "\"evidence\":\{json_escape(control.evidence)}" +
  "}"
}

///|
pub fn access_event_json(event : AccessEvent) -> String {
  "{" +
  "\"actor\":\{json_escape(event.actor)}," +
  "\"operation\":\{json_escape(event.operation)}," +
  "\"document_id\":\{json_escape(event.document_id)}," +
  "\"scope\":\{json_escape(event.scope)}," +
  "\"allowed\":\{event.allowed}," +
  "\"reason\":\{json_escape(event.reason)}," +
  "\"sequence\":\{event.sequence}" +
  "}"
}

///|
pub fn ComplianceReport::to_json(self : ComplianceReport) -> String {
  "{" +
  "\"document_id\":\{json_escape(self.document_id)}," +
  "\"passed\":\{self.passed}," +
  "\"checksum\":\{json_escape(self.checksum)}," +
  "\"controls\":[" +
  self.controls.map(compliance_control_json).join(",") +
  "],\"access_events\":[" +
  self.access_events.map(access_event_json).join(",") +
  "]}"
}

///|
pub fn ComplianceReport::to_markdown(self : ComplianceReport) -> String {
  let lines = [
    "| Control | Class | Status | Evidence |", "| --- | --- | --- | --- |",
  ]
  for control in self.controls {
    lines.push(
      "| \{control.control_id} | \{compliance_class_name(control.class)} | \{control.passed} | \{control.evidence} |",
    )
  }
  lines.push("")
  lines.push("passed=\{self.passed}; checksum=\{self.checksum}")
  lines.join("\n")
}

///|
pub fn compliance_class_counts(
  controls : Array[ComplianceControl],
) -> Map[String, Int] {
  let counts : Map[String, Int] = Map([])
  for control in controls {
    let key = compliance_class_name(control.class)
    counts[key] = counts.get_or_default(key, 0) + 1
  }
  counts
}

///|
pub fn compliance_failed_ids(
  controls : Array[ComplianceControl],
) -> Array[String] {
  controls
  .filter(fn(control) { !control.passed })
  .map(fn(control) { control.control_id })
}

///|
pub fn compliance_has_prohibited(controls : Array[ComplianceControl]) -> Bool {
  controls.any(fn(control) { control.class == Prohibited })
}

///|
pub fn compliance_has_restricted_failure(
  controls : Array[ComplianceControl],
) -> Bool {
  controls.any(fn(control) { control.class >= Restricted && !control.passed })
}

///|
pub fn compliance_requires_human_review(
  controls : Array[ComplianceControl],
) -> Bool {
  compliance_has_prohibited(controls) ||
  compliance_has_restricted_failure(controls)
}

///|
pub fn compliance_access_is_monotonic(events : Array[AccessEvent]) -> Bool {
  let mut valid = true
  for i in 1..= events[i].sequence {
      valid = false
    }
  }
  valid
}

///|
pub fn compliance_access_denial_rate(events : Array[AccessEvent]) -> Float {
  if events.is_empty() {
    0.0
  } else {
    Float::from_int(events.filter(fn(event) { !event.allowed }).length()) /
    Float::from_int(events.length())
  }
}

///|
pub fn compliance_report_checksum(report : ComplianceReport) -> String {
  stable_hash(report.to_json())
}