///|
pub fn default_rule_pack() -> RulePack {
  rule_pack(
    name="clinical-default",
    version="1.0.0",
    locale=Mixed,
    description="Deterministic Chinese-English clinical PHI rules",
    rules=comprehensive_rules(),
  )
}

///|
pub fn chinese_rule_pack() -> RulePack {
  let rules = rules_for_locale(comprehensive_rules(), Chinese)
  rule_pack(
    name="clinical-zh",
    version="1.0.0",
    locale=Chinese,
    description="Chinese clinical and insurance fields",
    rules~,
  )
}

///|
pub fn english_rule_pack() -> RulePack {
  let rules = rules_for_locale(comprehensive_rules(), English)
  rule_pack(
    name="clinical-en",
    version="1.0.0",
    locale=English,
    description="English clinical and insurance fields",
    rules~,
  )
}

///|
pub fn high_precision_rule_pack() -> RulePack {
  let rules = rules_above_confidence(comprehensive_rules(), 90)
  rule_pack(
    name="clinical-high-precision",
    version="1.0.0",
    locale=Mixed,
    description="High precision identifiers for conservative pipelines",
    rules~,
  )
}

///|
pub fn pack_rule_ids(pack : RulePack) -> Array[String] {
  rule_ids(pack.rules)
}

///|
pub fn pack_enabled_rules(pack : RulePack) -> Array[Rule] {
  enabled_rules(pack.rules)
}

///|
pub fn pack_disabled_rules(pack : RulePack) -> Array[Rule] {
  pack.rules.filter(fn(item) { !item.enabled })
}

///|
pub fn pack_rule(pack : RulePack, id : String) -> Rule? {
  let mut result = None
  for item in pack.rules {
    if item.id == id {
      result = Some(item)
    }
  }
  result
}

///|
pub fn pack_has_rule(pack : RulePack, id : String) -> Bool {
  pack_rule(pack, id) is Some(_)
}

///|
pub fn pack_enable_rule(pack : RulePack, id : String) -> RulePack {
  let rules = pack.rules.map(fn(item) {
    if item.id == id {
      { ..item, enabled: true }
    } else {
      item
    }
  })
  { ..pack, rules, }
}

///|
pub fn pack_disable_rule(pack : RulePack, id : String) -> RulePack {
  let rules = pack.rules.map(fn(item) {
    if item.id == id {
      { ..item, enabled: false }
    } else {
      item
    }
  })
  { ..pack, rules, }
}

///|
pub fn pack_replace_rule(pack : RulePack, replacement : Rule) -> RulePack {
  let mut replaced = false
  let rules = pack.rules.map(fn(item) {
    if item.id == replacement.id {
      replaced = true
      replacement
    } else {
      item
    }
  })
  if !replaced {
    rules.push(replacement)
  }
  { ..pack, rules, }
}

///|
pub fn pack_filter_confidence(pack : RulePack, minimum : Int) -> RulePack {
  { ..pack, rules: pack.rules.filter(fn(item) { item.confidence >= minimum }) }
}

///|
pub fn pack_filter_kind(pack : RulePack, kind : PhiKind) -> RulePack {
  { ..pack, rules: rules_for_kind(pack.rules, kind) }
}

///|
pub fn pack_with_lifecycle(
  pack : RulePack,
  lifecycle : RuleLifecycle,
) -> RulePack {
  { ..pack, lifecycle, }
}

///|
pub fn pack_validate(pack : RulePack) -> Array[RuleIssue] {
  validate_rules(pack.rules)
}

///|
pub fn pack_is_valid(pack : RulePack) -> Bool {
  pack_validate(pack).is_empty()
}

///|
pub fn pack_summary(pack : RulePack) -> String {
  [
    "name=\{pack.name}",
    "version=\{pack.version}",
    "locale=\{pack.locale}",
    "lifecycle=\{rule_lifecycle_name(pack.lifecycle)}",
    "rules=\{pack.rules.length()}",
    "valid=\{pack_is_valid(pack)}",
  ].join("\n")
}

///|
pub fn pack_to_text(pack : RulePack) -> String {
  let lines = [pack.name + "@" + pack.version, pack.description]
  for rule in pack.rules {
    lines.push(
      [
        rule.id,
        rule.label,
        phi_kind_name(rule.kind),
        "\{rule.confidence}",
        "\{rule.enabled}",
        rule.pattern,
      ].join("\t"),
    )
  }
  lines.join("\n")
}

///|
pub fn pack_checksum(pack : RulePack) -> String {
  stable_hash(pack_to_text(pack))
}

///|
pub fn select_pack(profile : Profile) -> RulePack {
  match profile {
    ChineseClinical => chinese_rule_pack()
    EnglishClinical => english_rule_pack()
    HighPrecision => high_precision_rule_pack()
    AllBuiltins => default_rule_pack()
  }
}

///|
pub fn profile_redaction(
  input : String,
  profile : Profile,
  mode : ReplacementMode,
) -> DeidResult raise DeidError {
  let policy = with_policy_mode(RedactionPolicy::default(), mode)
  redact_with_policy(input, policy, rules=select_pack(profile).rules)
}

///|
pub fn packs_have_disjoint_ids(left : RulePack, right : RulePack) -> Bool {
  !left.rules.any(fn(a) { right.rules.any(fn(b) { a.id == b.id }) })
}

///|
pub fn merge_rule_packs(left : RulePack, right : RulePack) -> RulePack {
  let rules = left.rules.copy()
  for rule in right.rules {
    if !rules.any(fn(item) { item.id == rule.id }) {
      rules.push(rule)
    }
  }
  rule_pack(
    name=left.name + "+" + right.name,
    version=left.version,
    locale=Mixed,
    description=left.description + "; " + right.description,
    rules~,
  )
}