// 词表模型与校验。
//
// 不变量(每一条都配测试):
//   V1 `byte_ids` 的 256 项必须全部存在(byte-level BPE 的根基)。
//   V2 每个 token 的字节长度 >= 1(不许出现空 token)。
//   V3 同一字节序列出现两次 -> 报错,而不是静默取一个。
//   V4 `Vocab` 构造完成后不可变;所有操作都是纯函数。

///|
/// 词表。`tokens[id]` 是该 id 的字节序列;`ranks` 是合并优先级;
/// `ids` 提供"字节序列 -> id"的反查;`specials` 是特殊 token 字面量。
pub(all) struct Vocab {
  tokens : Array[Bytes]
  ranks : Map[Bytes, Int]
  ids : Map[Bytes, Int]
  /// 与 `ranks` / `ids` 同内容的短键表:≤ `@bpe.SHORT_MAX` 字节的 token
  /// 被 `@bpe.pack_short` 打包成 `Int64`,编码热路径查这两张表就省掉了
  /// 每次都拼一个 `Bytes` 出来。构建期一次性填好。
  rank_short : Map[Int64, Int]
  id_short : Map[Int64, Int]
  specials : Array[(String, Int)]
  byte_ids : Array[Int]
}

///|
/// 由三块数据构造一个经过校验的 `Vocab`。
///
/// `specials` 的每一项是 `(字面量, id)`;id 必须落在 `tokens` 的范围内。
pub fn build_vocab(
  tokens : Array[Bytes],
  ranks : Map[Bytes, Int],
  specials : Array[(String, Int)],
) -> Vocab raise VocabError {
  let n = tokens.length()
  let ids : Map[Bytes, Int] = Map([])
  for i = 0; i < n; i = i + 1 {
    let t = tokens[i]
    if t.length() == 0 {
      raise EmptyToken(i)
    }
    match ids.get(t) {
      Some(prev) => raise DuplicateToken(prev, i)
      None => ids.set(t, i)
    }
  }
  // V1:256 个单字节 token 必须齐备。
  let byte_ids : Array[Int] = []
  for b = 0; b < 256; b = b + 1 {
    let one = Bytes::make(1, b.to_byte())
    match ids.get(one) {
      Some(id) => byte_ids.push(id)
      None => raise MissingByteToken(b)
    }
  }
  // 特殊 token:校验 id 范围,并按"字节长度降序、id 升序"排序(最长优先匹配)。
  let sorted : Array[(String, Int)] = []
  for e in specials {
    let (lit, id) = e
    if id < 0 || id >= n {
      raise SpecialOutOfRange(id, n)
    }
    sorted.push((lit, id))
  }
  sort_specials(sorted)
  // 短键表:与 ranks / ids 逐项对应,只是键换成 Int64。
  let rank_short : Map[Int64, Int] = Map([])
  for entry in ranks {
    let (key, r) = entry
    let len = key.length()
    if len > 0 && len <= @bpe.SHORT_MAX {
      rank_short.set(@bpe.pack_short(key, 0, len), r)
    }
  }
  let id_short : Map[Int64, Int] = Map([])
  for entry in ids {
    let (key, id) = entry
    let len = key.length()
    if len > 0 && len <= @bpe.SHORT_MAX {
      id_short.set(@bpe.pack_short(key, 0, len), id)
    }
  }
  {
    tokens,
    ranks,
    ids,
    rank_short,
    id_short,
    specials: sorted,
    byte_ids,
  }
}

///|
/// 就地排序:按字面量的**字节长度降序**,长度相同则按 id 升序。
/// 插入排序,完全确定,不依赖任何库的排序实现。
fn sort_specials(arr : Array[(String, Int)]) -> Unit {
  let n = arr.length()
  for i = 1; i < n; i = i + 1 {
    let cur = arr[i]
    let (cur_lit, cur_id) = cur
    let cur_len = @utf8.encode(cur_lit).length()
    let mut j = i - 1
    while j >= 0 {
      let (lit, id) = arr[j]
      let len = @utf8.encode(lit).length()
      let move_right = len < cur_len || (len == cur_len && id > cur_id)
      if !move_right {
        break
      }
      arr[j + 1] = arr[j]
      j = j - 1
    }
    arr[j + 1] = cur
  }
}

///|
/// token 总数。
pub fn Vocab::token_count(self : Vocab) -> Int {
  self.tokens.length()
}

///|
/// 单字节 token 的 id。
pub fn Vocab::byte_id(self : Vocab, b : Int) -> Int {
  self.byte_ids[b & 0xFF]
}

///|
/// 字节序列的合并优先级(越小越先合并)。
pub fn Vocab::rank_of(self : Vocab, bytes : Bytes) -> Int? {
  self.ranks.get(bytes)
}

///|
/// 字节序列对应的 token id。
pub fn Vocab::id_of(self : Vocab, bytes : Bytes) -> Int? {
  self.ids.get(bytes)
}

///|
/// 已登记的特殊 token 字面量列表(按最长优先排序)。
pub fn Vocab::special_literals(self : Vocab) -> Array[String] {
  self.specials.map(fn(e) {
    let (lit, _) = e
    lit
  })
}

///|
/// 特殊 token 名字对应的 id。
pub fn Vocab::special_id(self : Vocab, name : String) -> Int? {
  for e in self.specials {
    let (lit, id) = e
    if lit == name {
      return Some(id)
    }
  }
  None
}

///|
/// 转成 `bpe` 包认识的视图。`specials` 已是"按长度降序"的顺序。
pub fn Vocab::view(self : Vocab) -> @bpe.VocabView {
  let sp : Array[(Bytes, Int)] = []
  for e in self.specials {
    let (lit, id) = e
    sp.push((@utf8.encode(lit), id))
  }
  {
    tokens: self.tokens,
    byte_ids: self.byte_ids,
    rank_of: self.ranks,
    id_of: self.ids,
    rank_short: self.rank_short,
    id_short: self.id_short,
    specials: sp,
  }
}