use alloc::collections::{BTreeMap, BTreeSet};
use alloc::vec::Vec;
use unicode_normalization::UnicodeNormalization;
use unicode_normalization::char::{canonical_combining_class, decompose_canonical};
use crate::nfc_map::{HANGUL_S_BASE, HANGUL_S_COUNT};
const MAX_CODE_POINT: u32 = 0x10_FFFF;
const QUICK_LIMIT: u32 = 0x300;
pub fn build<'a, I>(keys: I) -> Vec<u8>
where
I: IntoIterator<Item = &'a str>,
{
let mut set: BTreeSet<char> = BTreeSet::new();
for key in keys {
set.extend(key.nfd());
}
if set.is_empty() {
return Vec::new();
}
let mut entries: BTreeMap<char, Vec<char>> = BTreeMap::new();
let mut reaches_slow_path = false;
let mut buf: Vec<char> = Vec::new();
for cp in 0..=MAX_CODE_POINT {
if cp.wrapping_sub(HANGUL_S_BASE) < HANGUL_S_COUNT {
continue;
}
let Some(ch) = char::from_u32(cp) else {
continue;
};
buf.clear();
decompose_canonical(ch, |c| buf.push(c));
if !buf.iter().all(|c| set.contains(c)) {
continue;
}
if cp >= QUICK_LIMIT {
reaches_slow_path = true;
}
entries.insert(ch, buf.clone());
}
if !reaches_slow_path {
return Vec::new();
}
let classes: Vec<(char, u8)> = set
.iter()
.map(|c| (*c, canonical_combining_class(*c)))
.filter(|(_, ccc)| *ccc != 0)
.collect();
let mut pool: Vec<char> = Vec::new();
let mut shared: BTreeMap<Vec<char>, u32> = BTreeMap::new();
let mut table: Vec<(char, u8, u32)> = Vec::new();
for (ch, decomp) in &entries {
if decomp.as_slice() == [*ch] {
table.push((*ch, 0, 0));
continue;
}
let off = *shared.entry(decomp.clone()).or_insert_with(|| {
let at = u32::try_from(pool.len()).unwrap_or(u32::MAX);
pool.extend_from_slice(decomp);
at
});
let len = u8::try_from(decomp.len()).unwrap_or(u8::MAX);
table.push((*ch, len, off));
}
let mut out = Vec::with_capacity(12 + table.len() * 8 + classes.len() * 4 + pool.len() * 3);
out.extend_from_slice(&count(table.len()).to_le_bytes());
out.extend_from_slice(&count(classes.len()).to_le_bytes());
out.extend_from_slice(&count(pool.len()).to_le_bytes());
for (ch, len, off) in &table {
push_code_point(&mut out, *ch);
out.push(*len);
out.extend_from_slice(&off.to_le_bytes());
}
for (ch, ccc) in &classes {
push_code_point(&mut out, *ch);
out.push(*ccc);
}
for ch in &pool {
push_code_point(&mut out, *ch);
}
out
}
fn count(n: usize) -> u32 {
u32::try_from(n).unwrap_or(u32::MAX)
}
fn push_code_point(out: &mut Vec<u8>, ch: char) {
let [x0, x1, x2, _] = u32::from(ch).to_le_bytes();
out.extend_from_slice(&[x0, x1, x2]);
}