use crate::error::WordTableError;
use crate::registry::{WordDefRegistry, WordEntry, random::RandomSelector};
use fast_radix_trie::RadixMap;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct WordCacheKey {
pub module_name: String,
pub search_key: String,
}
impl WordCacheKey {
pub fn new(module_name: &str, search_key: &str) -> Self {
Self {
module_name: module_name.to_string(),
search_key: search_key.to_string(),
}
}
}
struct CachedWordSelection {
words: Vec<String>,
next_index: usize,
}
pub struct WordTable {
entries: Vec<WordEntry>,
prefix_index: RadixMap<Vec<usize>>,
cached_selections: HashMap<WordCacheKey, CachedWordSelection>,
random_selector: Box<dyn RandomSelector>,
shuffle_enabled: bool,
}
impl WordTable {
pub fn new(random_selector: Box<dyn RandomSelector>) -> Self {
Self {
entries: Vec::new(),
prefix_index: RadixMap::new(),
cached_selections: HashMap::new(),
random_selector,
shuffle_enabled: true,
}
}
pub fn from_word_def_registry(
registry: WordDefRegistry,
random_selector: Box<dyn RandomSelector>,
) -> Self {
let entries = registry.into_entries();
let mut prefix_index = RadixMap::new();
for entry in &entries {
let entry_list = prefix_index
.entry(entry.key.as_bytes())
.or_insert_with(Vec::new);
entry_list.push(entry.id);
}
Self {
entries,
prefix_index,
cached_selections: HashMap::new(),
random_selector,
shuffle_enabled: true,
}
}
pub fn collect_word_candidates(
&self,
module_name: &str,
key: &str,
) -> Result<Vec<String>, WordTableError> {
if module_name.is_empty() {
let mut global_entry_ids: Vec<usize> = Vec::new();
for (matched_key, ids) in self.prefix_index.iter_prefix(key.as_bytes()) {
if !matched_key.starts_with(&[b':']) {
global_entry_ids.extend(ids.iter().copied());
}
}
if global_entry_ids.is_empty() {
return Err(WordTableError::WordNotFound {
key: key.to_string(),
});
}
let mut all_words: Vec<String> = Vec::new();
for id in &global_entry_ids {
if let Some(entry) = self.entries.get(*id) {
all_words.extend(entry.values.iter().cloned());
}
}
if all_words.is_empty() {
return Err(WordTableError::WordNotFound {
key: key.to_string(),
});
}
return Ok(all_words);
}
let local_key = format!(":{}:{}", module_name, key);
let mut local_entry_ids: Vec<usize> = Vec::new();
for (_matched_key, ids) in self.prefix_index.iter_prefix(local_key.as_bytes()) {
local_entry_ids.extend(ids.iter().copied());
}
if local_entry_ids.is_empty() {
return Err(WordTableError::WordNotFound {
key: key.to_string(),
});
}
let mut local_words: Vec<String> = Vec::new();
for id in &local_entry_ids {
if let Some(entry) = self.entries.get(*id) {
local_words.extend(entry.values.iter().cloned());
}
}
if local_words.is_empty() {
return Err(WordTableError::WordNotFound {
key: key.to_string(),
});
}
Ok(local_words)
}
pub fn search_word(
&mut self,
module_name: &str,
key: &str,
_filters: &[String],
) -> Result<String, WordTableError> {
let cache_key = WordCacheKey::new(module_name, key);
if let Some(cached) = self.cached_selections.get_mut(&cache_key) {
if cached.next_index < cached.words.len() {
let word = cached.words[cached.next_index].clone();
cached.next_index += 1;
return Ok(word);
}
}
let all_words = self.collect_word_candidates(module_name, key)?;
let mut word_indices: Vec<usize> = (0..all_words.len()).collect();
if self.shuffle_enabled {
self.random_selector.shuffle_usize(&mut word_indices);
}
let shuffled_words: Vec<String> = word_indices
.into_iter()
.map(|i| all_words[i].clone())
.collect();
let result = shuffled_words[0].clone();
self.cached_selections.insert(
cache_key,
CachedWordSelection {
words: shuffled_words,
next_index: 1,
},
);
Ok(result)
}
pub fn set_shuffle_enabled(&mut self, enabled: bool) {
self.shuffle_enabled = enabled;
}
pub fn entries(&self) -> &[WordEntry] {
&self.entries
}
pub fn clear_cache(&mut self) {
self.cached_selections.clear();
}
pub fn replace_selector(&mut self, selector: Box<dyn RandomSelector>) {
self.random_selector = selector;
self.cached_selections.clear();
}
}