use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, OnceLock};

use crate::command_safety::shell_parser::prewarm_bash_parser;
use crate::tools::tree_sitter_runtime::prewarm_workspace_languages;
use crate::tools::{AstGrepStatus, RipgrepStatus};
use crate::utils::common::detect_workspace_languages;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SearchToolReadiness {
    Ready,
    Missing,
    Error,
}

impl SearchToolReadiness {
    #[must_use]
    pub fn is_ready(self) -> bool {
        matches!(self, Self::Ready)
    }

    #[must_use]
    pub fn label(self) -> &'static str {
        match self {
            Self::Ready => "ready",
            Self::Missing => "missing",
            Self::Error => "error",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SearchToolBundleStatus {
    pub ripgrep: SearchToolReadiness,
    pub ast_grep: SearchToolReadiness,
}

impl SearchToolBundleStatus {
    #[must_use]
    pub fn all_ready(self) -> bool {
        self.ripgrep.is_ready() && self.ast_grep.is_ready()
    }

    #[must_use]
    pub fn all_unavailable(self) -> bool {
        !self.ripgrep.is_ready() && !self.ast_grep.is_ready()
    }

    #[must_use]
    pub fn has_errors(self) -> bool {
        matches!(self.ripgrep, SearchToolReadiness::Error)
            || matches!(self.ast_grep, SearchToolReadiness::Error)
    }

    #[must_use]
    pub fn header_summary(self) -> String {
        if self.all_ready() {
            return "Search: ripgrep · ast-grep".to_string();
        }
        format!(
            "Search: ripgrep {} · ast-grep {}",
            self.ripgrep.label(),
            self.ast_grep.label()
        )
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SearchRuntimeSnapshot {
    pub(crate) workspace_languages: Vec<String>,
    pub(crate) search_tools: SearchToolBundleStatus,
    pub(crate) ripgrep_ready: bool,
    pub(crate) ast_grep_ready: bool,
    pub(crate) code_tree_sitter_languages: Vec<String>,
    pub(crate) bash_tree_sitter_ready: bool,
}

static SEARCH_RUNTIME_CACHE: OnceLock<Mutex<HashMap<PathBuf, SearchRuntimeSnapshot>>> =
    OnceLock::new();

pub(crate) fn snapshot_for_workspace(workspace_root: &Path) -> SearchRuntimeSnapshot {
    let workspace_root = workspace_root.to_path_buf();
    let cache = SEARCH_RUNTIME_CACHE.get_or_init(|| Mutex::new(HashMap::new()));

    if let Some(snapshot) = cache
        .lock()
        .expect("search runtime cache mutex must not be poisoned")
        .get(&workspace_root)
        .cloned()
    {
        return snapshot;
    }

    let workspace_languages = detect_workspace_languages(&workspace_root);
    let search_tools = SearchToolBundleStatus {
        ripgrep: SearchToolReadiness::from_ripgrep_status(RipgrepStatus::check()),
        ast_grep: SearchToolReadiness::from_ast_grep_status(AstGrepStatus::check()),
    };
    let snapshot = SearchRuntimeSnapshot {
        code_tree_sitter_languages: prewarm_workspace_languages(
            workspace_languages.iter().map(String::as_str),
        ),
        workspace_languages,
        search_tools,
        ripgrep_ready: search_tools.ripgrep.is_ready(),
        ast_grep_ready: search_tools.ast_grep.is_ready(),
        bash_tree_sitter_ready: prewarm_bash_parser().is_ok(),
    };

    let mut guard = cache
        .lock()
        .expect("search runtime cache mutex must not be poisoned");
    guard
        .entry(workspace_root)
        .or_insert_with(|| snapshot.clone())
        .clone()
}

pub fn dominant_workspace_language(workspace_root: &Path) -> Option<String> {
    snapshot_for_workspace(workspace_root)
        .workspace_languages
        .into_iter()
        .next()
}

pub fn search_tool_bundle_status(workspace_root: &Path) -> SearchToolBundleStatus {
    snapshot_for_workspace(workspace_root).search_tools
}

impl SearchToolReadiness {
    fn from_ripgrep_status(status: RipgrepStatus) -> Self {
        match status {
            RipgrepStatus::Available { .. } => Self::Ready,
            RipgrepStatus::NotFound => Self::Missing,
            RipgrepStatus::Error { .. } => Self::Error,
        }
    }

    fn from_ast_grep_status(status: AstGrepStatus) -> Self {
        match status {
            AstGrepStatus::Available { .. } => Self::Ready,
            AstGrepStatus::NotFound => Self::Missing,
            AstGrepStatus::Error { .. } => Self::Error,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{
        SearchToolBundleStatus, SearchToolReadiness, dominant_workspace_language,
        snapshot_for_workspace,
    };
    use std::fs;
    use tempfile::TempDir;

    #[test]
    fn snapshot_for_workspace_captures_languages_and_bash_parser_state() {
        let workspace = TempDir::new().expect("workspace tempdir");
        fs::create_dir_all(workspace.path().join("src")).expect("create src");
        fs::create_dir_all(workspace.path().join("web")).expect("create web");
        fs::write(workspace.path().join("src/lib.rs"), "fn alpha() {}\n").expect("write rust");
        fs::write(workspace.path().join("web/app.ts"), "const app = 1;\n").expect("write ts");

        let snapshot = snapshot_for_workspace(workspace.path());

        assert_eq!(
            snapshot.workspace_languages,
            vec!["Rust".to_string(), "TypeScript".to_string()]
        );
        assert_eq!(
            snapshot.ripgrep_ready,
            snapshot.search_tools.ripgrep.is_ready()
        );
        assert_eq!(
            snapshot.ast_grep_ready,
            snapshot.search_tools.ast_grep.is_ready()
        );
        assert_eq!(
            snapshot.code_tree_sitter_languages,
            vec!["Rust".to_string(), "TypeScript".to_string()]
        );
        assert!(snapshot.bash_tree_sitter_ready);
    }

    #[test]
    fn snapshot_for_workspace_reuses_cached_languages() {
        let workspace = TempDir::new().expect("workspace tempdir");
        fs::create_dir_all(workspace.path().join("src")).expect("create src");
        fs::write(workspace.path().join("src/lib.rs"), "fn alpha() {}\n").expect("write rust");

        let initial = snapshot_for_workspace(workspace.path());

        fs::create_dir_all(workspace.path().join("web")).expect("create web");
        fs::write(workspace.path().join("web/app.ts"), "const app = 1;\n").expect("write ts");

        let cached = snapshot_for_workspace(workspace.path());

        assert_eq!(initial.workspace_languages, cached.workspace_languages);
    }

    #[test]
    fn dominant_workspace_language_returns_first_detected_language() {
        let workspace = TempDir::new().expect("workspace tempdir");
        fs::create_dir_all(workspace.path().join("src")).expect("create src");
        fs::create_dir_all(workspace.path().join("web")).expect("create web");
        fs::write(workspace.path().join("src/lib.rs"), "fn alpha() {}\n").expect("write rust");
        fs::write(workspace.path().join("src/main.rs"), "fn beta() {}\n").expect("write rust");
        fs::write(workspace.path().join("web/app.ts"), "const app = 1;\n").expect("write ts");

        assert_eq!(
            dominant_workspace_language(workspace.path()).as_deref(),
            Some("Rust")
        );
    }

    #[test]
    fn snapshot_for_workspace_ignores_languages_without_shared_parser_support() {
        let workspace = TempDir::new().expect("workspace tempdir");
        fs::create_dir_all(workspace.path().join("ios")).expect("create ios");
        fs::write(workspace.path().join("ios/App.swift"), "struct App {}\n").expect("write swift");

        let snapshot = snapshot_for_workspace(workspace.path());

        assert_eq!(snapshot.workspace_languages, vec!["Swift".to_string()]);
        assert!(snapshot.code_tree_sitter_languages.is_empty());
    }

    #[test]
    fn search_tool_bundle_header_summary_reflects_readiness() {
        let status = SearchToolBundleStatus {
            ripgrep: SearchToolReadiness::Ready,
            ast_grep: SearchToolReadiness::Missing,
        };

        assert_eq!(
            status.header_summary(),
            "Search: ripgrep ready · ast-grep missing"
        );
        assert!(!status.all_ready());
        assert!(!status.all_unavailable());
        assert!(!status.has_errors());
    }

    #[test]
    fn search_tool_bundle_header_summary_all_ready() {
        let status = SearchToolBundleStatus {
            ripgrep: SearchToolReadiness::Ready,
            ast_grep: SearchToolReadiness::Ready,
        };

        assert_eq!(status.header_summary(), "Search: ripgrep · ast-grep");
        assert!(status.all_ready());
    }
}
