///|
/// Additional deterministic rules for outpatient notes, laboratory records,
/// insurance forms, and mixed Chinese-English exports.
pub fn extended_rules() -> Array[Rule] {
  [
    rule(
      "bank_card_context",
      "Bank card number",
      Insurance,
      "(银行卡|借记卡|信用卡|Card)[:: ]{1,3}[0-9 -]{13,23}",
      "[CARD]",
      88,
      contextual=true,
    ),
    rule(
      "birth_date_context",
      "Date of birth",
      Date,
      "(出生日期|出生|DOB|Date of Birth)[:: ]{1,3}(19|20)\\d{2}[-/.年](0?[1-9]|1[0-2])[-/.月]([0-2]?\\d|3[0-1])日?",
      "[DOB]",
      94,
      contextual=true,
    ),
    rule(
      "time_context",
      "Clinical time",
      Date,
      "(就诊时间|采样时间|手术时间|Time)[:: ]{1,3}(19|20)\\d{2}[-/.年](0?[1-9]|1[0-2])[-/.月]([0-2]?\\d|3[0-1])([日T ]|$)",
      "[TIME]",
      78,
      contextual=true,
    ),
    rule(
      "ipv4",
      "IPv4 address",
      Custom("network_address"),
      "\\b([0-9]{1,3}\\.){3}[0-9]{1,3}\\b",
      "[IP]",
      84,
    ),
    rule(
      "url",
      "URL",
      Custom("url"),
      "https?://[A-Za-z0-9./_?=&%:#-]{6,160}",
      "[URL]",
      82,
    ),
    rule(
      "uuid",
      "UUID",
      Custom("uuid"),
      "\\b[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\\b",
      "[UUID]",
      86,
    ),
    rule(
      "imei_context",
      "Device IMEI",
      Custom("device_identifier"),
      "(IMEI|设备号|终端号)[:: ]{1,3}[0-9 -]{14,20}",
      "[DEVICE]",
      92,
      contextual=true,
    ),
    rule(
      "mac_address",
      "MAC address",
      Custom("device_identifier"),
      "\\b([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}\\b",
      "[DEVICE]",
      90,
    ),
    rule(
      "license_plate",
      "Vehicle license plate",
      Custom("vehicle_identifier"),
      "(车牌|车号)[:: ]{1,3}[A-Z\\u4e00-\\u9fff][A-Z][A-Z0-9]{5,6}",
      "[PLATE]",
      86,
      contextual=true,
    ),
    rule(
      "postal_code_context",
      "Postal code",
      Address,
      "(邮编|邮政编码|ZIP|Postal Code)[:: ]{1,3}[0-9A-Za-z -]{4,10}",
      "[POSTAL]",
      84,
      contextual=true,
    ),
    rule(
      "lab_accession_context",
      "Laboratory accession",
      MedicalRecord,
      "(检验号|样本号|标本号|Accession|Lab ID)[:: ]{1,3}[A-Za-z0-9_-]{4,24}",
      "[LAB]",
      91,
      contextual=true,
    ),
    rule(
      "visit_id_context",
      "Visit identifier",
      MedicalRecord,
      "(就诊号|门诊ID|Visit ID|Encounter)[:: ]{1,3}[A-Za-z0-9_-]{4,24}",
      "[VISIT]",
      90,
      contextual=true,
    ),
    rule(
      "prescription_context",
      "Prescription identifier",
      MedicalRecord,
      "(处方号|处方编号|Prescription)[:: ]{1,3}[A-Za-z0-9_-]{4,24}",
      "[RX]",
      88,
      contextual=true,
    ),
    rule(
      "diagnosis_code_context",
      "Diagnosis code",
      MedicalRecord,
      "(诊断编码|ICD-?10|Diagnosis Code)[:: ]{1,3}[A-Z][0-9]{2,4}(\\.[0-9A-Z]{1,4})?",
      "[DIAGNOSIS]",
      83,
      contextual=true,
    ),
    rule(
      "fax_context",
      "Fax number",
      Phone,
      "(传真|Fax)[:: ]{1,3}[+0-9() .-]{7,20}",
      "[FAX]",
      80,
      contextual=true,
    ),
    rule(
      "wechat_context",
      "WeChat identifier",
      Custom("online_identifier"),
      "(微信|WeChat|Vx)[:: ]{1,3}[A-Za-z][A-Za-z0-9_-]{5,30}",
      "[ACCOUNT]",
      87,
      contextual=true,
    ),
    rule(
      "qq_context",
      "QQ identifier",
      Custom("online_identifier"),
      "(QQ)[:: ]{1,3}[0-9]{5,12}",
      "[ACCOUNT]",
      85,
      contextual=true,
    ),
    rule(
      "tax_identifier_context",
      "Tax identifier",
      Insurance,
      "(统一社会信用代码|税号|Tax ID)[:: ]{1,3}[0-9A-Z-]{8,24}",
      "[TAX_ID]",
      88,
      contextual=true,
    ),
    rule(
      "iban_context",
      "IBAN",
      Insurance,
      "(IBAN|国际银行账号)[:: ]{1,3}[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}",
      "[IBAN]",
      90,
      contextual=true,
    ),
    rule(
      "emergency_contact_context",
      "Emergency contact",
      PersonName,
      "(紧急联系人|Emergency Contact)[:: ]{1,3}[\\u4e00-\\u9fff]{2,6}",
      "[CONTACT]",
      90,
      contextual=true,
    ),
    rule(
      "operator_context",
      "Operator or staff name",
      PersonName,
      "(医生|护士|经办人|Operator|Clinician)[:: ]{1,3}[\\u4e00-\\u9fffA-Z][\\u4e00-\\u9fffA-Za-z .'-]{1,24}",
      "[STAFF]",
      82,
      contextual=true,
    ),
    rule(
      "street_address_en",
      "English street address",
      Address,
      "\\b[0-9]{1,6} [A-Z][A-Za-z0-9 .'-]{3,48}(Street|St\\.|Road|Rd\\.|Avenue|Ave\\.|Lane|Ln\\.)\\b",
      "[ADDRESS]",
      80,
    ),
    rule(
      "passport_context",
      "Passport identifier in context",
      IdNumber,
      "(护照号|Passport)[:: ]{1,3}[A-Z][A-Z0-9]{5,14}",
      "[PASSPORT]",
      92,
      contextual=true,
    ),
    rule(
      "national_health_context",
      "National health identifier",
      Insurance,
      "(健康卡号|医保卡号|Health ID)[:: ]{1,3}[A-Za-z0-9-]{6,24}",
      "[HEALTH_ID]",
      91,
      contextual=true,
    ),
    rule(
      "email_context",
      "Contextual email",
      Email,
      "(邮箱|Email|E-mail)[:: ]{1,3}[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}",
      "[EMAIL]",
      97,
      contextual=true,
    ),
  ]
}

///|
pub fn comprehensive_rules() -> Array[Rule] {
  let all = builtin_rules()
  all.append(extended_rules())
  all
}

///|
pub fn rules_for_kind(rules : Array[Rule], kind : PhiKind) -> Array[Rule] {
  rules.filter(fn(item) { item.kind == kind })
}

///|
pub fn rules_above_confidence(
  rules : Array[Rule],
  threshold : Int,
) -> Array[Rule] {
  rules.filter(fn(item) { item.confidence >= threshold })
}

///|
pub fn rule_ids(rules : Array[Rule]) -> Array[String] {
  rules.map(fn(item) { item.id })
}

///|
pub fn rule_count_by_kind(rules : Array[Rule]) -> Map[String, Int] {
  let result : Map[String, Int] = Map([])
  for item in rules {
    let key = phi_kind_name(item.kind)
    result[key] = result.get_or_default(key, 0) + 1
  }
  result
}

///|
pub fn rule_confidence_histogram(rules : Array[Rule]) -> Map[String, Int] {
  let result : Map[String, Int] = Map([])
  for item in rules {
    let key = "\{confidence_band(item.confidence)}"
    result[key] = result.get_or_default(key, 0) + 1
  }
  result
}

///|
pub fn rule_ids_with_prefix(
  rules : Array[Rule],
  prefix : String,
) -> Array[String] {
  rules
  .filter(fn(item) { item.id.has_prefix(prefix) })
  .map(fn(item) { item.id })
}

///|
pub fn rules_for_locale(
  rules : Array[Rule],
  locale : LocaleHint,
) -> Array[Rule] {
  match locale {
    Chinese =>
      rules.filter(fn(item) {
        item.id.has_prefix("cn_") ||
        item.id.has_prefix("insurance") ||
        item.id.contains("context") ||
        item.id == "email"
      })
    English =>
      rules.filter(fn(item) {
        item.id.has_prefix("en_") ||
        item.id == "us_phone" ||
        item.id.contains("context") ||
        item.id == "email"
      })
    Mixed | Neutral => rules
  }
}

///|
pub fn disabled_rule_ids(rules : Array[Rule]) -> Array[String] {
  rules.filter(fn(item) { !item.enabled }).map(fn(item) { item.id })
}

///|
pub fn rule_surface_report(rules : Array[Rule]) -> String {
  let by_kind = rule_count_by_kind(rules)
  let by_band = rule_confidence_histogram(rules)
  let lines = [
    "rules=\{rules.length()}",
    "enabled=\{enabled_rules(rules).length()}",
    "disabled=\{disabled_rule_ids(rules).length()}",
    "kinds=\{by_kind.length()}",
    "confidence_bands=\{by_band.length()}",
  ]
  lines.join("\n")
}