use crate::indexer::{
indexer::trie::{TrieNode, WordMeta},
utils::{tokenizer::tokenize, word_utils::get_word_half},
};
use crate::indexer::config::config::index_dir;
use anyhow::Result;
use data_encoding::BASE32_NOPAD;
use std::collections::HashMap;
use std::path::PathBuf;
use std::fs;
use rust_stemmers::{Algorithm, Stemmer};
use std::io::BufReader;
use std::time::Instant;
pub fn rank_documents(results: Vec<(HashMap<String, WordMeta>, String)>) -> Vec<(String, f32)> {
let mut doc_scores: HashMap<String, f32> = HashMap::new();
let mut doc_occurrences: HashMap<String, usize> = HashMap::new();
let files_metadata = match fs::read_to_string(PathBuf::from(index_dir()).join("doc_indexes/files_metadata.json")) {
Ok(data) => data,
Err(e) => {
eprintln!("Error reading files_metadata.json: {}", e);
return Vec::new(); }
};
let files_metadata: HashMap<String, serde_json::Value> = match serde_json::from_str(&files_metadata) {
Ok(data) => data,
Err(e) => {
eprintln!("Error parsing files_metadata.json: {}", e);
return Vec::new(); }
};
for (doc_map, _found_word) in results {
for (doc_id, word_meta) in doc_map {
*doc_scores.entry(doc_id.clone()).or_insert(0.0) += word_meta.weight;
*doc_occurrences.entry(doc_id).or_insert(0) += 1;
}
}
for (doc_id, occurrences) in &doc_occurrences {
if let Some(score) = doc_scores.get_mut(doc_id) {
*score *= *occurrences as f32; }
}
let mut scored_docs: Vec<(String, f32)> = doc_scores.into_iter().collect();
scored_docs.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
for doc in &mut scored_docs {
if let Some(metadata) = files_metadata.get(&doc.0) {
if let Some(path) = metadata.get("path").and_then(|p| p.as_str()) {
doc.0 = path.to_string();
}
}
}
scored_docs
}
fn fuzzy_search<'a>(
node: &'a TrieNode,
chars: &[char],
idx: usize,
allowed_errors: usize,
current_word: String, ) -> Option<(String, &'a TrieNode)> {
if idx == chars.len() {
return Some((current_word, node));
}
let ch = chars[idx];
if let Some(child) = node.children.get(&ch) {
if let Some((reconstructed_word, result)) = fuzzy_search(child, chars, idx + 1, allowed_errors, current_word.clone() + &ch.to_string()) {
return Some((reconstructed_word, result));
}
}
if allowed_errors == 0 {
return None;
}
for (other_ch, child) in &node.children {
if *other_ch != ch {
if let Some((reconstructed_word, result)) = fuzzy_search(child, chars, idx + 1, allowed_errors - 1, current_word.clone() + &other_ch.to_string()) {
return Some((reconstructed_word, result));
}
}
}
if let Some((reconstructed_word, result)) = fuzzy_search(node, chars, idx + 1, allowed_errors - 1, current_word.clone()) {
return Some((reconstructed_word, result));
}
for (_other_ch, child) in &node.children {
if let Some((reconstructed_word, result)) = fuzzy_search(child, chars, idx, allowed_errors - 1, current_word.clone()) {
return Some((reconstructed_word, result));
}
}
None
}
pub fn search_word(word: &str) -> Result<Option<(HashMap<String, WordMeta>, String)>> {
let stemmer = Stemmer::create(Algorithm::English);
let stemmed_word = stemmer.stem(word).to_string();
if stemmed_word.is_empty() {
return Ok(None);
}
let word_half = get_word_half(&stemmed_word);
let encoded_name = BASE32_NOPAD.encode(word_half.as_bytes());
let mut filename = PathBuf::from(index_dir());
for ch in encoded_name.chars().take(4) {
filename.push(ch.to_string());
}
filename.push(format!("{}.json", encoded_name));
println!("encoded_name: {}", encoded_name);
if !filename.exists() {
return Ok(None);
}
let file_data: HashMap<String, TrieNode> = {
let file = fs::File::open(&filename)?;
let reader = BufReader::new(file);
serde_json::from_reader(reader)?
};
let trie = file_data.get(&word_half)
.ok_or_else(|| anyhow::anyhow!("No index for word half {}", word_half))?;
let current = trie;
let chars: Vec<char> = stemmed_word.chars().collect();
let result = fuzzy_search(current, &chars, 0, 2, String::new())
.ok_or_else(|| anyhow::anyhow!("Word not found with allowed distance"))?;
if let Some(doc_ids) = result.1.doc_ids.clone() {
Ok(Some((doc_ids, result.0)))
} else {
Err(anyhow::anyhow!("No doc_ids found for the word"))
}
}
pub fn search_query(query: &str) {
let start_time = Instant::now(); let words: Vec<String> = tokenize(query);
let results: Vec<(HashMap<String, WordMeta>, String)> = words.iter()
.filter_map(|word| match search_word(word) {
Ok(Some((doc_ids, found_word))) => {
println!("Found word: {}", found_word); Some((doc_ids, found_word)) }
Ok(None) => None, Err(e) => {
eprintln!("Error searching word '{}': {}", word, e);
None
}
})
.collect();
let ranked = rank_documents(results);
let elapsed_time = start_time.elapsed();
println!("Search completed in: {:?}", elapsed_time);
println!("Ranked documents: {:#?}", ranked.iter().take(10).collect::<Vec<_>>());
}