// Copyright (c) 2026 Christian Hansen 
//
// Copyright (c) 2026 fundon 
// 
// Unicode trie data and DFA transition table (UTS #51 v17.0.0).
// DO NOT EDIT — Generated by ucdgen.
//
// Licensed under the MIT License.

///|
fn trie_lookup(
  cp : Int,
  idx0 : ReadOnlyArray[Int],
  idx1 : ReadOnlyArray[Int],
  data : ReadOnlyArray[Int],
  block_shift : Int,
  page_shift : Int,
  block_mask : Int,
  page_mask : Int,
) -> Bool {
  let page = (cp >> (block_shift + page_shift)) & 255
  let off1 = (cp >> block_shift) & page_mask
  let off2 = cp & block_mask

  let i0 = idx0[page]
  let i1 = idx1[(i0 << page_shift) + off1]

  let data_idx = (i1 << block_shift) + off2
  ((data[data_idx >> 3] >> (data_idx & 7)) & 1) == 1
}

// ── Modifier Base trie (520 bytes) ─────────────────────────────────────

///|
let modifier_base_idx0 : ReadOnlyArray[Int] = [
  0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0,
]

///|
let modifier_base_idx1 : ReadOnlyArray[Int] = [
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 3, 4, 5, 6, 0, 0, 7, 8, 0, 0, 0, 0, 0,
]

///|
let modifier_base_data : ReadOnlyArray[Int] = [
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 156, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 204, 255, 1, 0, 192, 255, 255, 17, 238, 128, 2, 0, 0, 4, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 4, 0, 0, 97,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 248, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 112, 0, 1, 16, 0, 0, 0, 0, 0, 0, 0, 144, 0, 255,
  64, 0, 255, 115, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 96, 11, 0, 224, 254,
  63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 56, 0, 0, 0, 0, 0, 255, 1,
]

///|
pub fn is_modifier_base(cp : Int) -> Bool {
  trie_lookup(
    cp, modifier_base_idx0, modifier_base_idx1, modifier_base_data, 8, 5, 255, 31,
  )
}

// ── Emoji trie (952 bytes) ──────────────────────────────────────────────

///|
let emoji_idx0 : ReadOnlyArray[Int] = [
  0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2,
]

///|
let emoji_idx1 : ReadOnlyArray[Int] = [
  0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 4, 5, 2, 2, 6, 7, 2, 8, 2, 9, 10, 11, 12, 13,
  2, 2, 14, 2, 2, 2, 15, 2, 2, 2, 2, 2, 2, 2, 2, 2, 16, 2, 2, 2, 2, 17, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 18, 19, 20, 21, 22, 2, 23, 24, 25, 26, 27, 28,
  29, 30, 2, 31, 2, 2, 32, 25, 33, 34, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
]

///|
let emoji_data : ReadOnlyArray[Int] = [
  0, 0, 0, 0, 8, 4, 255, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 16, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 240, 3, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 1, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 254, 15, 7, 0, 0,
  0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 64, 0, 1, 0, 0, 0,
  0, 0, 0, 120, 31, 64, 50, 33, 77, 196, 0, 7, 5, 255, 15, 128, 105, 1, 0, 200, 0,
  0, 252, 26, 131, 12, 3, 96, 48, 193, 26, 0, 0, 6, 191, 39, 36, 191, 84, 32, 2,
  1, 24, 0, 144, 80, 184, 0, 24, 0, 0, 0, 0, 0, 224, 0, 2, 0, 1, 128, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 24, 0,
  0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 32, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 128, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 192, 0, 64, 254, 7, 0, 0, 0, 0, 0, 0, 0, 0,
  192, 255, 255, 255, 6, 0, 0, 4, 0, 128, 252, 7, 0, 0, 3, 0, 0, 0, 0, 0, 255, 255,
  255, 255, 243, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  207, 206, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 185, 255, 255, 255,
  255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 191, 255, 255,
  255, 255, 255, 255, 255, 63, 0, 126, 255, 255, 255, 128, 249, 7, 128, 60, 97, 0,
  48, 1, 6, 16, 28, 0, 14, 112, 10, 129, 8, 252, 255, 255, 255, 255, 255, 255, 255,
  255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 63, 248,
  231, 241, 63, 26, 249, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 15, 1, 0, 0,
  240, 255, 255, 255, 255, 255, 247, 191, 255, 255, 255, 255, 255, 255, 255, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 31, 255, 199, 255, 255, 255, 255, 255,
  255, 127, 225, 255, 159, 255, 135, 255, 1,
]

///|
pub fn is_emoji(cp : Int) -> Bool {
  trie_lookup(cp, emoji_idx0, emoji_idx1, emoji_data, 7, 6, 127, 63)
}

// ── Emoji_Presentation trie (744 bytes) ─────────────────────────────────

///|
let presentation_idx0 : ReadOnlyArray[Int] = [
  0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0,
]

///|
let presentation_idx1 : ReadOnlyArray[Int] = [
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 3, 4, 5, 6, 7, 0,
  0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 10,
  0, 11, 12, 0, 13, 14, 15, 16, 17, 18, 19, 20, 0, 21, 0, 0, 22, 23, 24, 25, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0,
]

///|
let presentation_data : ReadOnlyArray[Int] = [
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 9, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 48, 0, 0, 0, 0, 0, 0, 255, 15, 0, 0,
  0, 0, 128, 0, 0, 8, 0, 2, 12, 0, 96, 48, 64, 16, 0, 0, 4, 44, 36, 32, 12, 0, 0,
  0, 1, 0, 0, 0, 80, 184, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 1, 128, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0,
  0, 64, 254, 7, 0, 0, 0, 0, 0, 0, 0, 0, 192, 255, 255, 255, 2, 0, 0, 4, 0, 128,
  124, 7, 0, 0, 3, 0, 0, 0, 0, 0, 255, 255, 255, 255, 1, 224, 191, 255, 255, 255,
  255, 255, 255, 255, 255, 223, 255, 255, 15, 0, 255, 255, 255, 255, 255, 135, 15,
  0, 255, 255, 17, 255, 255, 255, 255, 255, 255, 255, 255, 127, 253, 255, 255, 255,
  255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  255, 255, 255, 159, 255, 255, 255, 255, 255, 255, 255, 63, 0, 120, 255, 255, 255,
  0, 0, 4, 0, 0, 96, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 255, 255, 255, 255,
  255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255,
  255, 63, 16, 231, 241, 0, 24, 240, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255,
  15, 1, 0, 0, 240, 255, 255, 255, 255, 255, 247, 191, 255, 255, 255, 255, 255, 255,
  255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 31, 255, 199, 255, 255, 255,
  255, 255, 255, 127, 225, 255, 159, 255, 135, 255, 1,
]

///|
pub fn is_presentation(cp : Int) -> Bool {
  trie_lookup(
    cp, presentation_idx0, presentation_idx1, presentation_data, 7, 6, 127, 63,
  )
}

// ── DFA states and classes ──────────────────────────────────────────────

///|
// ── DFA states and classes ──────────────────────────────────────────────

pub(all) enum State {
  REJECT
  START
  TERMINAL
  EMOJI
  MODIFIER_BASE
  OPTIONAL_ZWJ
  KEYCAP_VS
  TAG_BASE
  RI
  TAG_SPEC
  TAG_EMPTY
  KEYCAP_BASE
  ZWJ
} derive(Eq, Debug, Compare)

///|
pub(all) enum Class {
  OTHER
  EMOJI
  MODIFIER_BASE
  MODIFIER
  VS15
  VS16
  RI
  KEYCAP_BASE
  KEYCAP_TERM
  TAG_BASE
  TAG_SPEC
  TAG_TERM
  ZWJ
} derive(Eq, Debug)

///|
/// Convert State enum to its integer discriminant.
pub fn state_to_int(s : State) -> Int {
  match s {
    State::REJECT => 0
    State::START => 1
    State::TERMINAL => 2
    State::EMOJI => 3
    State::MODIFIER_BASE => 4
    State::OPTIONAL_ZWJ => 5
    State::KEYCAP_VS => 6
    State::TAG_BASE => 7
    State::RI => 8
    State::TAG_SPEC => 9
    State::TAG_EMPTY => 10
    State::KEYCAP_BASE => 11
    State::ZWJ => 12
  }
}

///|
/// Convert Class enum to its integer discriminant.
pub fn class_to_int(c : Class) -> Int {
  match c {
    Class::OTHER => 0
    Class::EMOJI => 1
    Class::MODIFIER_BASE => 2
    Class::MODIFIER => 3
    Class::VS15 => 4
    Class::VS16 => 5
    Class::RI => 6
    Class::KEYCAP_BASE => 7
    Class::KEYCAP_TERM => 8
    Class::TAG_BASE => 9
    Class::TAG_SPEC => 10
    Class::TAG_TERM => 11
    Class::ZWJ => 12
  }
}

///|
/// Convert an integer back to State enum.
pub fn int_to_state(n : Int) -> State {
  match n {
    0 => State::REJECT
    1 => State::START
    2 => State::TERMINAL
    3 => State::EMOJI
    4 => State::MODIFIER_BASE
    5 => State::OPTIONAL_ZWJ
    6 => State::KEYCAP_VS
    7 => State::TAG_BASE
    8 => State::RI
    9 => State::TAG_SPEC
    10 => State::TAG_EMPTY
    11 => State::KEYCAP_BASE
    12 => State::ZWJ
    _ => State::REJECT
  }
}

///|
pub const RECORD_STATE_SHIFT : Int = 16

// ── DFA transition table ────────────────────────────────────────────────

///|
let dfa_table : ReadOnlyArray[ReadOnlyArray[Int]] = [
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [1, 3, 4, 2, 1, 1, 8, 11, 0, 7, 1, 1, 1],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 2, 5, 0, 0, 0, 0, 0, 0, 12],
  [0, 0, 0, 5, 2, 5, 0, 0, 0, 0, 0, 0, 12],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12],
  [0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0],
  [0, 0, 0, 0, 2, 5, 0, 0, 0, 0, 9, 10, 12],
  [0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 2, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 6, 6, 0, 0, 2, 0, 0, 0, 0],
  [0, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
]