mod db;
mod parse;
pub mod query;
mod resolve;
pub mod server;

use ignore::WalkBuilder;
use serde::Serialize;
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use std::sync::mpsc::{self, Sender};
use std::sync::{Arc, Mutex};
use tauri::{AppHandle, Manager};

/// 超过此大小的文件不索引（生成代码/压缩产物等）
const MAX_FILE: u64 = 2 * 1024 * 1024;

/// 后台索引任务
enum Job {
    /// 全量扫描某项目；bool=force：true 时忽略 mtime 强制重解析所有文件（手动重建用）
    Scan(String, bool),
    /// 增量更新某项目的若干变动路径（存在则重解析，不存在则删除）
    Update(String, Vec<PathBuf>),
    /// 删除某项目的整个索引库
    Drop(String),
}

/// 单项目索引状态（供前端展示）
#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProjectStatus {
    pub indexing: bool,
    pub files: u64,
    pub symbols: u64,
    pub last_error: Option<String>,
    pub updated_at: u64,
}

type StatusMap = Arc<Mutex<HashMap<String, ProjectStatus>>>;

/// 索引管理器：持有后台工作线程的任务通道与各项目状态。作为 Tauri 托管状态。
pub struct IndexState {
    tx: Mutex<Sender<Job>>,
    status: StatusMap,
    /// 上次聚焦兜底对账的时间戳（ms），用于节流
    last_reconcile: Mutex<u64>,
}

impl IndexState {
    /// 启动单个后台工作线程（顺序处理任务，天然避免并发打满 CPU 冻结 UI）
    pub fn start() -> Self {
        let (tx, rx) = mpsc::channel::<Job>();
        let status: StatusMap = Arc::new(Mutex::new(HashMap::new()));
        let st = status.clone();
        std::thread::Builder::new()
            .name("ccgui-indexer".into())
            .spawn(move || {
                // Indexer 在本线程内创建并长期持有，缓存查询配置；不跨线程
                let mut indexer = parse::Indexer::new();
                while let Ok(job) = rx.recv() {
                    match job {
                        Job::Scan(root, force) => full_scan(&mut indexer, &root, &st, force),
                        Job::Update(root, paths) => update_files(&mut indexer, &root, &paths, &st),
                        Job::Drop(root) => drop_index(&root, &st),
                    }
                }
            })
            .expect("启动索引线程失败");
        Self {
            tx: Mutex::new(tx),
            status,
            last_reconcile: Mutex::new(0),
        }
    }

    fn enqueue(&self, job: Job) {
        let _ = self.tx.lock().unwrap().send(job);
    }

    /// 普通扫描：跳过 mtime 未变的文件（启动/新增项目用，快）
    pub fn scan(&self, root: &str) {
        self.enqueue(Job::Scan(root.to_string(), false));
    }

    /// 强制重建：忽略 mtime 重解析所有文件（手动“重建”按钮用）
    pub fn rebuild(&self, root: &str) {
        self.enqueue(Job::Scan(root.to_string(), true));
    }

    pub fn update(&self, root: &str, paths: Vec<PathBuf>) {
        self.enqueue(Job::Update(root.to_string(), paths));
    }

    pub fn drop_project(&self, root: &str) {
        self.enqueue(Job::Drop(root.to_string()));
    }

    pub fn status_of(&self, root: &str) -> ProjectStatus {
        self.status.lock().unwrap().get(root).cloned().unwrap_or_default()
    }
}

// ---------- 后台任务实现 ----------

fn full_scan(indexer: &mut parse::Indexer, root: &str, status: &StatusMap, force: bool) {
    set_status(status, root, |s| s.indexing = true);
    let result = scan_inner(indexer, root, status, force);
    set_status(status, root, |s| {
        s.indexing = false;
        s.updated_at = now_ms();
        s.last_error = result.err();
    });
}

fn scan_inner(
    indexer: &mut parse::Indexer,
    root: &str,
    status: &StatusMap,
    force: bool,
) -> Result<(), String> {
    let (files, symbols) = scan_core(indexer, root, force)?;
    set_status(status, root, |s| {
        s.files = files;
        s.symbols = symbols;
    });
    Ok(())
}

/// 一次性同步全量扫描（供 --index-scan 无界面命令使用），强制重解析，返回 (文件数, 符号定义数)
pub fn scan_once(root: &str) -> Result<(u64, u64), String> {
    let mut indexer = parse::Indexer::new();
    scan_core(&mut indexer, root, true)
}

/// 全量扫描核心：遍历项目、解析入库、清理已不存在的文件，返回统计。
/// force=true 时忽略 mtime 强制重解析所有文件。
fn scan_core(indexer: &mut parse::Indexer, root: &str, force: bool) -> Result<(u64, u64), String> {
    let db_path = db::db_path_for(root);
    let mut conn = db::open_rw(&db_path)?;
    let tx = conn.transaction().map_err(|e| e.to_string())?;
    let mut seen: HashSet<String> = HashSet::new();
    for entry in WalkBuilder::new(root).hidden(true).build() {
        let entry = match entry {
            Ok(e) => e,
            Err(_) => continue,
        };
        if !entry.file_type().map(|t| t.is_file()).unwrap_or(false) {
            continue;
        }
        let path = entry.path();
        if is_noise(path) {
            continue;
        }
        let ext = ext_of(path);
        if !parse::is_indexable(&ext) {
            continue;
        }
        let rel = rel_path(root, path);
        seen.insert(rel.clone());
        let meta = match path.metadata() {
            Ok(m) => m,
            Err(_) => {
                db::record_parse_issue(&tx, &rel, &ext, "metadata_failed")?;
                continue;
            }
        };
        if meta.len() > MAX_FILE {
            db::record_parse_issue(&tx, &rel, &ext, "too_large")?;
            continue;
        }
        let mtime = mtime_ms(&meta);
        // 非强制时跳过 mtime 未变的文件
        if !force && db::file_mtime(&tx, &rel) == Some(mtime) {
            continue;
        }
        let bytes = match std::fs::read(path) {
            Ok(b) => b,
            Err(_) => {
                db::record_parse_issue(&tx, &rel, &ext, "read_failed")?;
                continue;
            }
        };
        if let Some(parsed) = indexer.parse(&ext, &bytes) {
            let source = String::from_utf8_lossy(&bytes);
            db::upsert_file(&tx, &rel, mtime, &parsed, &source)?;
        } else {
            // 可索引扩展名但解析无果：通常是该语言 tags 配置构建失败（索引盲区）
            db::record_parse_issue(&tx, &rel, &ext, "parse_failed_or_lang_unsupported")?;
        }
    }
    // 清理库中本次未见到的文件（已删除或新被忽略）
    for rel in db::all_relpaths(&tx)? {
        if !seen.contains(&rel) {
            db::delete_file(&tx, &rel)?;
        }
    }
    let _ = db::set_meta(&tx, "last_scan_ms", &now_ms().to_string());
    tx.commit().map_err(|e| e.to_string())?;
    // 全部文件入库后再解析导入指向（需要全量文件集合）
    db::resolve_imports(&conn, root)?;
    Ok(db::counts(&conn))
}

fn update_files(indexer: &mut parse::Indexer, root: &str, paths: &[PathBuf], status: &StatusMap) {
    let db_path = db::db_path_for(root);
    // 未建库则忽略增量（等首次全量）
    if !db_path.exists() {
        return;
    }
    let result = (|| -> Result<(), String> {
        let mut conn = db::open_rw(&db_path)?;
        let tx = conn.transaction().map_err(|e| e.to_string())?;
        for path in paths {
            if is_noise(path) {
                continue;
            }
            let rel = rel_path(root, path);
            if path.is_file() {
                let ext = ext_of(path);
                if !parse::is_indexable(&ext) {
                    continue;
                }
                let meta = match path.metadata() {
                    Ok(m) => m,
                    Err(_) => continue,
                };
                if meta.len() > MAX_FILE {
                    continue;
                }
                let mtime = mtime_ms(&meta);
                if let Ok(bytes) = std::fs::read(path) {
                    if let Some(parsed) = indexer.parse(&ext, &bytes) {
                        let source = String::from_utf8_lossy(&bytes);
                        db::upsert_file(&tx, &rel, mtime, &parsed, &source)?;
                    }
                }
            } else if !path.exists() {
                // 文件或整个目录被删除 → 删除该路径及其所有子路径的索引
                // （覆盖删除/重命名源端；监听器删目录通常只报目录本身）
                db::delete_path(&tx, &rel)?;
            }
            // else：仍存在的目录，忽略（其下文件各自有事件）
        }
        tx.commit().map_err(|e| e.to_string())?;
        // 重算导入指向：新增文件可能让既有未解析导入变为可解析
        db::resolve_imports(&conn, root)?;
        let (files, symbols) = db::counts(&conn);
        set_status(status, root, |s| {
            s.files = files;
            s.symbols = symbols;
        });
        Ok(())
    })();
    if let Err(e) = result {
        set_status(status, root, |s| s.last_error = Some(e));
    }
}

fn drop_index(root: &str, status: &StatusMap) {
    let db_path = db::db_path_for(root);
    let _ = std::fs::remove_file(&db_path);
    let _ = std::fs::remove_file(db_path.with_extension("db-wal"));
    let _ = std::fs::remove_file(db_path.with_extension("db-shm"));
    status.lock().unwrap().remove(root);
}

/// 清理孤儿索引库：磁盘上对应不到在册项目的 .db 删除
pub fn gc_orphans(known_roots: &[String]) {
    let known: HashSet<PathBuf> = known_roots.iter().map(|r| db::db_path_for(r)).collect();
    let dir = db::index_dir();
    if let Ok(rd) = std::fs::read_dir(&dir) {
        for e in rd.flatten() {
            let p = e.path();
            if p.extension().and_then(|x| x.to_str()) == Some("db") && !known.contains(&p) {
                let _ = std::fs::remove_file(&p);
                let _ = std::fs::remove_file(p.with_extension("db-wal"));
                let _ = std::fs::remove_file(p.with_extension("db-shm"));
            }
        }
    }
}

/// 窗口重新聚焦时的兜底对账：notify 在 git 批量操作下可能丢事件且不自愈，
/// 聚焦时对所有项目跑一次 mtime 对账 scan（只重解析变化文件，开销小）。节流 30s。
pub fn reconcile_on_focus(app: &AppHandle) {
    let state = app.state::<crate::AppState>();
    let (enabled, roots) = {
        let cfg = state.config.lock().unwrap();
        (
            cfg.settings.index_enabled,
            cfg.projects.iter().map(|p| p.path.clone()).collect::<Vec<_>>(),
        )
    };
    if !enabled {
        return;
    }
    let index = app.state::<IndexState>();
    {
        let mut last = index.last_reconcile.lock().unwrap();
        let now = now_ms();
        if now.saturating_sub(*last) < 30_000 {
            return;
        }
        *last = now;
    }
    for root in &roots {
        index.scan(root);
    }
}

/// 文件变动回调（由 watch.rs 调用）：开关开启时把变动路径交给增量更新
pub fn on_files_changed(app: &AppHandle, root: &str, paths: Vec<PathBuf>) {
    if paths.is_empty() {
        return;
    }
    let enabled = app
        .state::<crate::AppState>()
        .config
        .lock()
        .unwrap()
        .settings
        .index_enabled;
    if !enabled {
        return;
    }
    app.state::<IndexState>().update(root, paths);
}

// ---------- MCP 服务器注册（写入 <项目>/.mcp.json，scope=project）----------

/// 在项目 .mcp.json 注册 ccgui-index 服务器（指向本 exe 的 --mcp-index 模式）
pub fn register_server(root: &str) -> Result<(), String> {
    let exe = std::env::current_exe()
        .map_err(|e| e.to_string())?
        .to_string_lossy()
        .into_owned();
    let json = serde_json::json!({
        "command": exe,
        "args": ["--mcp-index", "--project", root]
    })
    .to_string();
    crate::mcp::save("project", Some(root), "ccgui-index", &json)?;
    // 自动批准本项目的 ccgui-index：免去 Claude Code 首次的项目 MCP 信任提示，
    // 让索引开箱即用。批准失败不致命（用户仍可手动信任），不阻断注册。
    let _ = crate::mcp::set_project_mcp_enabled(root, "ccgui-index", true);
    Ok(())
}

/// 注销：仅在 .mcp.json 存在时删除该键，避免凭空创建配置文件
pub fn unregister_server(root: &str) -> Result<(), String> {
    if !Path::new(root).join(".mcp.json").exists() {
        return Ok(());
    }
    crate::mcp::delete("project", Some(root), "ccgui-index")?;
    // 同步撤销自动批准，保持 .mcp.json 与 settings.local.json 一致
    let _ = crate::mcp::set_project_mcp_enabled(root, "ccgui-index", false);
    Ok(())
}

// ---------- 辅助 ----------

fn is_noise(path: &Path) -> bool {
    path.components().any(|c| {
        matches!(
            c.as_os_str().to_str(),
            Some("node_modules") | Some(".git") | Some("target") | Some("dist") | Some("out")
        )
    })
}

fn ext_of(path: &Path) -> String {
    path.extension()
        .and_then(|e| e.to_str())
        .unwrap_or("")
        .to_ascii_lowercase()
}

fn rel_path(root: &str, path: &Path) -> String {
    path.strip_prefix(root)
        .unwrap_or(path)
        .to_string_lossy()
        .replace('\\', "/")
}

fn mtime_ms(meta: &std::fs::Metadata) -> u64 {
    meta.modified()
        .ok()
        .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
        .map(|d| d.as_millis() as u64)
        .unwrap_or(0)
}

fn now_ms() -> u64 {
    crate::store::now_ms()
}

fn set_status(status: &StatusMap, root: &str, f: impl FnOnce(&mut ProjectStatus)) {
    let mut map = status.lock().unwrap();
    f(map.entry(root.to_string()).or_default());
}
