// 预切分:为**一条固定模式**手写的扫描器。
//
// 纪律(见计划 §5.3):本项目**不做通用正则引擎**。生态里若已有正则,
// 也不使用——那样会抹掉"从零实现"的物证,而且我们只需要这一条模式。
//
// 模式(cl100k_base;取自参考实现 tiktoken 0.14.0 的 pat_str 原文):
//
//   '(?i:[sdmt]|ll|ve|re)
// | [^\r\n\p{L}\p{N}]?+\p{L}++
// | \p{N}{1,3}+
// |  ?[^\s\p{L}\p{N}]++[\r\n]*+
// | \s++$
// | \s*[\r\n]
// | \s+(?!\S)
// | \s
//
// 与原文的等价性:
// * 占用(possessive)量词 `?+` `++` `*+` 在本模式下**不影响语义**,只减少回溯:
//   这些位置一旦失配,正则引擎也不会通过放弃占用找到别的匹配。
// * 因此实现按普通贪婪处理,交替实现为**有序分支**(取第一个能匹配的分支)。
// * `\s++$` **不可省略**:它负责「到串尾的整段空白」的吸收,
//   漏掉会让尾部空白被切碎(与参考实现不一致)。
//
// 语义要点:
// * 交替是**有序**的:从上到下取第一个能匹配的分支(不是全局最长匹配)。
// * 每个分支内部贪婪。
//
// **非法 UTF-8 的分野**(这是参考实现自己的一条暗规则,见
// `bpe/pretoken_align_wbtest.mbt` 与 `alignment_wbtest.mbt`):
// 参考实现的 `encode(bytes)` 会先尝试把输入**严格**解码成文本;
//   * 解码成功 -> 正则跑在**文本**上,`\p{L}` / `\p{N}` / `\s` 是真 Unicode 属性类
//     (由 `@unicode` 的生成表提供);
//   * 解码失败(截断序列、孤立首字节、超范围编码、代理区等)-> **整个输入作为一片**,
//     不再切分,直接交给 BPE。
//
// 第二条不是省事:`b" \xec\x8b\xa4\xed"`(`U+C2E4` 后跟一个悬挂的 `0xED`)在参考实现里
// 编成**单个** token `62085`(`b" \xec\x8b\xa4\xed"` 自身),只有整片 BPE 才可能到达;
// 而 `b" \xc2\x850"`(合法 UTF-8,`U+0085` 是 White_Space)会走到 `\s+(?!\S)`
// 的回退,把空格单独切出来。两条行为并存,正是「先试解码、失败就整片」的指纹。
//
// **第二轮优化**(0.7.0 之后,输出逐字节不变,由 corpus 2,935 例与 pretoken 金样守着):
// 原来先 `decode_utf8_lenient` 生成 `Array[CodePoint]`,再让各分支反复调
// `@unicode.is_letter` / `is_number` / `is_whitespace`——每个码点被分类 3~5 次,
// 每次都是一次二分查表外加一次 tuple 拆箱。现在改成**单遍解码 + 只分类一次**:
// 每个码点压进一个 `Int`(`cp << 4 | 类别位`),另外只存字节起点;此后所有分支
// 都退化成位测试。分类结果与 `\p{L}` / `\p{N}` / `\s` / `[\r\n]` 的语义逐位对应。

///|
/// 类别位:`\p{L}`、`\p{N}`、`\s`、`[\r\n]`。
const K_L : Int = 1

///|
const K_N : Int = 2

///|
const K_S : Int = 4

///|
const K_NL : Int = 8

///|
/// 单元 = `cp << 4 | 类别位`。
fn is_l(u : Int) -> Bool {
  (u & K_L) != 0
}

///|
fn is_n(u : Int) -> Bool {
  (u & K_N) != 0
}

///|
fn is_s(u : Int) -> Bool {
  (u & K_S) != 0
}

///|
fn is_crlf(u : Int) -> Bool {
  (u & K_NL) != 0
}

///|
/// 既非空白、也非字母、也非数字 —— 对应 `[^\s\p{L}\p{N}]`。
fn is_other(u : Int) -> Bool {
  (u & (K_L | K_N | K_S)) == 0
}

///|
fn cp_of(u : Int) -> Int {
  u >> 4
}

///|
/// 单遍宽松解码 + 分类。遇到**任何一个**非法字节立即返回 `None`:
/// 参考实现的分野是「整串严格解码失败 -> 整串一片」,所以不必解完再看。
fn classify(bytes : Bytes) -> (Array[Int], Array[Int])? {
  let n = bytes.length()
  let units : Array[Int] = []
  let starts : Array[Int] = []
  let mut i = 0
  while i < n {
    let b0 = bytes[i].to_int() & 0xFF
    let mut cp = 0
    let mut size = 0
    if b0 < 0x80 {
      cp = b0
      size = 1
    } else if b0 >= 0xC2 && b0 <= 0xDF && i + 1 < n {
      let b1 = bytes[i + 1].to_int() & 0xFF
      if is_cont(b1) {
        cp = ((b0 & 0x1F) << 6) | (b1 & 0x3F)
        size = 2
      }
    } else if b0 >= 0xE0 && b0 <= 0xEF && i + 2 < n {
      let b1 = bytes[i + 1].to_int() & 0xFF
      let b2 = bytes[i + 2].to_int() & 0xFF
      let b1_ok = if b0 == 0xE0 {
        b1 >= 0xA0 && b1 <= 0xBF
      } else if b0 == 0xED {
        b1 >= 0x80 && b1 <= 0x9F
      } else {
        is_cont(b1)
      }
      if b1_ok && is_cont(b2) {
        cp = ((b0 & 0x0F) << 12) | ((b1 & 0x3F) << 6) | (b2 & 0x3F)
        size = 3
      }
    } else if b0 >= 0xF0 && b0 <= 0xF4 && i + 3 < n {
      let b1 = bytes[i + 1].to_int() & 0xFF
      let b2 = bytes[i + 2].to_int() & 0xFF
      let b3 = bytes[i + 3].to_int() & 0xFF
      let b1_ok = if b0 == 0xF0 {
        b1 >= 0x90 && b1 <= 0xBF
      } else if b0 == 0xF4 {
        b1 >= 0x80 && b1 <= 0x8F
      } else {
        is_cont(b1)
      }
      if b1_ok && is_cont(b2) && is_cont(b3) {
        cp = ((b0 & 0x07) << 18) |
          ((b1 & 0x3F) << 12) |
          ((b2 & 0x3F) << 6) |
          (b3 & 0x3F)
        size = 4
      }
    }
    if size == 0 {
      return None
    }
    let u = cp.reinterpret_as_uint()
    // 三个类互斥(L / N / White_Space 在 Unicode 里没有交集),所以命中即停:
    // 中文与英文各只查 1 次表,而不是原来的 3 次。
    // ASCII 直接判:`\p{L}` = A-Za-z,`\p{N}` = 0-9,`\s` = 09..0D 与 20,
    // 与生成表在 < 0x80 上逐点一致(由 `ascii_class_wbtest` 守着)。
    let mut kind = 0
    if u < 0x80 {
      if (u >= 0x41 && u <= 0x5A) || (u >= 0x61 && u <= 0x7A) {
        kind = K_L
      } else if u >= 0x30 && u <= 0x39 {
        kind = K_N
      } else if u == 0x20 || (u >= 0x09 && u <= 0x0D) {
        kind = K_S
      }
    } else if @unicode.is_letter(u) {
      kind = K_L
    } else if @unicode.is_number(u) {
      kind = K_N
    } else if @unicode.is_whitespace(u) {
      kind = K_S
    }
    if cp == 0x0D || cp == 0x0A {
      kind = kind | K_NL
    }
    units.push((cp << 4) | kind)
    starts.push(i)
    i = i + size
  }
  Some((units, starts))
}

///|
/// 预切分结果:把原始字节切成若干"片",每片提供 `[start, end)` 字节区间。
///
/// 返回的是**区间**而非副本,避免在热路径上反复分配;
/// 调用方需要时用 `slice_bytes` 取副本。
pub fn pretokenize(bytes : Bytes) -> Array[(Int, Int)] {
  let starts : Array[Int] = []
  let ends : Array[Int] = []
  pretokenize_spans(bytes, starts, ends)
  let out : Array[(Int, Int)] = []
  for i = 0; i < starts.length(); i = i + 1 {
    out.push((starts[i], ends[i]))
  }
  out
}

///|
/// 预切分的内部形式:起点与终点分别入两个数组,省掉每片一个 tuple。
/// 热路径(`encode_ordinary` / `count`)走这条。
fn pretokenize_spans(bytes : Bytes, starts_out : Array[Int], ends_out : Array[Int]) -> Unit {
  match classify(bytes) {
    // 严格解码失败 -> 整串作为一片(参考实现的指纹)。
    None => {
      starts_out.push(0)
      ends_out.push(bytes.length())
    }
    Some((units, starts)) => {
      let n = units.length()
      let mut i = 0
      while i < n {
        let matched = match_at(units, i)
        // 兜底:非空白的单码点至少被吃掉 1 个码点,保证循环必然前进。
        let step = if matched > 0 { matched } else { 1 }
        let start = starts[i]
        let end = if i + step < n { starts[i + step] } else { bytes.length() }
        starts_out.push(start)
        ends_out.push(end)
        i = i + step
      }
    }
  }
}

///|
/// 在码点流的位置 `i` 依次尝试 8 个分支,返回匹配的码点数(0 表示全部失败)。
fn match_at(units : Array[Int], i : Int) -> Int {
  let m1 = match_contraction(units, i)
  if m1 > 0 {
    return m1
  }
  let m2 = match_optional_lead_letters(units, i)
  if m2 > 0 {
    return m2
  }
  let m3 = match_digits(units, i)
  if m3 > 0 {
    return m3
  }
  let m4 = match_punctuation(units, i)
  if m4 > 0 {
    return m4
  }
  let m5 = match_ws_to_end(units, i)
  if m5 > 0 {
    return m5
  }
  let m6 = match_ws_then_newline(units, i)
  if m6 > 0 {
    return m6
  }
  let m7 = match_ws_not_followed(units, i)
  if m7 > 0 {
    return m7
  }
  match_ws(units, i)
}

///|
fn ascii_lower(cp : Int) -> Int {
  if cp >= 0x41 && cp <= 0x5A {
    cp + 0x20
  } else {
    cp
  }
}

///|
/// 分支 1:`(?i:'s|'t|'re|'ve|'m|'ll|'d)`
fn match_contraction(units : Array[Int], i : Int) -> Int {
  let n = units.length()
  if i >= n || cp_of(units[i]) != 0x27 {
    return 0
  }
  // 只考虑 ASCII 缩写(与参考实现一致:这些后缀都是 ASCII)。
  let c1 = if i + 1 < n { ascii_lower(cp_of(units[i + 1])) } else { -1 }
  let c2 = if i + 2 < n { ascii_lower(cp_of(units[i + 2])) } else { -1 }
  let two = c1 == 0x73 || c1 == 0x74 || c1 == 0x6D || c1 == 0x64
  if two {
    return 2
  }
  if c1 == 0x72 || c1 == 0x76 || c1 == 0x6C {
    if c2 != -1 {
      let is_re = c1 == 0x72 && c2 == 0x65
      let is_ve = c1 == 0x76 && c2 == 0x65
      let is_ll = c1 == 0x6C && c2 == 0x6C
      if is_re || is_ve || is_ll {
        return 3
      }
    }
  }
  0
}

///|
/// 分支 2:`[^\r\n\p{L}\p{N}]?\p{L}+`
fn match_optional_lead_letters(units : Array[Int], i : Int) -> Int {
  let n = units.length()
  if i >= n {
    return 0
  }
  let mut j = i
  if !is_l(units[i]) {
    // 可选前导:非 \r、非 \n、非字母、非数字
    let ok_lead = !is_crlf(units[i]) && !is_l(units[i]) && !is_n(units[i])
    if !ok_lead || i + 1 >= n || !is_l(units[i + 1]) {
      return 0
    }
    j = i + 1
  }
  // 贪婪吃字母
  let mut k = j
  while k < n && is_l(units[k]) {
    k = k + 1
  }
  k - i
}

///|
/// 分支 3:`\p{N}{1,3}`
fn match_digits(units : Array[Int], i : Int) -> Int {
  let n = units.length()
  if i >= n || !is_n(units[i]) {
    return 0
  }
  let mut k = 0
  while k < 3 && i + k < n && is_n(units[i + k]) {
    k = k + 1
  }
  k
}

///|
/// 分支 4:` ?[^\s\p{L}\p{N}]+[\r\n]*`
fn match_punctuation(units : Array[Int], i : Int) -> Int {
  let n = units.length()
  if i >= n {
    return 0
  }
  let mut j = i
  if cp_of(units[i]) == 0x20 && i + 1 < n && is_other(units[i + 1]) {
    j = i + 1
  } else if !is_other(units[i]) {
    return 0
  }
  let mut k = j
  while k < n && is_other(units[k]) {
    k = k + 1
  }
  // 尾部换行,贪婪
  let mut e = k
  while e < n && is_crlf(units[e]) {
    e = e + 1
  }
  e - i
}

///|
/// 分支 5:`\s++$`
///
/// 语义:从 i 起到串尾**全是空白**时整段吸收(占有量词不回退,`$` 落在串尾);
/// 只要后面还有非空白,本分支整体失败。
fn match_ws_to_end(units : Array[Int], i : Int) -> Int {
  let n = units.length()
  let mut w = 0
  while i + w < n && is_s(units[i + w]) {
    w = w + 1
  }
  if w > 0 && i + w == n {
    w
  } else {
    0
  }
}

///|
/// 分支 6:`\s*[\r\n]`
///
/// 语义:匹配"从 i 开始的最长全空白前缀,且该前缀以 \r 或 \n 结尾"。
fn match_ws_then_newline(units : Array[Int], i : Int) -> Int {
  let n = units.length()
  let mut w = i
  while w < n && is_s(units[w]) {
    w = w + 1
  }
  if w == i {
    return 0
  }
  // 回退到运行段里最后一个 \r 或 \n
  let mut p = w - 1
  while p >= i {
    if is_crlf(units[p]) {
      return p - i + 1
    }
    p = p - 1
  }
  0
}

///|
/// 分支 6:`\s+(?!\S)`
///
/// 语义:贪婪吃空白;若后面还有非空白则回退一个(把最后一个空白留给后面的分支)。
fn match_ws_not_followed(units : Array[Int], i : Int) -> Int {
  let n = units.length()
  let mut w = 0
  while i + w < n && is_s(units[i + w]) {
    w = w + 1
  }
  if w == 0 {
    return 0
  }
  if i + w == n {
    w // 已到串尾,后面的 (?!\S) 恒成立
  } else if w >= 2 {
    w - 1 // 后面是非空白 -> 回退一个
  } else {
    0
  }
}

///|
/// 分支 7:`\s+`
fn match_ws(units : Array[Int], i : Int) -> Int {
  let n = units.length()
  let mut w = 0
  while i + w < n && is_s(units[i + w]) {
    w = w + 1
  }
  w
}