///|
pub enum IdentifierKind {
  PhoneIdentifier
  EmailIdentifier
  GovernmentIdentifier
  AccountIdentifier
  DeviceIdentifier
  LocationIdentifier
  UnknownIdentifier
} derive(Debug, Eq)

///|
pub fn identifier_kind_values() -> Array[IdentifierKind] {
  [
    PhoneIdentifier,
    EmailIdentifier,
    GovernmentIdentifier,
    AccountIdentifier,
    DeviceIdentifier,
    LocationIdentifier,
    UnknownIdentifier,
  ]
}

///|
pub fn identifier_kind_name(kind : IdentifierKind) -> String {
  match kind {
    PhoneIdentifier => "phone"
    EmailIdentifier => "email"
    GovernmentIdentifier => "government"
    AccountIdentifier => "account"
    DeviceIdentifier => "device"
    LocationIdentifier => "location"
    UnknownIdentifier => "unknown"
  }
}

///|
pub fn normalize_phone(value : String) -> String {
  let digits = keep_digits(value)
  if digits.has_prefix("0086") {
    "+86" + digits[4:].to_owned()
  } else if digits.has_prefix("86") && digits.length() == 13 {
    "+86" + digits[2:].to_owned()
  } else {
    digits
  }
}

///|
pub fn phone_region_hint(value : String) -> String {
  let normalized = normalize_phone(value)
  if normalized.has_prefix("+86") {
    "CN"
  } else if normalized.has_prefix("+1") {
    "US/CA"
  } else if normalized.has_prefix("+44") {
    "GB"
  } else {
    "UNKNOWN"
  }
}

///|
pub fn normalize_email(value : String) -> String {
  trim_ascii_space(value).to_lower()
}

///|
pub fn email_domain(value : String) -> String {
  match normalize_email(value).split_once("@") {
    Some((_, domain)) => domain.to_owned()
    None => ""
  }
}

///|
pub fn email_local_part(value : String) -> String {
  match normalize_email(value).split_once("@") {
    Some((local_part, _)) => local_part.to_owned()
    None => ""
  }
}

///|
pub fn normalize_identifier(value : String, kind : IdentifierKind) -> String {
  match kind {
    PhoneIdentifier => normalize_phone(value)
    EmailIdentifier => normalize_email(value)
    GovernmentIdentifier | AccountIdentifier | DeviceIdentifier =>
      normalized_identifier(value)
    LocationIdentifier | UnknownIdentifier => trim_ascii_space(value)
  }
}

///|
pub fn identifier_is_reversible(value : String, kind : IdentifierKind) -> Bool {
  match kind {
    PhoneIdentifier
    | EmailIdentifier
    | GovernmentIdentifier
    | AccountIdentifier
    | DeviceIdentifier => !normalize_identifier(value, kind).is_empty()
    LocationIdentifier | UnknownIdentifier => false
  }
}

///|
pub fn mask_identifier(value : String, kind : IdentifierKind) -> String {
  let normalized = normalize_identifier(value, kind)
  match kind {
    PhoneIdentifier => {
      let digits = keep_digits(normalized)
      if digits.length() <= 4 {
        "****"
      } else {
        digits[:3].to_owned() + "****" + digits[digits.length() - 4:].to_owned()
      }
    }
    EmailIdentifier => {
      let local_part = email_local_part(normalized)
      let domain = email_domain(normalized)
      if local_part.length() <= 1 {
        "*" + "@" + domain
      } else {
        local_part[:1].to_owned() + "***@" + domain
      }
    }
    _ => edge_mask(normalized, 1, 1, false)
  }
}

///|
pub fn probable_chinese_name(value : String) -> Bool {
  let text = trim_ascii_space(value)
  let chars = text.to_array()
  chars.length() >= 2 && chars.length() <= 6 && chars.all(is_cjk_char)
}

///|
pub fn probable_english_name(value : String) -> Bool {
  let words = trim_ascii_space(value).split(" ").to_array()
  words.length() >= 1 &&
  words.length() <= 4 &&
  words.all(fn(word) {
    let item = word.to_owned()
    !item.is_empty() &&
    item
    .to_array()
    .all(fn(c) { c.is_ascii_alphabetic() || c == '-' || c == '\'' })
  })
}

///|
pub fn name_initials(value : String) -> String {
  let words = trim_ascii_space(value).split(" ").to_array()
  let builder = StringBuilder()
  for word in words {
    match word.to_owned().to_array().get(0) {
      Some(c) => builder.write_string(c.to_string().to_upper())
      None => ()
    }
  }
  builder.to_string()
}

///|
pub fn normalize_address(value : String) -> String {
  collapse_spaces(normalize_punctuation(value)).to_lower()
}

///|
pub fn address_components(value : String) -> Array[String] {
  let normalized = normalize_address(value)
  normalized.split(",").map(fn(item) { item.to_owned() }).to_array()
}

///|
pub fn probable_address(value : String) -> Bool {
  let text = normalize_address(value)
  text.length() >= 6 &&
  (
    text.contains("路") ||
    text.contains("街") ||
    text.contains("号") ||
    text.contains("street") ||
    text.contains("road") ||
    text.contains("avenue")
  )
}

///|
pub fn date_digits(value : String) -> String {
  keep_digits(value)
}

///|
pub fn normalize_date(value : String) -> String? {
  let digits = date_digits(value)
  if digits.length() == 8 {
    match parse_compact_date(digits) {
      Some((year, month, day)) =>
        Some(
          pad_left(year.to_string(), 4, '0') +
          "-" +
          pad_left(month.to_string(), 2, '0') +
          "-" +
          pad_left(day.to_string(), 2, '0'),
        )
      None => None
    }
  } else {
    None
  }
}

///|
pub fn date_is_future(value : String, current_year : Int) -> Bool {
  match normalize_date(value) {
    Some(normalized) => decimal_value(normalized[0:4].to_owned()) > current_year
    None => false
  }
}

///|
pub fn identifier_class(value : String) -> String {
  if valid_chinese_id(value) {
    "chinese-id"
  } else if valid_bank_card(value) {
    "bank-card"
  } else if valid_email_address(value) {
    "email"
  } else if valid_phone_number(value) {
    "phone"
  } else if valid_uuid(value) {
    "uuid"
  } else if valid_ipv4(value) {
    "ipv4"
  } else {
    "unknown"
  }
}

///|
pub fn identifier_quality(value : String) -> Int {
  let normalized = normalized_identifier(value)
  let mut score = 0
  if normalized.length() >= 4 {
    score += 20
  }
  if normalized.length() >= 8 {
    score += 20
  }
  if valid_chinese_id(value) {
    score += 40
  }
  if valid_bank_card(value) {
    score += 30
  }
  if valid_email_address(value) {
    score += 25
  }
  if valid_phone_number(value) {
    score += 25
  }
  if score > 100 {
    100
  } else {
    score
  }
}

///|
pub fn pseudonym_for(
  value : String,
  kind : IdentifierKind,
  salt : String,
) -> String {
  let digest = stable_hash(salt + ":" + normalize_identifier(value, kind))
  match kind {
    PhoneIdentifier => "PHONE-" + digest[:6].to_owned()
    EmailIdentifier => "user-" + digest[:8].to_owned() + "@example.invalid"
    GovernmentIdentifier => "ID-" + digest
    AccountIdentifier => "ACCOUNT-" + digest[:10].to_owned()
    DeviceIdentifier => "DEVICE-" + digest[:10].to_owned()
    LocationIdentifier => "LOCATION-" + digest[:8].to_owned()
    UnknownIdentifier => "TOKEN-" + digest[:8].to_owned()
  }
}

///|
pub fn pseudonym_map(
  values : Array[String],
  kind : IdentifierKind,
  salt : String,
) -> Map[String, String] {
  let result : Map[String, String] = Map([])
  for value in values {
    result[value] = pseudonym_for(value, kind, salt)
  }
  result
}