///|
/// Apply IDNA mapping to a domain name
fn map_domain(
  domain : String,
  use_std3_ascii_rules~ : Bool,
) -> String raise IdnaError {
  let result = StringBuilder::new()
  for c in domain {
    match @idna_data.lookup_idna_mapping(c) {
      @idna_data.Valid | @idna_data.Deviation(_) =>
        // Keep the character (nontransitional mode keeps deviation as-is)
        result.write_char(c)
      @idna_data.Ignored =>
        // Remove the character (don't add to result)
        ()
      @idna_data.Mapped(mapping) =>
        // Replace with mapping
        for mapped_c in mapping {
          result.write_char(mapped_c)
        }
      @idna_data.Disallowed => raise DisallowedCodePoint(cp=c.to_int())
      @idna_data.DisallowedSTD3Valid =>
        if use_std3_ascii_rules {
          raise DisallowedCodePoint(cp=c.to_int())
        } else {
          result.write_char(c)
        }
      @idna_data.DisallowedSTD3Mapped(mapping) =>
        if use_std3_ascii_rules {
          raise DisallowedCodePoint(cp=c.to_int())
        } else {
          for mapped_c in mapping {
            result.write_char(mapped_c)
          }
        }
    }
  }
  result.to_string()
}

///|
/// Apply IDNA mapping to a single label (for ToUnicode)
fn map_label(
  label : String,
  use_std3_ascii_rules~ : Bool,
) -> String raise IdnaError {
  map_domain(label, use_std3_ascii_rules~)
}

///|
/// Split domain into labels at separators
fn split_labels(domain : String) -> Array[String] {
  let labels : Array[String] = []
  let current = StringBuilder::new()
  for c in domain {
    if is_label_separator(c) {
      labels.push(current.to_string())
      current.reset()
    } else {
      current.write_char(c)
    }
  }

  // Add final label
  labels.push(current.to_string())
  labels
}

///|
/// Check if a character is a label separator
fn is_label_separator(c : Char) -> Bool {
  c == '.' || c == '\u{3002}' || c == '\u{FF0E}' || c == '\u{FF61}'
}