///|
pub(all) enum IdentifierStatus {
  Allowed
  Restricted
} derive(Eq, Debug)

///|
pub(all) struct IdentifierStatusIssue {
  codepoint_index : Int32
  codepoint : Int32
  codepoint_hex : String
} derive(Eq, Debug)

///|
pub(all) enum IdentifierStatusError {
  Empty
  RestrictedCharacter(IdentifierStatusIssue)
} derive(Eq, Debug)

///|
/// Exact Unicode property; this does not validate identifier grammar.
pub fn get_identifier_status(cp : Int32) -> IdentifierStatus {
  if @unicode.identifier_status_allowed(cp) {
    Allowed
  } else {
    Restricted
  }
}

///|
/// Opt-in literal-codepoint filter; no normalization or canonical closure.
/// Existing profile defaults are unaffected. Index counts Unicode scalars.
pub fn validate_identifier_status(
  input : String,
) -> Result[Unit, IdentifierStatusError] {
  if input.is_empty() {
    return Err(Empty)
  }
  let mut index = 0
  for ch in input {
    let cp = ch.to_int()
    if !@unicode.identifier_status_allowed(cp) {
      return Err(
        RestrictedCharacter({
          codepoint_index: index,
          codepoint: cp,
          codepoint_hex: @unicode.format_codepoint(cp),
        }),
      )
    }
    index += 1
  }
  Ok(())
}