use std::collections::HashMap;
use std::path::Path;
use std::time::{Duration, Instant, UNIX_EPOCH};

pub(crate) fn cmd_index(args: &[String]) {
    let project_root = super::common::detect_project_root(args);
    let root = Path::new(&project_root);

    let sub = args
        .iter()
        .find(|a| !a.starts_with("--"))
        .map(String::as_str);
    match sub {
        Some("status") => {
            println!(
                "{}",
                crate::core::index_orchestrator::status_json(&project_root)
            );
        }
        Some("build") => {
            crate::core::index_orchestrator::ensure_all_background(&project_root);
            println!("started");
        }
        Some("build-full") => {
            let bm25_path = crate::core::bm25_index::BM25Index::index_file_path(root);
            let _ = std::fs::remove_file(&bm25_path);
            if let Some(dir) = crate::core::graph_index::ProjectIndex::index_dir(&project_root) {
                let _ = std::fs::remove_file(dir.join("index.json.zst"));
                let _ = std::fs::remove_file(dir.join("index.json"));
            }
            crate::core::index_orchestrator::ensure_all_background(&project_root);

            let started = std::time::Instant::now();
            let timeout = Duration::from_mins(2);
            eprint!("rebuilding indexes");
            loop {
                std::thread::sleep(Duration::from_millis(500));
                let status = crate::core::index_orchestrator::status_json(&project_root);
                if !status.contains("\"building\"") {
                    break;
                }
                eprint!(".");
                if started.elapsed() > timeout {
                    eprintln!(" timeout (background build continues)");
                    return;
                }
            }
            eprintln!(" done");
        }
        Some("build-graph") => {
            let root_str = project_root.clone();
            let result =
                crate::tools::ctx_impact::handle("build", None, &root_str, None, Some("text"));
            println!("{result}");
        }
        Some("watch") => run_watcher(root),
        _ => {
            eprintln!(
                "Usage: lean-ctx index <status|build|build-full|build-graph|watch> [--root <path>]\n\
                 Examples:\n\
                   lean-ctx index status\n\
                   lean-ctx index build          (BM25 + JSON graph index)\n\
                   lean-ctx index build-full     (force rebuild all indexes)\n\
                   lean-ctx index build-graph    (SQLite property graph for impact analysis)\n\
                   lean-ctx index watch"
            );
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct FileState {
    mtime_ms: u64,
    size_bytes: u64,
}

fn run_watcher(project_root: &Path) {
    let hash = crate::core::index_namespace::namespace_hash(project_root);
    let lock_name = format!("index-watch-{}", &hash[..8.min(hash.len())]);
    let Some(lock) = crate::core::startup_guard::try_acquire_lock(
        &lock_name,
        Duration::from_millis(800),
        Duration::from_secs(8),
    ) else {
        eprintln!("index watcher already running");
        return;
    };

    let mut last = snapshot_code_files(project_root);
    let mut pending: Option<Instant> = None;
    let poll = Duration::from_millis(700);
    let debounce = Duration::from_millis(900);

    loop {
        lock.touch();
        std::thread::sleep(poll);

        let cur = snapshot_code_files(project_root);
        if cur != last {
            last = cur;
            pending = Some(Instant::now());
            continue;
        }

        if let Some(t) = pending {
            if t.elapsed() >= debounce {
                crate::core::index_orchestrator::ensure_all_background(
                    project_root.to_string_lossy().as_ref(),
                );
                pending = None;
            }
        }
    }
}

fn snapshot_code_files(project_root: &Path) -> HashMap<String, FileState> {
    let walker = ignore::WalkBuilder::new(project_root)
        .hidden(true)
        .git_ignore(true)
        .git_global(true)
        .git_exclude(true)
        .build();

    let mut out: HashMap<String, FileState> = HashMap::new();
    for entry in walker.flatten() {
        let path = entry.path();
        if !path.is_file() {
            continue;
        }
        if path.components().any(|c| c.as_os_str() == ".git") {
            continue;
        }
        if !is_code_file(path) {
            continue;
        }
        let Ok(meta) = path.metadata() else {
            continue;
        };
        let Ok(modified) = meta.modified() else {
            continue;
        };
        let Some(mtime_ms) = modified
            .duration_since(UNIX_EPOCH)
            .ok()
            .map(|d| d.as_millis() as u64)
        else {
            continue;
        };

        let rel = path
            .strip_prefix(project_root)
            .unwrap_or(path)
            .to_string_lossy()
            .to_string();
        if rel.is_empty() {
            continue;
        }

        out.insert(
            rel,
            FileState {
                mtime_ms,
                size_bytes: meta.len(),
            },
        );
    }
    out
}

fn is_code_file(path: &Path) -> bool {
    crate::core::bm25_index::is_code_file(path)
}
