///|
/// Document families determine the default profile and review posture.
pub(all) enum DocumentKind {
  ClinicalNote
  DischargeSummary
  LaboratoryReport
  ImagingReport
  ReferralLetter
  BillingRecord
  Message
  StructuredResource
  Unknown
} derive(Debug, Eq)

///|
pub(all) enum RouteAction {
  StrictRedaction
  StandardRedaction
  ReviewBeforeRelease
  Reject
  Retain
} derive(Debug, Eq)

///|
pub(all) struct RouteDecision {
  kind : DocumentKind
  action : RouteAction
  profile : Profile
  confidence : Int
  reasons : Array[String]
} derive(Debug, Eq)

///|
pub(all) struct DocumentRoute {
  document_id : String
  decision : RouteDecision
  input : String
  output : String
  findings : Array[Finding]
  diagnostics : Array[Diagnostic]
} derive(Debug)

///|
pub(all) struct RouterPolicy {
  min_confidence : Int
  reject_empty : Bool
  strict_for_billing : Bool
  review_for_unknown : Bool
  config : RedactionConfig
} derive(Debug, Eq)

///|
pub(all) struct DocumentRouter {
  policy : RouterPolicy
  mut processed : Int
  mut rejected : Int
  mut reviewed : Int
} derive(Debug)

///|
pub fn document_kind_name(kind : DocumentKind) -> String {
  match kind {
    ClinicalNote => "clinical_note"
    DischargeSummary => "discharge_summary"
    LaboratoryReport => "laboratory_report"
    ImagingReport => "imaging_report"
    ReferralLetter => "referral_letter"
    BillingRecord => "billing_record"
    Message => "message"
    StructuredResource => "structured_resource"
    Unknown => "unknown"
  }
}

///|
pub fn document_profile_name(profile : Profile) -> String {
  match profile {
    ChineseClinical => "chinese_clinical"
    EnglishClinical => "english_clinical"
    HighPrecision => "high_precision"
    AllBuiltins => "all_builtins"
  }
}

///|
pub fn route_action_name(action : RouteAction) -> String {
  match action {
    StrictRedaction => "strict_redaction"
    StandardRedaction => "standard_redaction"
    ReviewBeforeRelease => "review_before_release"
    Reject => "reject"
    Retain => "retain"
  }
}

///|
pub fn RouterPolicy::default() -> RouterPolicy {
  {
    min_confidence: 55,
    reject_empty: true,
    strict_for_billing: true,
    review_for_unknown: true,
    config: RedactionConfig::default(),
  }
}

///|
pub fn RouterPolicy::strict() -> RouterPolicy {
  {
    ..RouterPolicy::default(),
    min_confidence: 70,
    config: { ..RedactionConfig::default(), policy: RedactionPolicy::strict() },
  }
}

///|
pub fn DocumentRouter::new(policy : RouterPolicy) -> DocumentRouter {
  { policy, processed: 0, rejected: 0, reviewed: 0 }
}

///|
pub fn DocumentRouter::default() -> DocumentRouter {
  DocumentRouter::new(RouterPolicy::default())
}

///|
pub fn DocumentRouter::reset(self : DocumentRouter) -> Unit {
  self.processed = 0
  self.rejected = 0
  self.reviewed = 0
}

///|
pub fn DocumentRouter::processed_count(self : DocumentRouter) -> Int {
  self.processed
}

///|
pub fn DocumentRouter::rejected_count(self : DocumentRouter) -> Int {
  self.rejected
}

///|
pub fn DocumentRouter::reviewed_count(self : DocumentRouter) -> Int {
  self.reviewed
}

///|
fn keyword_score(text : String, keywords : Array[String]) -> Int {
  let mut score = 0
  for keyword in keywords {
    if text.to_lower().contains(keyword.to_lower()) {
      score += 20
    }
  }
  if score > 100 {
    100
  } else {
    score
  }
}

///|
pub fn classify_document(text : String) -> (DocumentKind, Int, Array[String]) {
  let candidates : Array[(DocumentKind, Array[String])] = [
    (DischargeSummary, ["出院小结", "出院记录", "discharge summary"]),
    (LaboratoryReport, ["检验报告", "化验", "laboratory", "lab result"]),
    (ImagingReport, ["影像报告", "放射", "imaging", "radiology"]),
    (ReferralLetter, ["转诊", "会诊", "referral", "consultation"]),
    (BillingRecord, ["费用", "账单", "billing", "invoice", "insurance"]),
    (Message, ["短信", "聊天", "message", "chat", "联系"]),
    (StructuredResource, ["resource_type=", "fhir", "hl7", "json"]),
    (
      ClinicalNote,
      ["病史", "主诉", "现病史", "clinical note", "assessment"],
    ),
  ]
  let mut best = DocumentKind::Unknown
  let mut best_score = 0
  let mut reasons : Array[String] = []
  for candidate in candidates {
    let score = keyword_score(text, candidate.1)
    if score > best_score {
      best = candidate.0
      best_score = score
      reasons = candidate.1
    }
  }
  (best, best_score, reasons)
}

///|
pub fn document_kind_profile(kind : DocumentKind) -> Profile {
  match kind {
    ClinicalNote | DischargeSummary | ReferralLetter => AllBuiltins
    LaboratoryReport | ImagingReport => HighPrecision
    BillingRecord => HighPrecision
    Message => EnglishClinical
    StructuredResource => AllBuiltins
    Unknown => AllBuiltins
  }
}

///|
pub fn document_kind_action(
  kind : DocumentKind,
  confidence : Int,
  policy : RouterPolicy,
) -> RouteAction {
  if confidence < policy.min_confidence && policy.review_for_unknown {
    ReviewBeforeRelease
  } else {
    match kind {
      BillingRecord if policy.strict_for_billing => StrictRedaction
      Unknown if policy.review_for_unknown => ReviewBeforeRelease
      LaboratoryReport | ImagingReport => StrictRedaction
      _ => StandardRedaction
    }
  }
}

///|
pub fn route_text(text : String, policy : RouterPolicy) -> RouteDecision {
  let (kind, confidence, reasons) = classify_document(text)
  {
    kind,
    action: document_kind_action(kind, confidence, policy),
    profile: document_kind_profile(kind),
    confidence,
    reasons,
  }
}

///|
pub fn route_decision_summary(decision : RouteDecision) -> String {
  let reasons = decision.reasons.join(",")
  [
    "kind=\{document_kind_name(decision.kind)}",
    "action=\{route_action_name(decision.action)}",
    "profile=\{document_profile_name(decision.profile)}",
    "confidence=\{decision.confidence}",
    "reasons=\{reasons}",
  ].join("\n")
}

///|
pub fn route_decision_json(decision : RouteDecision) -> String {
  let reasons = decision.reasons.map(json_escape).join(",")
  "{" +
  "\"kind\":\{json_escape(document_kind_name(decision.kind))}," +
  "\"action\":\{json_escape(route_action_name(decision.action))}," +
  "\"profile\":\{json_escape(document_profile_name(decision.profile))}," +
  "\"confidence\":\{decision.confidence}," +
  "\"reasons\":[" +
  reasons +
  "]}"
}

///|
pub fn DocumentRouter::route(
  self : DocumentRouter,
  document_id : String,
  input : String,
) -> DocumentRoute raise DeidError {
  self.processed += 1
  if input.trim().is_empty() && self.policy.reject_empty {
    self.rejected += 1
    let decision = {
      kind: Unknown,
      action: Reject,
      profile: AllBuiltins,
      confidence: 100,
      reasons: ["empty input"],
    }
    { document_id, decision, input, output: "", findings: [], diagnostics: [] }
  } else {
    let decision = route_text(input, self.policy)
    match decision.action {
      Reject => {
        self.rejected += 1
        {
          document_id,
          decision,
          input,
          output: "",
          findings: [],
          diagnostics: [],
        }
      }
      ReviewBeforeRelease => {
        self.reviewed += 1
        let pipeline = pipeline_for_profile(
          input,
          decision.profile,
          self.policy.config.policy.mode,
        )
        {
          document_id,
          decision,
          input,
          output: pipeline.text(),
          findings: pipeline.findings,
          diagnostics: pipeline.diagnostics,
        }
      }
      StrictRedaction | StandardRedaction => {
        let mode = if decision.action == StrictRedaction {
          PreserveLength
        } else {
          self.policy.config.policy.mode
        }
        let pipeline = pipeline_for_profile(input, decision.profile, mode)
        {
          document_id,
          decision,
          input,
          output: pipeline.text(),
          findings: pipeline.findings,
          diagnostics: pipeline.diagnostics,
        }
      }
      Retain =>
        {
          document_id,
          decision,
          input,
          output: input,
          findings: [],
          diagnostics: [],
        }
    }
  }
}

///|
pub fn DocumentRouter::route_batch(
  self : DocumentRouter,
  items : Array[BatchItem],
) -> Array[DocumentRoute] raise DeidError {
  let routes = []
  for item in items {
    routes.push(self.route(item.id, item.text))
  }
  routes
}

///|
pub fn DocumentRouter::route_sections(
  self : DocumentRouter,
  document_id : String,
  input : String,
) -> Array[DocumentRoute] raise DeidError {
  let routes = []
  let sections = sectionize(input)
  if sections.is_empty() {
    routes.push(self.route(document_id, input))
  } else {
    for section in sections {
      routes.push(
        self.route(
          document_id + ":" + section_kind_name(section.kind),
          section.text,
        ),
      )
    }
  }
  routes
}

///|
pub fn document_route_is_release_ready(route : DocumentRoute) -> Bool {
  route.decision.action != Reject &&
  !route.diagnostics.any(fn(item) { item.severity == Error }) &&
  output_contract_passes(route.input, {
    text: route.output,
    findings: route.findings,
    offsets: [],
    audit: {
      input_length: route.input.length(),
      output_length: route.output.length(),
      finding_count: route.findings.length(),
      applied_count: route.findings.length(),
      counts: Map([]),
      findings: route.findings,
      offsets: [],
    },
  })
}

///|
pub fn document_route_summary(route : DocumentRoute) -> String {
  [
    "document_id=\{route.document_id}",
    route_decision_summary(route.decision),
    "input_length=\{route.input.length()}",
    "output_length=\{route.output.length()}",
    "findings=\{route.findings.length()}",
    "diagnostics=\{route.diagnostics.length()}",
    "ready=\{document_route_is_release_ready(route)}",
  ].join("\n")
}

///|
pub fn document_route_json(route : DocumentRoute) -> String {
  "{" +
  "\"document_id\":\{json_escape(route.document_id)}," +
  "\"decision\":\{route_decision_json(route.decision)}," +
  "\"input_length\":\{route.input.length()}," +
  "\"output_length\":\{route.output.length()}," +
  "\"finding_count\":\{route.findings.length()}," +
  "\"diagnostic_count\":\{route.diagnostics.length()}," +
  "\"ready\":\{document_route_is_release_ready(route)}" +
  "}"
}

///|
pub fn document_route_kind_counts(
  routes : Array[DocumentRoute],
) -> Map[String, Int] {
  let counts : Map[String, Int] = Map([])
  for route in routes {
    let key = document_kind_name(route.decision.kind)
    counts[key] = counts.get_or_default(key, 0) + 1
  }
  counts
}

///|
pub fn document_route_action_counts(
  routes : Array[DocumentRoute],
) -> Map[String, Int] {
  let counts : Map[String, Int] = Map([])
  for route in routes {
    let key = route_action_name(route.decision.action)
    counts[key] = counts.get_or_default(key, 0) + 1
  }
  counts
}

///|
pub fn document_route_findings(routes : Array[DocumentRoute]) -> Array[Finding] {
  let findings = []
  for route in routes {
    findings.append(route.findings)
  }
  findings
}

///|
pub fn document_route_rejected(routes : Array[DocumentRoute]) -> Array[String] {
  routes
  .filter(fn(route) { route.decision.action == Reject })
  .map(fn(route) { route.document_id })
}

///|
pub fn document_route_reviewed(routes : Array[DocumentRoute]) -> Array[String] {
  routes
  .filter(fn(route) { route.decision.action == ReviewBeforeRelease })
  .map(fn(route) { route.document_id })
}

///|
pub fn document_route_ready_count(routes : Array[DocumentRoute]) -> Int {
  routes.filter(document_route_is_release_ready).length()
}

///|
pub fn document_route_checksum(routes : Array[DocumentRoute]) -> String {
  stable_hash(
    routes.map(fn(route) { route.document_id + ":" + route.output }).join("\n"),
  )
}

///|
pub fn DocumentRouter::summary(self : DocumentRouter) -> String {
  [
    "processed=\{self.processed}",
    "rejected=\{self.rejected}",
    "reviewed=\{self.reviewed}",
    "policy_min_confidence=\{self.policy.min_confidence}",
  ].join("\n")
}

///|
pub fn document_kind_keywords(kind : DocumentKind) -> Array[String] {
  match kind {
    ClinicalNote => ["病史", "主诉", "现病史", "assessment", "note"]
    DischargeSummary => ["出院", "discharge", "summary"]
    LaboratoryReport => ["检验", "化验", "laboratory", "lab"]
    ImagingReport => ["影像", "放射", "imaging", "radiology"]
    ReferralLetter => ["转诊", "会诊", "referral"]
    BillingRecord => ["费用", "账单", "billing", "invoice"]
    Message => ["短信", "聊天", "message", "chat"]
    StructuredResource => ["resource_type", "fhir", "hl7"]
    Unknown => []
  }
}

///|
pub fn document_kind_contains(text : String, kind : DocumentKind) -> Bool {
  keyword_score(text, document_kind_keywords(kind)) > 0
}

///|
pub fn document_kind_candidates(text : String) -> Array[DocumentKind] {
  [
    ClinicalNote,
    DischargeSummary,
    LaboratoryReport,
    ImagingReport,
    ReferralLetter,
    BillingRecord,
    Message,
    StructuredResource,
  ].filter(fn(kind) { document_kind_contains(text, kind) })
}

///|
pub fn document_route_reasons(route : DocumentRoute) -> Array[String] {
  route.decision.reasons
}