///|
/// Hangul syllable constants (from Unicode Chapter 3.12)
/// These enable algorithmic decomposition/composition without lookup tables

///|
/// First Hangul syllable (가)
let hangul_s_base : Int = 0xAC00

///|
/// First leading consonant jamo (ᄀ)
let hangul_l_base : Int = 0x1100

///|
/// First vowel jamo (ᅡ)
let hangul_v_base : Int = 0x1161

///|
/// First trailing consonant jamo (ᆨ) - note: T_BASE itself represents no trailing
let hangul_t_base : Int = 0x11A7

///|
/// Number of leading consonant jamo (19)
let hangul_l_count : Int = 19

///|
/// Number of vowel jamo (21)
let hangul_v_count : Int = 21

///|
/// Number of trailing consonant jamo including none (28)
let hangul_t_count : Int = 28

///|
/// V_COUNT * T_COUNT = 588
let hangul_n_count : Int = 588

///|
/// Total number of Hangul syllables (11172)
let hangul_s_count : Int = 11172

///|
/// Check if a character is a precomposed Hangul syllable
fn is_hangul_syllable(c : Char) -> Bool {
  let cp = c.to_int()
  cp >= hangul_s_base && cp < hangul_s_base + hangul_s_count
}

///|
/// Check if a character is a leading consonant jamo (L)
fn is_hangul_l(c : Char) -> Bool {
  let cp = c.to_int()
  cp >= hangul_l_base && cp < hangul_l_base + hangul_l_count
}

///|
/// Check if a character is a vowel jamo (V)
fn is_hangul_v(c : Char) -> Bool {
  let cp = c.to_int()
  cp >= hangul_v_base && cp < hangul_v_base + hangul_v_count
}

///|
/// Check if a character is a trailing consonant jamo (T)
/// Note: T_BASE (0x11A7) itself is not a valid trailing jamo
fn is_hangul_t(c : Char) -> Bool {
  let cp = c.to_int()
  cp > hangul_t_base && cp < hangul_t_base + hangul_t_count
}

///|
/// Check if a character is an LV syllable (no trailing consonant)
fn is_hangul_lv(c : Char) -> Bool {
  if !is_hangul_syllable(c) {
    return false
  }
  (c.to_int() - hangul_s_base) % hangul_t_count == 0
}

///|
/// Algorithmically decompose a Hangul syllable into jamo
/// Returns L + V for LV syllables, L + V + T for LVT syllables
fn decompose_hangul(syllable : Char) -> Array[Char] {
  let s_index = syllable.to_int() - hangul_s_base
  let l = hangul_l_base + s_index / hangul_n_count
  let v = hangul_v_base + s_index % hangul_n_count / hangul_t_count
  let t_index = s_index % hangul_t_count
  if t_index == 0 {
    [l.unsafe_to_char(), v.unsafe_to_char()] // LV syllable
  } else {
    [
      l.unsafe_to_char(),
      v.unsafe_to_char(),
      (hangul_t_base + t_index).unsafe_to_char(),
    ] // LVT syllable
  }
}

///|
/// Try to compose two characters into a Hangul syllable
/// Returns Some(syllable) if composition is possible, None otherwise
fn try_compose_hangul(first : Char, second : Char) -> Char? {
  // L + V -> LV syllable
  if is_hangul_l(first) && is_hangul_v(second) {
    let l_index = first.to_int() - hangul_l_base
    let v_index = second.to_int() - hangul_v_base
    return Some(
      (hangul_s_base + (l_index * hangul_v_count + v_index) * hangul_t_count).unsafe_to_char(),
    )
  }
  // LV + T -> LVT syllable
  if is_hangul_lv(first) && is_hangul_t(second) {
    return Some(
      (first.to_int() + (second.to_int() - hangul_t_base)).unsafe_to_char(),
    )
  }
  None
}