// BPE 训练:给定语料与参数,产出**逐字节可复现**的词表。
//
// 训练与编码共用同一套预切分(`@bpe.pretokenize`):训练时把每片当成一个"词",
// 于是学出来的合并规则恰好就是编码时会用到的那批——两边不会错位。
//
// ## 可复现性从哪来
//
// 训练里唯一的"选择"是:每一步合并哪个 pair。只要这个选择由**全序**决定,
// 训练就是纯函数。规则(tie-break,写进规范):
//
//   1. pair 频次降序;
//   2. 频次相同 -> `left` 字节序升序;
//   3. 再相同 -> `right` 字节序升序。
//
// 第 2、3 条合起来对**不同的 pair** 构成全序(pair 就是 `(left, right)`),
// 所以永远不存在并列,排序稳不稳定都无所谓。另外:
//
// * 频次是整数累加,与遍历顺序无关;
// * 词在统计前先按字节序排好,训练与 `Map` 的迭代顺序**完全无关**;
// * 候选挑选用自写的归并排序,不依赖任何库的排序实现。
//
// 这三条加在一起,才敢说"同输入两次训练逐字节一致"。

///|
/// 一次合并:把 `left` 与 `right` 拼成 `merged`,占据 `rank` 位。
pub(all) struct Merge {
  rank : Int
  merged : Bytes
  left : Bytes
  right : Bytes
}

///|
/// 一次训练的完整产物。
///
/// 除了词表本身,还把**输入与参数**一并带上:只有词表无法回答
/// "这是拿什么语料、什么参数训出来的",而"可复现"要求这个问题有答案。
pub(all) struct TrainResult {
  vocab : Vocab
  merges : Array[Merge]
  corpus_bytes : Int
  corpus_words : Int
  vocab_size : Int
  min_frequency : Int
}

///|
/// 字节序比较(memcmp 语义):`-1` / `0` / `1`。
fn compare_bytes(a : Bytes, b : Bytes) -> Int {
  let an = a.length()
  let bn = b.length()
  let n = if an < bn { an } else { bn }
  for i = 0; i < n; i = i + 1 {
    let x = a[i].to_int() & 0xFF
    let y = b[i].to_int() & 0xFF
    if x < y {
      return -1
    }
    if x > y {
      return 1
    }
  }
  if an < bn {
    return -1
  }
  if an > bn {
    return 1
  }
  0
}

///|
/// 字节序列相等:先比长度(绝大多数不等在这一步就出局),再逐字节比。
fn bytes_eq(a : Bytes, b : Bytes) -> Bool {
  if a.length() != b.length() {
    return false
  }
  compare_bytes(a, b) == 0
}

///|
/// pair 的无歧义键:`左长度(2 字节大端) + 左 + 右`。
///
/// 不能拿 `左 ++ 右` 当键:`("ab","c")` 与 `("a","bc")` 都拼成 `"abc"`,
/// 却是**两个不同的 pair**,频次必须分开累计。前缀长度把这件事说清楚。
fn pair_key(left : Bytes, right : Bytes) -> Bytes {
  let ln = left.length()
  let rn = right.length()
  let out : Array[Byte] = Array::make(2 + ln + rn, (0).to_byte())
  out[0] = (ln >> 8).to_byte()
  out[1] = (ln & 0xFF).to_byte()
  for i = 0; i < ln; i = i + 1 {
    out[2 + i] = left.unsafe_get(i)
  }
  for i = 0; i < rn; i = i + 1 {
    out[2 + ln + i] = right.unsafe_get(i)
  }
  Bytes::from_array(out)
}

///|
/// 对下标数组做自写的**稳定归并排序**。
///
/// 不调用库排序:训练的可复现性不该依赖"某个库的排序稳不稳定"这种实现细节。
/// `less(i, j)` 为真表示 `i` 排在 `j` 前面。
fn sort_indices(order : Array[Int], less : (Int, Int) -> Bool) -> Unit {
  let n = order.length()
  if n < 2 {
    return
  }
  let buf : Array[Int] = Array::make(n, 0)
  let mut width = 1
  while width < n {
    let mut i = 0
    while i < n {
      let mid = if i + width < n { i + width } else { n }
      let end = if i + 2 * width < n { i + 2 * width } else { n }
      let mut a = i
      let mut b = mid
      let mut k = i
      while a < mid || b < end {
        // 相等时先取左边 -> 稳定。
        if a < mid && (b >= end || !less(order[b], order[a])) {
          buf[k] = order[a]
          a = a + 1
        } else {
          buf[k] = order[b]
          b = b + 1
        }
        k = k + 1
      }
      i = i + 2 * width
    }
    for j = 0; j < n; j = j + 1 {
      order[j] = buf[j]
    }
    width = width * 2
  }
}

///|
/// 候选排序:频次降序 -> `left` 字节序升序 -> `right` 字节序升序。
fn candidate_less(
  lefts : Array[Bytes],
  rights : Array[Bytes],
  counts : Array[Int64],
  i : Int,
  j : Int,
) -> Bool {
  if counts[i] != counts[j] {
    return counts[i] > counts[j]
  }
  let c = compare_bytes(lefts[i], lefts[j])
  if c != 0 {
    return c < 0
  }
  compare_bytes(rights[i], rights[j]) < 0
}

///|
/// 在一段符号序列里,把 `(left, right)` 全部就地合并成 `merged`。
///
/// 从左到右扫一遍,每次匹配吃掉两个符号,因此结果**不重叠**——
/// 这与编码器逐片合并的语义一致。
fn merge_once(
  seq : Array[Bytes],
  left : Bytes,
  right : Bytes,
  merged : Bytes,
) -> Array[Bytes] {
  let out : Array[Bytes] = []
  let mut i = 0
  let n = seq.length()
  while i < n {
    if i + 1 < n && bytes_eq(seq[i], left) && bytes_eq(seq[i + 1], right) {
      out.push(merged)
      i = i + 2
    } else {
      out.push(seq[i])
      i = i + 1
    }
  }
  out
}

///|
/// 从语料训练一个词表。
///
/// * `vocab_size` —— **目标总数**,含 256 个单字节与 `specials`。
///   语料不够(或频次不够)就提前停下,不补零、不灌水。
/// * `min_frequency` —— pair 频次低于它就停止合并(GPT-2 传统取 2)。
///   取 0 或负数表示"只看 `vocab_size`,不设频次下限"。
/// * `specials` —— 特殊 token 字面量,按给定顺序追加在合并产物之后。
///
/// 返回的 `TrainResult` 同时带出合并序列与本次的参数,便于冻结与 diff。
pub fn train(
  corpus : Array[Bytes],
  vocab_size : Int,
  min_frequency : Int,
  specials : Array[String],
) -> TrainResult raise VocabError {
  let floor = 256 + specials.length()
  if vocab_size < floor {
    raise TrainSizeTooSmall(vocab_size, floor)
  }

  // 1. 基座:256 个单字节,id 与 rank 都是 0..255。
  let tokens : Array[Bytes] = []
  let ranks : Map[Bytes, Int] = Map([])
  for b = 0; b < 256; b = b + 1 {
    let one = Bytes::make(1, b.to_byte())
    ranks.set(one, b)
    tokens.push(one)
  }

  // 2. 预切分 + 词频。
  let freq : Map[Bytes, Int64] = Map([])
  let mut corpus_bytes = 0
  for doc in corpus {
    corpus_bytes = corpus_bytes + doc.length()
    for entry in @bpe.pretokenize(doc) {
      let (s, e) = entry
      if e > s {
        let piece = @bpe.slice_bytes(doc, s, e)
        match freq.get(piece) {
          Some(c) => freq.set(piece, c + 1L)
          None => freq.set(piece, 1L)
        }
      }
    }
  }

  // 3. 词按字节序排好:训练从此与 Map 的迭代顺序彻底无关。
  let raw_words : Array[Bytes] = []
  let raw_freq : Array[Int64] = []
  for k, v in freq {
    raw_words.push(k)
    raw_freq.push(v)
  }
  let word_order : Array[Int] = []
  for i = 0; i < raw_words.length(); i = i + 1 {
    word_order.push(i)
  }
  sort_indices(word_order, fn(i, j) { compare_bytes(raw_words[i], raw_words[j]) < 0 })
  let words : Array[Bytes] = []
  let word_freq : Array[Int64] = []
  for i in word_order {
    words.push(raw_words[i])
    word_freq.push(raw_freq[i])
  }

  // 4. 每个词展开成单字节符号序列,逐步合并。
  let syms : Array[Array[Bytes]] = []
  for w in words {
    let s : Array[Bytes] = []
    for i = 0; i < w.length(); i = i + 1 {
      s.push(Bytes::make(1, w[i]))
    }
    syms.push(s)
  }

  let merges : Array[Merge] = []
  let limit = vocab_size - specials.length()
  while tokens.length() < limit {
    // 统计当前所有相邻 pair(按词频加权)。
    let lefts : Array[Bytes] = []
    let rights : Array[Bytes] = []
    let counts : Array[Int64] = []
    let index : Map[Bytes, Int] = Map([])
    for wi = 0; wi < syms.length(); wi = wi + 1 {
      let seq = syms[wi]
      let f = word_freq[wi]
      let mut i = 0
      while i + 1 < seq.length() {
        let key = pair_key(seq[i], seq[i + 1])
        match index.get(key) {
          Some(k) => counts[k] = counts[k] + f
          None => {
            index.set(key, lefts.length())
            lefts.push(seq[i])
            rights.push(seq[i + 1])
            counts.push(f)
          }
        }
        i = i + 1
      }
    }
    if lefts.length() == 0 {
      break
    }
    let order : Array[Int] = []
    for i = 0; i < lefts.length(); i = i + 1 {
      order.push(i)
    }
    sort_indices(order, fn(i, j) { candidate_less(lefts, rights, counts, i, j) })
    let best = order[0]
    if min_frequency > 0 && counts[best] < min_frequency.to_int64() {
      break
    }
    let merged = @bpe.concat_bytes(lefts[best], rights[best])
    let rank = tokens.length()
    ranks.set(merged, rank)
    tokens.push(merged)
    merges.push(
      { rank: rank, merged: merged, left: lefts[best], right: rights[best] },
    )
    for wi = 0; wi < syms.length(); wi = wi + 1 {
      syms[wi] = merge_once(syms[wi], lefts[best], rights[best], merged)
    }
  }

  // 5. 特殊 token 追加在合并产物之后(顺序由调用方给定,天然确定)。
  let special_entries : Array[(String, Int)] = []
  for lit in specials {
    let id = tokens.length()
    tokens.push(@utf8.encode(lit))
    special_entries.push((lit, id))
  }
  {
    vocab: build_vocab(tokens, ranks, special_entries),
    merges: merges,
    corpus_bytes: corpus_bytes,
    corpus_words: words.length(),
    vocab_size: vocab_size,
    min_frequency: min_frequency,
  }
}