// Copyright (c) 2026 Christian Hansen 
// 
// 
// Copyright (c) 2026 fundon 
//
// MoonBit translation — Unicode Emoji Segmentation (UTS #51 v17.0.0)
//
// Licensed under the MIT License.

// ── Sequence types ──────────────────────────────────────────────────────

///|
pub(all) enum EmojiSequenceType {
  BASIC
  KEYCAP
  FLAG
  TAG
  MODIFIER
  ZWJ
} derive(Eq, Debug)

///|
pub(all) enum EmojiPresentationStyle {
  DEFAULT
  TEXT
  EMOJI
} derive(Eq, Debug)

// Helper: check if style is DEFAULT

///|
fn style_is_default(style : EmojiPresentationStyle) -> Bool {
  match style {
    EmojiPresentationStyle::DEFAULT => true
    _ => false
  }
}

// ── UCD predicates ──────────────────────────────────────────────────────

///|
pub fn is_keycap_base(cp : Int) -> Bool {
  cp == 0x0023 || cp == 0x002A || (cp >= 0x0030 && cp <= 0x0039)
}

///|
pub fn is_enclosing_keycap(cp : Int) -> Bool {
  cp == 0x20E3
}

///|
pub fn is_tag_base(cp : Int) -> Bool {
  cp == 0x1F3F4
}

///|
pub fn is_tag_spec(cp : Int) -> Bool {
  cp >= 0xE0020 && cp <= 0xE007E
}

///|
pub fn is_tag_term(cp : Int) -> Bool {
  cp == 0xE007F
}

///|
pub fn is_text_presentation_selector(cp : Int) -> Bool {
  cp == 0xFE0E
}

///|
pub fn is_emoji_presentation_selector(cp : Int) -> Bool {
  cp == 0xFE0F
}

///|
pub fn is_regional_indicator(cp : Int) -> Bool {
  cp >= 0x1F1E6 && cp <= 0x1F1FF
}

///|
pub fn is_modifier(cp : Int) -> Bool {
  cp >= 0x1F3FB && cp <= 0x1F3FF
}

// ── Trie lookup helper ──────────────────────────────────────────────────

///|

///|
pub fn dfa_is_accepting(state : State) -> Bool {
  state >= State::TERMINAL && state <= State::RI
}

///|
pub fn dfa_is_boundary(state : State) -> Bool {
  state == State::REJECT
}

///|
pub fn dfa_is_start(state : State) -> Bool {
  state == State::START
}

///|
pub fn dfa_step(state : State, klass : Class) -> State {
  int_to_state(dfa_table[state_to_int(state)][class_to_int(klass)])
}

///|
/// Step the DFA and record class/state bits into the bitmask accumulator.
pub fn dfa_step_record(
  state : State,
  klass : Class,
  bitmask : Int,
) -> (State, Int) {
  let next = dfa_table[state_to_int(state)][class_to_int(klass)]
  let ns = int_to_state(next)
  if ns != State::START && ns != State::REJECT {
    (
      ns,
      bitmask | (1 << class_to_int(klass)) | (1 << (next + RECORD_STATE_SHIFT)),
    )
  } else {
    (ns, bitmask)
  }
}

// ── UCD classify ────────────────────────────────────────────────────────

///|
pub fn ucd_classify(cp : Int) -> Class {
  if cp <= 0x200D {
    if cp == 0x200D {
      return Class::ZWJ
    }
    if cp == 0xA9 || cp == 0xAE {
      return Class::EMOJI
    }
    if is_keycap_base(cp) {
      return Class::KEYCAP_BASE
    }
    return Class::OTHER
  }
  if is_text_presentation_selector(cp) {
    return Class::VS15
  }
  if is_emoji_presentation_selector(cp) {
    return Class::VS16
  }
  if is_enclosing_keycap(cp) {
    return Class::KEYCAP_TERM
  }
  if is_tag_base(cp) {
    return Class::TAG_BASE
  }
  if is_tag_spec(cp) {
    return Class::TAG_SPEC
  }
  if is_tag_term(cp) {
    return Class::TAG_TERM
  }
  if is_regional_indicator(cp) {
    return Class::RI
  }
  if is_modifier(cp) {
    return Class::MODIFIER
  }
  if is_modifier_base(cp) {
    return Class::MODIFIER_BASE
  }
  if is_emoji(cp) {
    return Class::EMOJI
  }
  Class::OTHER
}

// ── DFA classify from recorded bitmask ──────────────────────────────────

///|
pub fn classify_type(bitmask : Int) -> EmojiSequenceType {
  if (bitmask & (1 << class_to_int(Class::ZWJ))) != 0 {
    return EmojiSequenceType::ZWJ
  }
  if (bitmask & (1 << (state_to_int(State::TAG_SPEC) + RECORD_STATE_SHIFT))) !=
    0 &&
    (bitmask & (1 << (state_to_int(State::TERMINAL) + RECORD_STATE_SHIFT))) != 0 {
    return EmojiSequenceType::TAG
  }
  if (bitmask & (1 << (state_to_int(State::RI) + RECORD_STATE_SHIFT))) != 0 &&
    (bitmask & (1 << (state_to_int(State::TERMINAL) + RECORD_STATE_SHIFT))) != 0 {
    return EmojiSequenceType::FLAG
  }
  if (bitmask & (1 << class_to_int(Class::MODIFIER_BASE))) != 0 &&
    (bitmask & (1 << class_to_int(Class::MODIFIER))) != 0 {
    return EmojiSequenceType::MODIFIER
  }
  if (bitmask & (1 << class_to_int(Class::KEYCAP_TERM))) != 0 {
    return EmojiSequenceType::KEYCAP
  }
  EmojiSequenceType::BASIC
}

///|
pub fn classify_style(bitmask : Int) -> EmojiPresentationStyle {
  if (bitmask & (1 << class_to_int(Class::VS15))) != 0 {
    return EmojiPresentationStyle::TEXT
  }
  if (bitmask & (1 << class_to_int(Class::VS16))) != 0 {
    return EmojiPresentationStyle::EMOJI
  }
  EmojiPresentationStyle::DEFAULT
}

// ── Presentation resolution ─────────────────────────────────────────────

///|
pub fn resolve_default(
  type_ : EmojiSequenceType,
  leading_cp : Int,
) -> EmojiPresentationStyle {
  match type_ {
    EmojiSequenceType::BASIC =>
      if is_presentation(leading_cp) {
        EmojiPresentationStyle::EMOJI
      } else {
        EmojiPresentationStyle::TEXT
      }
    EmojiSequenceType::KEYCAP => EmojiPresentationStyle::TEXT
    EmojiSequenceType::FLAG
    | EmojiSequenceType::TAG
    | EmojiSequenceType::MODIFIER
    | EmojiSequenceType::ZWJ => EmojiPresentationStyle::EMOJI
  }
}

///|
pub fn resolve(
  type_ : EmojiSequenceType,
  style : EmojiPresentationStyle,
  leading_cp : Int,
) -> EmojiPresentationStyle {
  if !style_is_default(style) {
    return style
  }
  resolve_default(type_, leading_cp)
}

// ── Scanner ─────────────────────────────────────────────────────────────

///|
pub struct ScanRange {
  start : Int
  end : Int
}

///|
pub fn scan_strict(codepoints : Array[Int]) -> Array[ScanRange] {
  let len = codepoints.length()
  let out : Array[ScanRange] = Array::new(capacity=len)
  let mut state = State::START
  let mut start = 0

  for i = 0; i < len; i = i + 1 {
    let klass = ucd_classify(codepoints[i])
    let next = dfa_step(state, klass)

    if dfa_is_boundary(next) {
      if dfa_is_accepting(state) {
        out.push({ start, end: i - 1 })
      }
      let next2 = dfa_step(State::START, klass)
      state = next2
      start = i
    } else {
      state = next
    }

    if dfa_is_start(state) {
      start = i + 1
    }
  }

  if start < len && dfa_is_accepting(state) {
    out.push({ start, end: len - 1 })
  }
  out
}

// ── Inline tests ────────────────────────────────────────────────────────

///|
pub fn scan_greedy(codepoints : Array[Int]) -> Array[ScanRange] {
  let len = codepoints.length()
  let out : Array[ScanRange] = Array::new(capacity=len)
  let mut state = State::START
  let mut start = 0
  let mut end = 0
  let mut has_accept = false

  for i = 0; i < len; i = i + 1 {
    let klass = ucd_classify(codepoints[i])
    let next = dfa_step(state, klass)

    if dfa_is_boundary(next) {
      if has_accept {
        out.push({ start, end })
        has_accept = false
      }
      let next2 = dfa_step(State::START, klass)
      state = next2
      start = i
    } else {
      state = next
    }

    if dfa_is_start(state) {
      start = i + 1
    }

    if dfa_is_accepting(state) {
      has_accept = true
      end = i
    }
  }

  if has_accept {
    out.push({ start, end })
  }
  out
}