use anyhow::Result;

use crate::config::Config;
use crate::secrets::SecretManager;
use crate::template::catalog::TemplateCatalog;

use super::index::{SearchHit, build_documents};
use super::local_fastembed::search_local;
use super::remote_openai_compat::search_remote;

pub async fn search_templates(
    query: &str,
    catalog: &TemplateCatalog,
    config: &Config,
    secrets: &SecretManager,
    limit: usize,
) -> Result<Vec<SearchHit>> {
    let documents = build_documents(catalog);

    if let Some(remote_hits) =
        search_remote(query, &documents, &config.search.remote, secrets).await?
    {
        return Ok(remote_hits.into_iter().take(limit).collect());
    }

    let query_owned = query.to_string();
    let docs_owned = documents.clone();
    let search_cfg = config.search.clone();

    let local_result =
        tokio::task::spawn_blocking(move || search_local(&query_owned, &docs_owned, &search_cfg))
            .await;

    let mut hits = match local_result {
        Ok(Ok(hits)) => hits,
        _ => lexical_fallback(query, &documents),
    };

    hits.truncate(limit);
    Ok(hits)
}

fn lexical_fallback(query: &str, documents: &[super::index::SearchDocument]) -> Vec<SearchHit> {
    let mut hits: Vec<SearchHit> = documents
        .iter()
        .map(|doc| {
            let score = lexical_score(query, &doc.text);
            SearchHit {
                key: doc.key.clone(),
                score,
                summary: doc.summary.clone(),
            }
        })
        .collect();
    hits.sort_by(|a, b| b.score.total_cmp(&a.score));
    hits
}

fn lexical_score(query: &str, text: &str) -> f32 {
    let query_lower = query.to_ascii_lowercase();
    let text_lower = text.to_ascii_lowercase();
    let mut score = 0.0;
    for token in query_lower.split_whitespace() {
        if text_lower.contains(token) {
            score += 1.0;
        }
    }
    score
}
