use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, Ordering};
use inputx_wubi::{L0Snapshot, WubiDict};
static DICT: OnceLock<WubiDict> = OnceLock::new();
fn dict() -> &'static WubiDict {
DICT.get_or_init(WubiDict::embedded)
}
const RARE_CODEPOINT_THRESHOLD: u32 = 0x20000;
static SHOW_RARE: AtomicBool = AtomicBool::new(false);
pub fn set_show_rare(show: bool) {
SHOW_RARE.store(show, Ordering::Relaxed);
}
pub fn show_rare() -> bool {
SHOW_RARE.load(Ordering::Relaxed)
}
pub fn warmup() {
let d = dict();
for code in &[
"g", "h", "j", "k", "l", "m", "a", "s", "d", "f", "p", "q", "wq",
] {
let _ = d.lookup(code);
}
}
pub fn is_displayable(word: &str) -> bool {
word.chars().all(|c| (c as u32) < RARE_CODEPOINT_THRESHOLD)
}
pub fn lookup(code: &str) -> Vec<String> {
let mut all = dict().lookup(code);
if !SHOW_RARE.load(Ordering::Relaxed) {
all.retain(|w| is_displayable(w));
}
all
}
pub fn lookup_with_scores(code: &str) -> Vec<(String, f64)> {
let mut all: Vec<(String, f64)> = Vec::new();
dict().lookup_with_scores_into(code, &mut all);
if !SHOW_RARE.load(Ordering::Relaxed) {
all.retain(|(w, _)| is_displayable(w));
}
all
}
pub fn lookup_with_layer(code: &str) -> Vec<(String, f64, inputx_wubi::Layer)> {
let mut all: Vec<(String, f64, inputx_wubi::Layer)> = Vec::new();
dict().lookup_with_layer_into(code, &mut all);
if !SHOW_RARE.load(Ordering::Relaxed) {
all.retain(|(w, _, _)| is_displayable(w));
}
all
}
pub fn lookup_with_freq_layer(
code: &str,
) -> Vec<(String, inputx_wubi::Layer, u64)> {
let reader = crate::wubi_idf_reader();
let entries = reader.lookup(code.as_bytes());
let mut all: Vec<(String, inputx_wubi::Layer, u64)> = entries
.into_iter()
.map(|e| {
(
e.word.to_string(),
crate::layer_from_idf_tag(e.flags.engine_tag()),
e.raw_freq as u64,
)
})
.collect();
if !SHOW_RARE.load(Ordering::Relaxed) {
all.retain(|(w, _, _)| is_displayable(w));
}
all
}
pub fn prefix_predictions(prefix: &str) -> Vec<(String, u64, usize)> {
let reader = crate::wubi_idf_reader();
let prefix_lower = prefix.to_ascii_lowercase();
let prefix_len = prefix_lower.len();
let mut all: Vec<(String, u64, usize)> = Vec::new();
reader.prefix_for_each_entry(prefix_lower.as_bytes(), |e| {
if e.code.len() <= prefix_len {
return;
}
all.push((e.word.to_string(), e.raw_freq as u64, e.code.len()));
});
all.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
if !SHOW_RARE.load(Ordering::Relaxed) {
all.retain(|(w, _, _)| is_displayable(w));
}
all
}
pub fn record_pick(code: &str, word: &str) {
dict().record_pick(code, word);
}
pub fn export_l0() -> L0Snapshot {
dict().export_l0()
}
pub fn import_l0(snap: L0Snapshot) -> usize {
dict().import_l0(snap)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_letter_jianma1_resolves() {
assert!(lookup("g").contains(&"一".to_string()));
}
#[test]
fn keyname_zigen_full_code() {
assert!(lookup("gggg").contains(&"王".to_string()));
}
#[test]
fn unknown_returns_empty() {
assert!(lookup("xyzz123").is_empty());
assert!(lookup("").is_empty());
}
}