use crate::{
    core, dashboard, doctor, heatmap, hook_handlers, mcp_stdio, report, setup, shell, status,
    token_report, tools, tui, uninstall,
};
use anyhow::Result;

pub fn run() {
    let mut args: Vec<String> = std::env::args().collect();

    // On Linux, if the binary was replaced while running, systemd may write
    // the path with " (deleted)" suffix into ExecStart, causing "(deleted)"
    // to appear as an argument. Strip it defensively.
    if args.get(1).is_some_and(|a| a == "(deleted)") {
        args.remove(1);
    }

    let enters_mcp = args.len() == 1 || args.get(1).is_some_and(|a| a == "mcp");
    if !enters_mcp {
        crate::core::logging::init_logging();
    }

    if args.len() > 1 {
        let rest = args[2..].to_vec();

        match args[1].as_str() {
            "-c" | "exec" => {
                let raw = rest.first().is_some_and(|a| a == "--raw");
                let cmd_args = if raw { &args[3..] } else { &args[2..] };
                let command = if cmd_args.len() == 1 {
                    cmd_args[0].clone()
                } else {
                    shell::join_command(cmd_args)
                };
                if std::env::var("LEAN_CTX_ACTIVE").is_ok()
                    || std::env::var("LEAN_CTX_DISABLED").is_ok()
                {
                    passthrough(&command);
                }
                if raw {
                    std::env::set_var("LEAN_CTX_RAW", "1");
                } else {
                    std::env::set_var("LEAN_CTX_COMPRESS", "1");
                }
                let code = shell::exec(&command);
                core::stats::flush();
                core::heatmap::flush();
                std::process::exit(code);
            }
            "-t" | "--track" => {
                let cmd_args = &args[2..];
                let code = if cmd_args.len() > 1 {
                    shell::exec_argv(cmd_args)
                } else {
                    let command = cmd_args[0].clone();
                    if std::env::var("LEAN_CTX_ACTIVE").is_ok()
                        || std::env::var("LEAN_CTX_DISABLED").is_ok()
                    {
                        passthrough(&command);
                    }
                    shell::exec(&command)
                };
                core::stats::flush();
                core::heatmap::flush();
                std::process::exit(code);
            }
            "shell" | "--shell" => {
                shell::interactive();
                return;
            }
            "gain" => {
                if rest.iter().any(|a| a == "--reset") {
                    core::stats::reset_all();
                    println!("Stats reset. All token savings data cleared.");
                    return;
                }
                if rest.iter().any(|a| a == "--live" || a == "--watch") {
                    core::stats::gain_live();
                    return;
                }
                let model = rest.iter().enumerate().find_map(|(i, a)| {
                    if let Some(v) = a.strip_prefix("--model=") {
                        return Some(v.to_string());
                    }
                    if a == "--model" {
                        return rest.get(i + 1).cloned();
                    }
                    None
                });
                let period = rest
                    .iter()
                    .enumerate()
                    .find_map(|(i, a)| {
                        if let Some(v) = a.strip_prefix("--period=") {
                            return Some(v.to_string());
                        }
                        if a == "--period" {
                            return rest.get(i + 1).cloned();
                        }
                        None
                    })
                    .unwrap_or_else(|| "all".to_string());
                let limit = rest
                    .iter()
                    .enumerate()
                    .find_map(|(i, a)| {
                        if let Some(v) = a.strip_prefix("--limit=") {
                            return v.parse::<usize>().ok();
                        }
                        if a == "--limit" {
                            return rest.get(i + 1).and_then(|v| v.parse::<usize>().ok());
                        }
                        None
                    })
                    .unwrap_or(10);

                if rest.iter().any(|a| a == "--graph") {
                    println!("{}", core::stats::format_gain_graph());
                } else if rest.iter().any(|a| a == "--daily") {
                    println!("{}", core::stats::format_gain_daily());
                } else if rest.iter().any(|a| a == "--json") {
                    println!(
                        "{}",
                        tools::ctx_gain::handle(
                            "json",
                            Some(&period),
                            model.as_deref(),
                            Some(limit)
                        )
                    );
                } else if rest.iter().any(|a| a == "--score") {
                    println!(
                        "{}",
                        tools::ctx_gain::handle("score", None, model.as_deref(), Some(limit))
                    );
                } else if rest.iter().any(|a| a == "--cost") {
                    println!(
                        "{}",
                        tools::ctx_gain::handle("cost", None, model.as_deref(), Some(limit))
                    );
                } else if rest.iter().any(|a| a == "--tasks") {
                    println!(
                        "{}",
                        tools::ctx_gain::handle("tasks", None, None, Some(limit))
                    );
                } else if rest.iter().any(|a| a == "--agents") {
                    println!(
                        "{}",
                        tools::ctx_gain::handle("agents", None, None, Some(limit))
                    );
                } else if rest.iter().any(|a| a == "--heatmap") {
                    println!(
                        "{}",
                        tools::ctx_gain::handle("heatmap", None, None, Some(limit))
                    );
                } else if rest.iter().any(|a| a == "--wrapped") {
                    println!(
                        "{}",
                        tools::ctx_gain::handle(
                            "wrapped",
                            Some(&period),
                            model.as_deref(),
                            Some(limit)
                        )
                    );
                } else if rest.iter().any(|a| a == "--pipeline") {
                    let stats_path = dirs::home_dir()
                        .unwrap_or_default()
                        .join(".lean-ctx")
                        .join("pipeline_stats.json");
                    if let Ok(data) = std::fs::read_to_string(&stats_path) {
                        if let Ok(stats) =
                            serde_json::from_str::<core::pipeline::PipelineStats>(&data)
                        {
                            println!("{}", stats.format_summary());
                        } else {
                            println!("No pipeline stats available yet (corrupt data).");
                        }
                    } else {
                        println!(
                            "No pipeline stats available yet. Use MCP tools to generate data."
                        );
                    }
                } else if rest.iter().any(|a| a == "--deep") {
                    println!(
                        "{}\n{}\n{}\n{}\n{}",
                        tools::ctx_gain::handle("report", None, model.as_deref(), Some(limit)),
                        tools::ctx_gain::handle("tasks", None, None, Some(limit)),
                        tools::ctx_gain::handle("cost", None, model.as_deref(), Some(limit)),
                        tools::ctx_gain::handle("agents", None, None, Some(limit)),
                        tools::ctx_gain::handle("heatmap", None, None, Some(limit))
                    );
                } else {
                    println!("{}", core::stats::format_gain());
                }
                return;
            }
            "token-report" | "report-tokens" => {
                let code = token_report::run_cli(&rest);
                if code != 0 {
                    std::process::exit(code);
                }
                return;
            }
            "pack" => {
                crate::cli::cmd_pack(&rest);
                return;
            }
            "proof" => {
                crate::cli::cmd_proof(&rest);
                return;
            }
            "verify" => {
                crate::cli::cmd_verify(&rest);
                return;
            }
            "audit" => {
                println!("{}", crate::cli::audit_report::generate_report());
                return;
            }
            "instructions" => {
                crate::cli::cmd_instructions(&rest);
                return;
            }
            "index" => {
                crate::cli::cmd_index(&rest);
                return;
            }
            "cep" => {
                println!("{}", tools::ctx_gain::handle("score", None, None, Some(10)));
                return;
            }
            "dashboard" => {
                if rest.iter().any(|a| a == "--help" || a == "-h") {
                    println!("Usage: lean-ctx dashboard [--port=N] [--host=H] [--project=PATH]");
                    println!("Examples:");
                    println!("  lean-ctx dashboard");
                    println!("  lean-ctx dashboard --port=3333");
                    println!("  lean-ctx dashboard --host=0.0.0.0");
                    return;
                }
                let port = rest
                    .iter()
                    .find_map(|p| p.strip_prefix("--port=").or_else(|| p.strip_prefix("-p=")))
                    .and_then(|p| p.parse().ok());
                let host = rest
                    .iter()
                    .find_map(|p| p.strip_prefix("--host=").or_else(|| p.strip_prefix("-H=")))
                    .map(String::from);
                let project = rest
                    .iter()
                    .find_map(|p| p.strip_prefix("--project="))
                    .map(String::from);
                if let Some(ref p) = project {
                    std::env::set_var("LEAN_CTX_DASHBOARD_PROJECT", p);
                }
                spawn_proxy_if_needed();
                run_async(dashboard::start(port, host));
                return;
            }
            "team" => {
                let sub = rest.first().map_or("help", std::string::String::as_str);
                match sub {
                    "serve" => {
                        #[cfg(feature = "team-server")]
                        {
                            let cfg_path = rest
                                .iter()
                                .enumerate()
                                .find_map(|(i, a)| {
                                    if let Some(v) = a.strip_prefix("--config=") {
                                        return Some(v.to_string());
                                    }
                                    if a == "--config" {
                                        return rest.get(i + 1).cloned();
                                    }
                                    None
                                })
                                .unwrap_or_default();

                            if cfg_path.trim().is_empty() {
                                eprintln!("Usage: lean-ctx team serve --config <path>");
                                std::process::exit(1);
                            }

                            let cfg = crate::http_server::team::TeamServerConfig::load(
                                std::path::Path::new(&cfg_path),
                            )
                            .unwrap_or_else(|e| {
                                eprintln!("Invalid team config: {e}");
                                std::process::exit(1);
                            });

                            if let Err(e) = run_async(crate::http_server::team::serve_team(cfg)) {
                                tracing::error!("Team server error: {e}");
                                std::process::exit(1);
                            }
                            return;
                        }
                        #[cfg(not(feature = "team-server"))]
                        {
                            eprintln!("lean-ctx team serve is not available in this build");
                            std::process::exit(1);
                        }
                    }
                    "token" => {
                        let action = rest.get(1).map_or("help", std::string::String::as_str);
                        if action == "create" {
                            #[cfg(feature = "team-server")]
                            {
                                let args = &rest[2..];
                                let cfg_path = args
                                    .iter()
                                    .enumerate()
                                    .find_map(|(i, a)| {
                                        if let Some(v) = a.strip_prefix("--config=") {
                                            return Some(v.to_string());
                                        }
                                        if a == "--config" {
                                            return args.get(i + 1).cloned();
                                        }
                                        None
                                    })
                                    .unwrap_or_default();
                                let token_id = args
                                    .iter()
                                    .enumerate()
                                    .find_map(|(i, a)| {
                                        if let Some(v) = a.strip_prefix("--id=") {
                                            return Some(v.to_string());
                                        }
                                        if a == "--id" {
                                            return args.get(i + 1).cloned();
                                        }
                                        None
                                    })
                                    .unwrap_or_default();
                                let scopes_csv = args
                                    .iter()
                                    .enumerate()
                                    .find_map(|(i, a)| {
                                        if let Some(v) = a.strip_prefix("--scopes=") {
                                            return Some(v.to_string());
                                        }
                                        if let Some(v) = a.strip_prefix("--scope=") {
                                            return Some(v.to_string());
                                        }
                                        if a == "--scopes" || a == "--scope" {
                                            return args.get(i + 1).cloned();
                                        }
                                        None
                                    })
                                    .unwrap_or_default();

                                if cfg_path.trim().is_empty()
                                    || token_id.trim().is_empty()
                                    || scopes_csv.trim().is_empty()
                                {
                                    eprintln!(
                                            "Usage: lean-ctx team token create --config <path> --id <id> --scopes <csv>"
                                        );
                                    std::process::exit(1);
                                }

                                let cfg_p = std::path::PathBuf::from(&cfg_path);
                                let mut cfg = crate::http_server::team::TeamServerConfig::load(
                                    cfg_p.as_path(),
                                )
                                .unwrap_or_else(|e| {
                                    eprintln!("Invalid team config: {e}");
                                    std::process::exit(1);
                                });

                                let mut scopes = Vec::new();
                                for part in scopes_csv.split(',') {
                                    let p = part.trim().to_ascii_lowercase();
                                    if p.is_empty() {
                                        continue;
                                    }
                                    let scope = match p.as_str() {
                                        "search" => crate::http_server::team::TeamScope::Search,
                                        "graph" => crate::http_server::team::TeamScope::Graph,
                                        "artifacts" => {
                                            crate::http_server::team::TeamScope::Artifacts
                                        }
                                        "index" => crate::http_server::team::TeamScope::Index,
                                        "events" => crate::http_server::team::TeamScope::Events,
                                        "sessionmutations" | "session_mutations" => {
                                            crate::http_server::team::TeamScope::SessionMutations
                                        }
                                        "knowledge" => {
                                            crate::http_server::team::TeamScope::Knowledge
                                        }
                                        "audit" => crate::http_server::team::TeamScope::Audit,
                                        _ => {
                                            eprintln!("Unknown scope: {p}. Valid: search, graph, artifacts, index, events, sessionmutations, knowledge, audit");
                                            std::process::exit(1);
                                        }
                                    };
                                    if !scopes.contains(&scope) {
                                        scopes.push(scope);
                                    }
                                }
                                if scopes.is_empty() {
                                    eprintln!("At least 1 scope is required");
                                    std::process::exit(1);
                                }

                                let (token, hash) = crate::http_server::team::create_token()
                                    .unwrap_or_else(|e| {
                                        eprintln!("Token generation failed: {e}");
                                        std::process::exit(1);
                                    });

                                cfg.tokens.push(crate::http_server::team::TeamTokenConfig {
                                    id: token_id,
                                    sha256_hex: hash,
                                    scopes,
                                });

                                cfg.save(cfg_p.as_path()).unwrap_or_else(|e| {
                                    eprintln!("Failed to write config: {e}");
                                    std::process::exit(1);
                                });

                                println!("{token}");
                                return;
                            }

                            #[cfg(not(feature = "team-server"))]
                            {
                                eprintln!("lean-ctx team token is not available in this build");
                                std::process::exit(1);
                            }
                        }
                        eprintln!(
                            "Usage: lean-ctx team token create --config <path> --id <id> --scopes <csv>"
                        );
                        std::process::exit(1);
                    }
                    "sync" => {
                        #[cfg(feature = "team-server")]
                        {
                            let args = &rest[1..];
                            let cfg_path = args
                                .iter()
                                .enumerate()
                                .find_map(|(i, a)| {
                                    if let Some(v) = a.strip_prefix("--config=") {
                                        return Some(v.to_string());
                                    }
                                    if a == "--config" {
                                        return args.get(i + 1).cloned();
                                    }
                                    None
                                })
                                .unwrap_or_default();
                            if cfg_path.trim().is_empty() {
                                eprintln!(
                                    "Usage: lean-ctx team sync --config <path> [--workspace <id>]"
                                );
                                std::process::exit(1);
                            }
                            let only_ws = args.iter().enumerate().find_map(|(i, a)| {
                                if let Some(v) = a.strip_prefix("--workspace=") {
                                    return Some(v.to_string());
                                }
                                if let Some(v) = a.strip_prefix("--workspace-id=") {
                                    return Some(v.to_string());
                                }
                                if a == "--workspace" || a == "--workspace-id" {
                                    return args.get(i + 1).cloned();
                                }
                                None
                            });

                            let cfg = crate::http_server::team::TeamServerConfig::load(
                                std::path::Path::new(&cfg_path),
                            )
                            .unwrap_or_else(|e| {
                                eprintln!("Invalid team config: {e}");
                                std::process::exit(1);
                            });

                            for ws in &cfg.workspaces {
                                if let Some(ref only) = only_ws {
                                    if ws.id != *only {
                                        continue;
                                    }
                                }
                                let git_dir = ws.root.join(".git");
                                if !git_dir.exists() {
                                    eprintln!(
                                        "workspace '{}' root is not a git repo: {}",
                                        ws.id,
                                        ws.root.display()
                                    );
                                    std::process::exit(1);
                                }
                                let status = std::process::Command::new("git")
                                    .arg("-C")
                                    .arg(&ws.root)
                                    .args(["fetch", "--all", "--prune"])
                                    .status()
                                    .unwrap_or_else(|e| {
                                        eprintln!(
                                            "git fetch failed for workspace '{}': {e}",
                                            ws.id
                                        );
                                        std::process::exit(1);
                                    });
                                if !status.success() {
                                    eprintln!(
                                        "git fetch failed for workspace '{}' (exit={})",
                                        ws.id,
                                        status.code().unwrap_or(1)
                                    );
                                    std::process::exit(1);
                                }
                            }
                            return;
                        }
                        #[cfg(not(feature = "team-server"))]
                        {
                            eprintln!("lean-ctx team sync is not available in this build");
                            std::process::exit(1);
                        }
                    }
                    _ => {
                        eprintln!(
                            "Usage:\n  lean-ctx team serve --config <path>\n  lean-ctx team token create --config <path> --id <id> --scopes <csv>\n  lean-ctx team sync --config <path> [--workspace <id>]"
                        );
                        std::process::exit(1);
                    }
                }
            }
            "serve" => {
                #[cfg(feature = "http-server")]
                {
                    let mut cfg = crate::http_server::HttpServerConfig::default();
                    let mut daemon_mode = false;
                    let mut stop_mode = false;
                    let mut status_mode = false;
                    let mut foreground_daemon = false;
                    let mut i = 0;
                    while i < rest.len() {
                        match rest[i].as_str() {
                            "--daemon" | "-d" => daemon_mode = true,
                            "--stop" => stop_mode = true,
                            "--status" => status_mode = true,
                            "--_foreground-daemon" => foreground_daemon = true,
                            "--host" | "-H" => {
                                i += 1;
                                if i < rest.len() {
                                    cfg.host.clone_from(&rest[i]);
                                }
                            }
                            arg if arg.starts_with("--host=") => {
                                cfg.host = arg["--host=".len()..].to_string();
                            }
                            "--port" | "-p" => {
                                i += 1;
                                if i < rest.len() {
                                    if let Ok(p) = rest[i].parse::<u16>() {
                                        cfg.port = p;
                                    }
                                }
                            }
                            arg if arg.starts_with("--port=") => {
                                if let Ok(p) = arg["--port=".len()..].parse::<u16>() {
                                    cfg.port = p;
                                }
                            }
                            "--project-root" => {
                                i += 1;
                                if i < rest.len() {
                                    cfg.project_root = std::path::PathBuf::from(&rest[i]);
                                }
                            }
                            arg if arg.starts_with("--project-root=") => {
                                cfg.project_root =
                                    std::path::PathBuf::from(&arg["--project-root=".len()..]);
                            }
                            "--auth-token" => {
                                i += 1;
                                if i < rest.len() {
                                    cfg.auth_token = Some(rest[i].clone());
                                }
                            }
                            arg if arg.starts_with("--auth-token=") => {
                                cfg.auth_token = Some(arg["--auth-token=".len()..].to_string());
                            }
                            "--stateful" => cfg.stateful_mode = true,
                            "--stateless" => cfg.stateful_mode = false,
                            "--json" => cfg.json_response = true,
                            "--sse" => cfg.json_response = false,
                            "--disable-host-check" => cfg.disable_host_check = true,
                            "--allowed-host" => {
                                i += 1;
                                if i < rest.len() {
                                    cfg.allowed_hosts.push(rest[i].clone());
                                }
                            }
                            arg if arg.starts_with("--allowed-host=") => {
                                cfg.allowed_hosts
                                    .push(arg["--allowed-host=".len()..].to_string());
                            }
                            "--max-body-bytes" => {
                                i += 1;
                                if i < rest.len() {
                                    if let Ok(n) = rest[i].parse::<usize>() {
                                        cfg.max_body_bytes = n;
                                    }
                                }
                            }
                            arg if arg.starts_with("--max-body-bytes=") => {
                                if let Ok(n) = arg["--max-body-bytes=".len()..].parse::<usize>() {
                                    cfg.max_body_bytes = n;
                                }
                            }
                            "--max-concurrency" => {
                                i += 1;
                                if i < rest.len() {
                                    if let Ok(n) = rest[i].parse::<usize>() {
                                        cfg.max_concurrency = n;
                                    }
                                }
                            }
                            arg if arg.starts_with("--max-concurrency=") => {
                                if let Ok(n) = arg["--max-concurrency=".len()..].parse::<usize>() {
                                    cfg.max_concurrency = n;
                                }
                            }
                            "--max-rps" => {
                                i += 1;
                                if i < rest.len() {
                                    if let Ok(n) = rest[i].parse::<u32>() {
                                        cfg.max_rps = n;
                                    }
                                }
                            }
                            arg if arg.starts_with("--max-rps=") => {
                                if let Ok(n) = arg["--max-rps=".len()..].parse::<u32>() {
                                    cfg.max_rps = n;
                                }
                            }
                            "--rate-burst" => {
                                i += 1;
                                if i < rest.len() {
                                    if let Ok(n) = rest[i].parse::<u32>() {
                                        cfg.rate_burst = n;
                                    }
                                }
                            }
                            arg if arg.starts_with("--rate-burst=") => {
                                if let Ok(n) = arg["--rate-burst=".len()..].parse::<u32>() {
                                    cfg.rate_burst = n;
                                }
                            }
                            "--request-timeout-ms" => {
                                i += 1;
                                if i < rest.len() {
                                    if let Ok(n) = rest[i].parse::<u64>() {
                                        cfg.request_timeout_ms = n;
                                    }
                                }
                            }
                            arg if arg.starts_with("--request-timeout-ms=") => {
                                if let Ok(n) = arg["--request-timeout-ms=".len()..].parse::<u64>() {
                                    cfg.request_timeout_ms = n;
                                }
                            }
                            "--help" | "-h" => {
                                eprintln!(
                                    "Usage: lean-ctx serve [--host H] [--port N] [--project-root DIR] [--daemon] [--stop] [--status]\\n\\
                                     \\n\\
                                     Options:\\n\\
                                       --daemon, -d          Start as background daemon (UDS)\\n\\
                                       --stop                Stop running daemon\\n\\
                                       --status              Show daemon status\\n\\
                                       --host, -H            Bind host (default: 127.0.0.1)\\n\\
                                       --port, -p            Bind port (default: 8080)\\n\\
                                       --project-root        Resolve relative paths against this root (default: cwd)\\n\\
                                       --auth-token          Require Authorization: Bearer <token> (required for non-loopback binds)\\n\\
                                       --stateful/--stateless  Streamable HTTP session mode (default: stateless)\\n\\
                                       --json/--sse          Response framing in stateless mode (default: json)\\n\\
                                       --max-body-bytes      Max request body size in bytes (default: 2097152)\\n\\
                                       --max-concurrency     Max concurrent requests (default: 32)\\n\\
                                       --max-rps             Max requests/sec (global, default: 50)\\n\\
                                       --rate-burst          Rate limiter burst (global, default: 100)\\n\\
                                       --request-timeout-ms  REST tool-call timeout (default: 30000)\\n\\
                                       --allowed-host        Add allowed Host header (repeatable)\\n\\
                                       --disable-host-check  Disable Host header validation (unsafe)"
                                );
                                return;
                            }
                            _ => {}
                        }
                        i += 1;
                    }

                    if stop_mode {
                        if let Err(e) = crate::daemon::stop_daemon() {
                            eprintln!("Error: {e}");
                            std::process::exit(1);
                        }
                        return;
                    }

                    if status_mode {
                        println!("{}", crate::daemon::daemon_status());
                        return;
                    }

                    if daemon_mode {
                        if let Err(e) = crate::daemon::start_daemon(&rest) {
                            eprintln!("Error: {e}");
                            std::process::exit(1);
                        }
                        return;
                    }

                    if foreground_daemon {
                        if let Err(e) = crate::daemon::init_foreground_daemon() {
                            eprintln!("Error writing PID file: {e}");
                            std::process::exit(1);
                        }
                        let addr = crate::daemon::daemon_addr();
                        if let Err(e) = run_async(crate::http_server::serve_ipc(cfg.clone(), addr))
                        {
                            tracing::error!("Daemon server error: {e}");
                            crate::daemon::cleanup_daemon_files();
                            std::process::exit(1);
                        }
                        crate::daemon::cleanup_daemon_files();
                        return;
                    }

                    if cfg.auth_token.is_none() {
                        if let Ok(v) = std::env::var("LEAN_CTX_HTTP_TOKEN") {
                            if !v.trim().is_empty() {
                                cfg.auth_token = Some(v);
                            }
                        }
                    }

                    if let Err(e) = run_async(crate::http_server::serve(cfg)) {
                        tracing::error!("HTTP server error: {e}");
                        std::process::exit(1);
                    }
                    return;
                }
                #[cfg(not(feature = "http-server"))]
                {
                    eprintln!("lean-ctx serve is not available in this build");
                    std::process::exit(1);
                }
            }
            "watch" => {
                if rest.iter().any(|a| a == "--help" || a == "-h") {
                    println!("Usage: lean-ctx watch");
                    println!("  Live TUI dashboard (real-time event stream).");
                    return;
                }
                if let Err(e) = tui::run() {
                    tracing::error!("TUI error: {e}");
                    std::process::exit(1);
                }
                return;
            }
            "proxy" => {
                #[cfg(feature = "http-server")]
                {
                    let sub = rest.first().map_or("help", std::string::String::as_str);
                    match sub {
                        "start" => {
                            let port: u16 = rest
                                .iter()
                                .find_map(|p| {
                                    p.strip_prefix("--port=").or_else(|| p.strip_prefix("-p="))
                                })
                                .and_then(|p| p.parse().ok())
                                .unwrap_or_else(crate::proxy_setup::default_port);
                            let autostart = rest.iter().any(|a| a == "--autostart");
                            if autostart {
                                crate::proxy_autostart::install(port, false);
                                return;
                            }
                            if let Err(e) = run_async(crate::proxy::start_proxy(port)) {
                                tracing::error!("Proxy error: {e}");
                                std::process::exit(1);
                            }
                        }
                        "stop" => {
                            let port: u16 = rest
                                .iter()
                                .find_map(|p| p.strip_prefix("--port="))
                                .and_then(|p| p.parse().ok())
                                .unwrap_or_else(crate::proxy_setup::default_port);
                            let health_url = format!("http://127.0.0.1:{port}/health");
                            match ureq::get(&health_url).call() {
                                Ok(resp) => {
                                    if let Ok(body) = resp.into_body().read_to_string() {
                                        if let Some(pid_str) = body
                                            .split("pid\":")
                                            .nth(1)
                                            .and_then(|s| s.split([',', '}']).next())
                                        {
                                            if let Ok(pid) = pid_str.trim().parse::<u32>() {
                                                let _ =
                                                    crate::ipc::process::terminate_gracefully(pid);
                                                std::thread::sleep(
                                                    std::time::Duration::from_millis(500),
                                                );
                                                if crate::ipc::process::is_alive(pid) {
                                                    let _ = crate::ipc::process::force_kill(pid);
                                                }
                                                println!(
                                                    "Proxy on port {port} stopped (PID {pid})."
                                                );
                                                return;
                                            }
                                        }
                                    }
                                    println!("Proxy on port {port} running but could not parse PID. Use `lean-ctx stop` to kill all.");
                                }
                                Err(_) => {
                                    println!("No proxy running on port {port}.");
                                }
                            }
                        }
                        "status" => {
                            let port: u16 = rest
                                .iter()
                                .find_map(|p| p.strip_prefix("--port="))
                                .and_then(|p| p.parse().ok())
                                .unwrap_or_else(crate::proxy_setup::default_port);
                            let cfg = crate::core::config::Config::load();
                            println!("lean-ctx proxy:");
                            match cfg.proxy_enabled {
                                Some(true) => println!("  Config:  enabled"),
                                Some(false) => println!("  Config:  disabled"),
                                None => println!("  Config:  undecided (not yet configured)"),
                            }
                            println!("  Port:    {port}");
                            if let Ok(resp) =
                                ureq::get(&format!("http://127.0.0.1:{port}/status")).call()
                            {
                                let body = resp.into_body().read_to_string().unwrap_or_default();
                                println!("  Process: running");
                                if let Ok(v) = serde_json::from_str::<serde_json::Value>(&body) {
                                    println!("  Requests:    {}", v["requests_total"]);
                                    println!("  Compressed:  {}", v["requests_compressed"]);
                                    println!("  Tokens saved: {}", v["tokens_saved"]);
                                    println!(
                                        "  Compression: {}%",
                                        v["compression_ratio_pct"].as_str().unwrap_or("0.0")
                                    );
                                }
                            } else {
                                println!("  Process: not running");
                            }
                            if cfg.proxy_enabled == Some(false) || cfg.proxy_enabled.is_none() {
                                println!();
                                println!("  Enable: lean-ctx proxy enable");

                                let home = dirs::home_dir().unwrap_or_default();
                                if crate::proxy_setup::has_stale_proxy_url(&home) {
                                    println!();
                                    println!("  \x1b[33m⚠ WARNING: Claude Code ANTHROPIC_BASE_URL points to the local proxy,\x1b[0m");
                                    println!("  \x1b[33m  but proxy is not enabled. This causes 401 auth failures.\x1b[0m");
                                    println!("  Fix:  lean-ctx proxy cleanup   (remove stale URL)");
                                    println!("        lean-ctx proxy enable    (enable the proxy)");
                                }
                            }
                        }
                        "enable" => {
                            let force = rest.iter().any(|a| a == "--force");
                            let mut cfg = crate::core::config::Config::load();
                            cfg.proxy_enabled = Some(true);
                            let _ = cfg.save();

                            let port = crate::proxy_setup::default_port();
                            crate::proxy_autostart::install(port, false);
                            std::thread::sleep(std::time::Duration::from_millis(500));

                            let home = dirs::home_dir().unwrap_or_default();
                            crate::proxy_setup::install_proxy_env_unchecked(
                                &home, port, false, force,
                            );
                            println!("\x1b[32m✓\x1b[0m Proxy enabled on port {port}. LLM requests will be compressed.");
                        }
                        "disable" => {
                            let mut cfg = crate::core::config::Config::load();
                            cfg.proxy_enabled = Some(false);
                            let _ = cfg.save();

                            crate::proxy_autostart::uninstall(false);
                            let home = dirs::home_dir().unwrap_or_default();
                            crate::proxy_setup::uninstall_proxy_env(&home, false);

                            println!(
                                "\x1b[32m✓\x1b[0m Proxy disabled. Original endpoint restored."
                            );
                            println!("  Re-enable anytime: lean-ctx proxy enable");
                        }
                        "cleanup" => {
                            let home = dirs::home_dir().unwrap_or_default();
                            let removed = crate::proxy_setup::cleanup_stale_proxy_env(&home);
                            if removed > 0 {
                                println!(
                                    "\x1b[32m✓\x1b[0m Cleaned up {removed} stale proxy URL(s)."
                                );
                                println!("  Restart your AI tool for changes to take effect.");
                            } else {
                                println!("  No stale proxy URLs found. Nothing to clean up.");
                            }
                        }
                        _ => {
                            println!("Usage: lean-ctx proxy <start|stop|status|enable|disable|cleanup> [--port=4444]");
                        }
                    }
                    return;
                }
                #[cfg(not(feature = "http-server"))]
                {
                    eprintln!("lean-ctx proxy is not available in this build");
                    std::process::exit(1);
                }
            }
            "init" => {
                super::cmd_init(&rest);
                return;
            }
            "setup" => {
                let non_interactive = rest.iter().any(|a| a == "--non-interactive");
                let yes = rest.iter().any(|a| a == "--yes" || a == "-y");
                let fix = rest.iter().any(|a| a == "--fix");
                let json = rest.iter().any(|a| a == "--json");
                let no_auto_approve = rest.iter().any(|a| a == "--no-auto-approve");

                if non_interactive || fix || json || yes {
                    let opts = setup::SetupOptions {
                        non_interactive,
                        yes,
                        fix,
                        json,
                        no_auto_approve,
                        ..Default::default()
                    };
                    match setup::run_setup_with_options(opts) {
                        Ok(report) => {
                            if json {
                                println!(
                                    "{}",
                                    serde_json::to_string_pretty(&report)
                                        .unwrap_or_else(|_| "{}".to_string())
                                );
                            }
                            if !report.success {
                                std::process::exit(1);
                            }
                        }
                        Err(e) => {
                            eprintln!("{e}");
                            std::process::exit(1);
                        }
                    }
                } else {
                    setup::run_setup();
                }
                return;
            }
            "install" => {
                let repair = rest.iter().any(|a| a == "--repair" || a == "--fix");
                let json = rest.iter().any(|a| a == "--json");
                if !repair {
                    eprintln!("Usage: lean-ctx install --repair [--json]");
                    std::process::exit(1);
                }
                let opts = setup::SetupOptions {
                    non_interactive: true,
                    yes: true,
                    fix: true,
                    json,
                    ..Default::default()
                };
                match setup::run_setup_with_options(opts) {
                    Ok(report) => {
                        if json {
                            println!(
                                "{}",
                                serde_json::to_string_pretty(&report)
                                    .unwrap_or_else(|_| "{}".to_string())
                            );
                        }
                        if !report.success {
                            std::process::exit(1);
                        }
                    }
                    Err(e) => {
                        eprintln!("{e}");
                        std::process::exit(1);
                    }
                }
                return;
            }
            "bootstrap" => {
                let json = rest.iter().any(|a| a == "--json");
                let opts = setup::SetupOptions {
                    non_interactive: true,
                    yes: true,
                    fix: true,
                    json,
                    ..Default::default()
                };
                match setup::run_setup_with_options(opts) {
                    Ok(report) => {
                        if json {
                            println!(
                                "{}",
                                serde_json::to_string_pretty(&report)
                                    .unwrap_or_else(|_| "{}".to_string())
                            );
                        }
                        if !report.success {
                            std::process::exit(1);
                        }
                    }
                    Err(e) => {
                        eprintln!("{e}");
                        std::process::exit(1);
                    }
                }
                return;
            }
            "status" => {
                let code = status::run_cli(&rest);
                if code != 0 {
                    std::process::exit(code);
                }
                return;
            }
            "read" => {
                super::cmd_read(&rest);
                core::stats::flush();
                return;
            }
            "diff" => {
                super::cmd_diff(&rest);
                core::stats::flush();
                return;
            }
            "grep" => {
                super::cmd_grep(&rest);
                core::stats::flush();
                return;
            }
            "find" => {
                super::cmd_find(&rest);
                core::stats::flush();
                return;
            }
            "ls" => {
                super::cmd_ls(&rest);
                core::stats::flush();
                return;
            }
            "deps" => {
                super::cmd_deps(&rest);
                core::stats::flush();
                return;
            }
            "discover" => {
                super::cmd_discover(&rest);
                return;
            }
            "ghost" => {
                super::cmd_ghost(&rest);
                return;
            }
            "filter" => {
                super::cmd_filter(&rest);
                return;
            }
            "heatmap" => {
                heatmap::cmd_heatmap(&rest);
                return;
            }
            "graph" => {
                let sub = rest.first().map_or("build", std::string::String::as_str);
                match sub {
                    "build" => {
                        let root = rest.get(1).cloned().or_else(|| {
                            std::env::current_dir()
                                .ok()
                                .map(|p| p.to_string_lossy().to_string())
                        });
                        let root = root.unwrap_or_else(|| ".".to_string());
                        let index = core::graph_index::load_or_build(&root);
                        println!(
                            "Graph built: {} files, {} edges",
                            index.files.len(),
                            index.edges.len()
                        );
                    }
                    "export-html" => {
                        let mut root: Option<String> = None;
                        let mut out: Option<String> = None;
                        let mut max_nodes: usize = 2500;

                        let args = &rest[1..];
                        let mut i = 0usize;
                        while i < args.len() {
                            let a = args[i].as_str();
                            if let Some(v) = a.strip_prefix("--root=") {
                                root = Some(v.to_string());
                            } else if a == "--root" {
                                root = args.get(i + 1).cloned();
                                i += 1;
                            } else if let Some(v) = a.strip_prefix("--out=") {
                                out = Some(v.to_string());
                            } else if a == "--out" {
                                out = args.get(i + 1).cloned();
                                i += 1;
                            } else if let Some(v) = a.strip_prefix("--max-nodes=") {
                                max_nodes = v.parse::<usize>().unwrap_or(0);
                            } else if a == "--max-nodes" {
                                let v = args.get(i + 1).map_or("", String::as_str);
                                max_nodes = v.parse::<usize>().unwrap_or(0);
                                i += 1;
                            }
                            i += 1;
                        }

                        let root = root
                            .or_else(|| {
                                std::env::current_dir()
                                    .ok()
                                    .map(|p| p.to_string_lossy().to_string())
                            })
                            .unwrap_or_else(|| ".".to_string());
                        let Some(out) = out else {
                            eprintln!("Usage: lean-ctx graph export-html --out <path> [--root <path>] [--max-nodes <n>]");
                            std::process::exit(1);
                        };
                        if max_nodes == 0 {
                            eprintln!("--max-nodes must be >= 1");
                            std::process::exit(1);
                        }

                        core::graph_export::export_graph_html(
                            &root,
                            std::path::Path::new(&out),
                            max_nodes,
                        )
                        .unwrap_or_else(|e| {
                            eprintln!("graph export failed: {e}");
                            std::process::exit(1);
                        });
                        println!("{out}");
                    }
                    _ => {
                        eprintln!(
                            "Usage:\n  lean-ctx graph build [path]\n  lean-ctx graph export-html --out <path> [--root <path>] [--max-nodes <n>]"
                        );
                        std::process::exit(1);
                    }
                }
                return;
            }
            "smells" => {
                let action = rest.first().map_or("summary", String::as_str);
                let rule = rest.iter().enumerate().find_map(|(i, a)| {
                    if let Some(v) = a.strip_prefix("--rule=") {
                        return Some(v.to_string());
                    }
                    if a == "--rule" {
                        return rest.get(i + 1).cloned();
                    }
                    None
                });
                let path = rest.iter().enumerate().find_map(|(i, a)| {
                    if let Some(v) = a.strip_prefix("--path=") {
                        return Some(v.to_string());
                    }
                    if a == "--path" {
                        return rest.get(i + 1).cloned();
                    }
                    None
                });
                let root = rest
                    .iter()
                    .enumerate()
                    .find_map(|(i, a)| {
                        if let Some(v) = a.strip_prefix("--root=") {
                            return Some(v.to_string());
                        }
                        if a == "--root" {
                            return rest.get(i + 1).cloned();
                        }
                        None
                    })
                    .or_else(|| {
                        std::env::current_dir()
                            .ok()
                            .map(|p| p.to_string_lossy().to_string())
                    })
                    .unwrap_or_else(|| ".".to_string());
                let fmt = if rest.iter().any(|a| a == "--json") {
                    Some("json")
                } else {
                    None
                };
                println!(
                    "{}",
                    tools::ctx_smells::handle(action, rule.as_deref(), path.as_deref(), &root, fmt)
                );
                return;
            }
            "session" => {
                super::cmd_session_action(&rest);
                return;
            }
            "ledger" => {
                super::cmd_ledger(&rest);
                return;
            }
            "control" | "context-control" => {
                super::cmd_control(&rest);
                return;
            }
            "plan" | "context-plan" => {
                super::cmd_plan(&rest);
                return;
            }
            "compile" | "context-compile" => {
                super::cmd_compile(&rest);
                return;
            }
            "knowledge" => {
                super::cmd_knowledge(&rest);
                return;
            }
            "overview" => {
                super::cmd_overview(&rest);
                return;
            }
            "compress" => {
                super::cmd_compress(&rest);
                return;
            }
            "wrapped" => {
                super::cmd_wrapped(&rest);
                return;
            }
            "sessions" => {
                super::cmd_sessions(&rest);
                return;
            }
            "benchmark" => {
                super::cmd_benchmark(&rest);
                return;
            }
            "profile" => {
                super::cmd_profile(&rest);
                return;
            }
            "config" => {
                super::cmd_config(&rest);
                return;
            }
            "stats" => {
                super::cmd_stats(&rest);
                return;
            }
            "cache" => {
                super::cmd_cache(&rest);
                return;
            }
            "theme" => {
                super::cmd_theme(&rest);
                return;
            }
            "tee" => {
                super::cmd_tee(&rest);
                return;
            }
            "terse" | "compression" => {
                super::cmd_compression(&rest);
                return;
            }
            "slow-log" => {
                super::cmd_slow_log(&rest);
                return;
            }
            "update" | "--self-update" => {
                core::updater::run(&rest);
                return;
            }
            "restart" => {
                cmd_restart();
                return;
            }
            "stop" => {
                cmd_stop();
                return;
            }
            "dev-install" => {
                cmd_dev_install();
                return;
            }
            "doctor" => {
                let code = doctor::run_cli(&rest);
                if code != 0 {
                    std::process::exit(code);
                }
                return;
            }
            "harden" => {
                super::harden::run(&rest);
                return;
            }
            "export-rules" => {
                super::export_rules::run(&rest);
                return;
            }
            "gotchas" | "bugs" => {
                super::cloud::cmd_gotchas(&rest);
                return;
            }
            "learn" => {
                super::cmd_learn(&rest);
                return;
            }
            "buddy" | "pet" => {
                super::cloud::cmd_buddy(&rest);
                return;
            }
            "hook" => {
                hook_handlers::mark_hook_environment();
                hook_handlers::arm_watchdog(std::time::Duration::from_secs(5));
                let action = rest.first().map_or("help", std::string::String::as_str);
                match action {
                    "rewrite" => hook_handlers::handle_rewrite(),
                    "redirect" => hook_handlers::handle_redirect(),
                    "observe" => hook_handlers::handle_observe(),
                    "copilot" => hook_handlers::handle_copilot(),
                    "codex-pretooluse" => hook_handlers::handle_codex_pretooluse(),
                    "codex-session-start" => hook_handlers::handle_codex_session_start(),
                    "rewrite-inline" => hook_handlers::handle_rewrite_inline(),
                    _ => {
                        eprintln!("Usage: lean-ctx hook <rewrite|redirect|observe|copilot|codex-pretooluse|codex-session-start|rewrite-inline>");
                        eprintln!("  Internal commands used by agent hooks (Claude, Cursor, Copilot, etc.)");
                        std::process::exit(1);
                    }
                }
                return;
            }
            "report-issue" | "report" => {
                report::run(&rest);
                return;
            }
            "uninstall" => {
                let dry_run = rest.iter().any(|a| a == "--dry-run");
                uninstall::run(dry_run);
                return;
            }
            "bypass" => {
                if rest.is_empty() {
                    eprintln!("Usage: lean-ctx bypass \"command\"");
                    eprintln!("Runs the command with zero compression (raw passthrough).");
                    std::process::exit(1);
                }
                let command = if rest.len() == 1 {
                    rest[0].clone()
                } else {
                    shell::join_command(&args[2..])
                };
                std::env::set_var("LEAN_CTX_RAW", "1");
                let code = shell::exec(&command);
                std::process::exit(code);
            }
            "safety-levels" | "safety" => {
                println!("{}", core::compression_safety::format_safety_table());
                return;
            }
            "cheat" | "cheatsheet" | "cheat-sheet" => {
                super::cmd_cheatsheet();
                return;
            }
            "login" => {
                super::cloud::cmd_login(&rest);
                return;
            }
            "register" => {
                super::cloud::cmd_register(&rest);
                return;
            }
            "forgot-password" => {
                super::cloud::cmd_forgot_password(&rest);
                return;
            }
            "sync" => {
                super::cloud::cmd_sync();
                return;
            }
            "contribute" => {
                super::cloud::cmd_contribute();
                return;
            }
            "cloud" => {
                super::cloud::cmd_cloud(&rest);
                return;
            }
            "upgrade" => {
                super::cloud::cmd_upgrade();
                return;
            }
            "--version" | "-V" => {
                println!("{}", core::integrity::origin_line());
                return;
            }
            "--help" | "-h" => {
                print_help();
                return;
            }
            "mcp" => {}
            _ => {
                tracing::error!("lean-ctx: unknown command '{}'", args[1]);
                print_help();
                std::process::exit(1);
            }
        }
    }

    if let Err(e) = run_mcp_server() {
        tracing::error!("lean-ctx: {e}");
        std::process::exit(1);
    }
}

fn passthrough(command: &str) -> ! {
    let (shell, flag) = shell::shell_and_flag();
    let status = std::process::Command::new(&shell)
        .arg(&flag)
        .arg(command)
        .env("LEAN_CTX_ACTIVE", "1")
        .status()
        .map_or(127, |s| s.code().unwrap_or(1));
    std::process::exit(status);
}

fn run_async<F: std::future::Future>(future: F) -> F::Output {
    tokio::runtime::Runtime::new()
        .expect("failed to create async runtime")
        .block_on(future)
}

fn run_mcp_server() -> Result<()> {
    use rmcp::ServiceExt;

    std::env::set_var("LEAN_CTX_MCP_SERVER", "1");

    crate::core::startup_guard::crash_loop_backoff("mcp-server");

    // Concurrency hardening:
    // - Smooths "thundering herd" MCP startups (multiple agent sessions).
    // - Limits Tokio worker/blocking threads to avoid host degradation.
    // - LEAN_CTX_WORKER_THREADS overrides the default for environments
    //   with many concurrent subagents (e.g. parallel review pipelines).
    let startup_lock = crate::core::startup_guard::try_acquire_lock(
        "mcp-startup",
        std::time::Duration::from_secs(3),
        std::time::Duration::from_secs(30),
    );

    let parallelism = std::thread::available_parallelism().map_or(2, std::num::NonZeroUsize::get);
    let worker_threads = resolve_worker_threads(parallelism);
    let max_blocking_threads = (worker_threads * 4).clamp(8, 32);

    let rt = tokio::runtime::Builder::new_multi_thread()
        .worker_threads(worker_threads)
        .max_blocking_threads(max_blocking_threads)
        .enable_all()
        .build()?;

    let server = tools::create_server();
    drop(startup_lock);

    // Auto-start proxy in background so the dashboard gets exact token data.
    spawn_proxy_if_needed();

    rt.block_on(async {
        core::logging::init_mcp_logging();
        core::protocol::set_mcp_context(true);

        tracing::info!(
            "lean-ctx v{} MCP server starting",
            env!("CARGO_PKG_VERSION")
        );

        let transport =
            mcp_stdio::HybridStdioTransport::new_server(tokio::io::stdin(), tokio::io::stdout());
        let server_handle = server.clone();
        let service = match server.serve(transport).await {
            Ok(s) => s,
            Err(e) => {
                let msg = e.to_string();
                if msg.contains("expect initialized")
                    || msg.contains("context canceled")
                    || msg.contains("broken pipe")
                {
                    tracing::debug!("Client disconnected before init: {msg}");
                    return Ok(());
                }
                return Err(e.into());
            }
        };
        match service.waiting().await {
            Ok(reason) => {
                tracing::info!("MCP server stopped: {reason:?}");
            }
            Err(e) => {
                let msg = e.to_string();
                if msg.contains("broken pipe")
                    || msg.contains("connection reset")
                    || msg.contains("context canceled")
                {
                    tracing::info!("MCP server: transport closed ({msg})");
                } else {
                    tracing::error!("MCP server error: {msg}");
                }
            }
        }

        server_handle.shutdown().await;

        core::stats::flush();
        core::heatmap::flush();
        core::mode_predictor::ModePredictor::flush();
        core::feedback::FeedbackStore::flush();

        Ok(())
    })
}

fn print_help() {
    println!(
        "lean-ctx {version} — Context Runtime for AI Agents

60+ compression patterns | 61 MCP tools | 10 read modes | Context Continuity Protocol

USAGE:
    lean-ctx                       Start MCP server (stdio)
    lean-ctx serve                 Start MCP server (Streamable HTTP)
    lean-ctx serve --daemon        Start as background daemon (Unix Domain Socket)
    lean-ctx serve --stop          Stop running daemon
    lean-ctx serve --status        Show daemon status
    lean-ctx -t \"command\"          Track command (full output + stats, no compression)
    lean-ctx -c \"command\"          Execute with compressed output (used by AI hooks)
    lean-ctx -c --raw \"command\"    Execute without compression (full output)
    lean-ctx exec \"command\"        Same as -c
    lean-ctx shell                 Interactive shell with compression

COMMANDS:
    gain                           Visual dashboard (colors, bars, sparklines, USD)
    gain --live                    Live mode: auto-refreshes every 1s in-place
    gain --graph                   30-day savings chart
    gain --daily                   Bordered day-by-day table with USD
    gain --json                    Raw JSON export of all stats
         token-report [--json]          Token + memory report (project + session + CEP)
    pack --pr                      PR Context Pack (changed files, impact, tests, artifacts)
    index <status|build|build-full|watch>  Codebase index utilities
    cep                            CEP impact report (score trends, cache, modes)
    watch                          Live TUI dashboard (real-time event stream)
    dashboard [--port=N] [--host=H] Open web dashboard (default: http://localhost:3333)
    serve [--host H] [--port N]    MCP over HTTP (Streamable HTTP, local-first)
    proxy start [--port=4444]      API proxy: compress tool_results before LLM API
    proxy status                   Show proxy statistics
    cache [list|clear|stats]       Show/manage file read cache
    wrapped [--week|--month|--all] Deprecated alias for gain --wrapped
    sessions [list|show|cleanup]   Manage CCP sessions (~/.lean-ctx/sessions/)
    benchmark run [path] [--json]  Run real benchmark on project files
    benchmark report [path]        Generate shareable Markdown report
    cheatsheet                     Command cheat sheet & workflow quick reference
    setup                          One-command setup: shell + editor + verify
    install --repair [--json]      Premium repair: merge-based setup refresh (no deletes)
    bootstrap                      Non-interactive setup + fix (zero-config)
    status [--json]                Show setup + MCP + rules status
    init [--global]                Install shell aliases (zsh/bash/fish/PowerShell)
    init --agent <name>            Configure MCP for specific editor/agent
    read <file> [-m mode]          Read file with compression
    diff <file1> <file2>           Compressed file diff
    grep <pattern> [path]          Search with compressed output
    find <pattern> [path]          Find files with compressed output
    ls [path]                      Directory listing with compression
    deps [path]                    Show project dependencies
    discover                       Find uncompressed commands in shell history
    ghost [--json]                 Ghost Token report: find hidden token waste
    filter [list|validate|init]    Manage custom compression filters (~/.lean-ctx/filters/)
    session                        Show adoption statistics
    session task <desc>            Set current task
    session finding <summary>      Record a finding
    session save                   Save current session
    session load [id]              Load session (latest if no ID)
    knowledge remember <value> --category <c> --key <k>   Store a fact
    knowledge recall [query] [--category <c>]             Retrieve facts
    knowledge search <query>       Cross-project knowledge search
    knowledge export [--format json|jsonl|simple] [--output <path>]  Export knowledge
    knowledge import <path> [--merge replace|append|skip-existing]   Import knowledge
    knowledge remove --category <c> --key <k>             Remove a fact
    knowledge status               Knowledge base summary
    overview [task]                Project overview (task-contextualized if given)
    compress [--signatures]        Context compression checkpoint
    config                         Show/edit configuration (~/.lean-ctx/config.toml)
    profile [list|show|diff|create|set]  Manage context profiles
    theme [list|set|export|import] Customize terminal colors and themes
    tee [list|clear|show <file>|last] Manage output tee files (~/.lean-ctx/tee/)
    terse [off|lite|full|ultra]    Set agent output verbosity (saves 25-65% output tokens)
    slow-log [list|clear]          Show/clear slow command log (~/.lean-ctx/slow-commands.log)
    update [--check]               Self-update lean-ctx binary from GitHub Releases
    stop                           Stop ALL lean-ctx processes (daemon, proxy, orphans)
    restart                        Restart daemon (applies config.toml changes)
    dev-install                    Build release + atomic install + restart (for development)
    gotchas [list|clear|export|stats] Bug Memory: view/manage auto-detected error patterns
    buddy [show|stats|ascii|json]  Token Guardian: your data-driven coding companion
    doctor integrations [--json]   Integration health checks (Cursor/Claude Code)
    doctor [--fix] [--json]        Run diagnostics (and optionally repair)
    smells [scan|summary|rules|file] [--rule=<r>] [--path=<p>] [--json]
                                   Code smell detection (Property Graph, 8 rules)
    control <action> [--target=<t>] Context field manipulation (exclude/pin/priority)
    plan <task> [--budget=N]       Context planning (optimal Phi-scored context plan)
    compile [--mode=<m>] [--budget=N] Context compilation (knapsack + Boltzmann)
    uninstall                      Remove shell hook, MCP configs, and data directory

SHELL HOOK PATTERNS (95+):
    git       status, log, diff, add, commit, push, pull, fetch, clone,
              branch, checkout, switch, merge, stash, tag, reset, remote
    docker    build, ps, images, logs, compose, exec, network
    npm/pnpm  install, test, run, list, outdated, audit
    cargo     build, test, check, clippy
    gh        pr list/view/create, issue list/view, run list/view
    kubectl   get pods/services/deployments, logs, describe, apply
    python    pip install/list/outdated, ruff check/format, poetry, uv
    linters   eslint, biome, prettier, golangci-lint
    builds    tsc, next build, vite build
    ruby      rubocop, bundle install/update, rake test, rails test
    tests     jest, vitest, pytest, go test, playwright, rspec, minitest
    iac       terraform, make, maven, gradle, dotnet, flutter, dart
    utils     curl, grep/rg, find, ls, wget, env
    data      JSON schema extraction, log deduplication

READ MODES:
    auto                           Auto-select optimal mode (default)
    full                           Full content (cached re-reads = 13 tokens)
    map                            Dependency graph + API signatures
    signatures                     tree-sitter AST extraction (18 languages)
    task                           Task-relevant filtering (requires ctx_session task)
    reference                      One-line reference stub (cheap cache key)
    aggressive                     Syntax-stripped content
    entropy                        Shannon entropy filtered
    diff                           Changed lines only
    lines:N-M                      Specific line ranges (e.g. lines:10-50,80)

ENVIRONMENT:
    LEAN_CTX_DISABLED=1            Bypass ALL compression + prevent shell hook from loading
    LEAN_CTX_ENABLED=0             Prevent shell hook auto-start (lean-ctx-on still works)
    LEAN_CTX_RAW=1                 Same as --raw for current command
    LEAN_CTX_AUTONOMY=false        Disable autonomous features
    LEAN_CTX_COMPRESS=1            Force compression (even for excluded commands)

OPTIONS:
    --version, -V                  Show version
    --help, -h                     Show this help

EXAMPLES:
    lean-ctx -c \"git status\"       Compressed git output
    lean-ctx -c \"kubectl get pods\" Compressed k8s output
    lean-ctx -c \"gh pr list\"       Compressed GitHub CLI output
    lean-ctx gain                  Visual terminal dashboard
    lean-ctx gain --live           Live auto-updating terminal dashboard
    lean-ctx gain --graph          30-day savings chart
    lean-ctx gain --daily          Day-by-day breakdown with USD
         lean-ctx token-report --json   Machine-readable token + memory report
    lean-ctx dashboard             Open web dashboard at localhost:3333
    lean-ctx dashboard --host=0.0.0.0  Bind to all interfaces (remote access)
    lean-ctx gain --wrapped        Wrapped report card (recommended)
    lean-ctx gain --wrapped --period=month  Monthly Wrapped report card
    lean-ctx sessions list         List all CCP sessions
    lean-ctx sessions show         Show latest session state
    lean-ctx discover              Find missed savings in shell history
    lean-ctx setup                 One-command setup (shell + editors + verify)
    lean-ctx install --repair      Premium repair path (non-interactive, merge-based)
    lean-ctx bootstrap             Non-interactive setup + fix (zero-config)
    lean-ctx bootstrap --json      Machine-readable bootstrap report
    lean-ctx init --global         Install shell aliases (includes lean-ctx-on/off/mode/status)
    lean-ctx-on                    Enable shell aliases in track mode (full output + stats)
    lean-ctx-off                   Disable all shell aliases
    lean-ctx-mode track            Track mode: full output, stats recorded (default)
    lean-ctx-mode compress         Compress mode: all output compressed (power users)
    lean-ctx-mode off              Same as lean-ctx-off
    lean-ctx-status                Show whether compression is active
    lean-ctx init --agent pi       Install Pi Coding Agent extension
    lean-ctx doctor                Check PATH, config, MCP, and dashboard port
    lean-ctx doctor integrations   Premium integration checks (Cursor/Claude Code)
    lean-ctx doctor --fix --json   Repair + machine-readable report
    lean-ctx status --json         Machine-readable current status
    lean-ctx session task \"implement auth\"
    lean-ctx session finding \"auth.rs:42 — missing validation\"
    lean-ctx knowledge remember \"Uses JWT\" --category auth --key token-type
    lean-ctx knowledge recall \"authentication\"
    lean-ctx knowledge search \"database migration\"
    lean-ctx overview \"refactor auth module\"
    lean-ctx compress --signatures
    lean-ctx read src/main.rs -m map
    lean-ctx grep \"pub fn\" src/
    lean-ctx deps .

CLOUD:
    cloud status                   Show cloud connection status
    login <email>                  Log into existing LeanCTX Cloud account
    register <email>               Create a new LeanCTX Cloud account
    forgot-password <email>        Send password reset email
    sync                           Upload local stats to cloud dashboard
    contribute                     Share anonymized compression data

TROUBLESHOOTING:
    Commands broken?     lean-ctx-off             (fixes current session)
    Permanent fix?       lean-ctx uninstall       (removes all hooks)
    Manual fix?          Edit ~/.zshrc, remove the \"lean-ctx shell hook\" block
    Binary missing?      Aliases auto-fallback to original commands (safe)
    Preview init?        lean-ctx init --global --dry-run

WEBSITE: https://leanctx.com
GITHUB:  https://github.com/yvgude/lean-ctx
",
        version = env!("CARGO_PKG_VERSION"),
    );
}

fn cmd_stop() {
    use crate::daemon;
    use crate::ipc;

    eprintln!("Stopping all lean-ctx processes…");

    // 1. Unload LaunchAgent/systemd first to prevent respawning
    crate::proxy_autostart::stop();
    eprintln!("  Unloaded autostart (LaunchAgent/systemd).");

    // 2. Stop daemon via IPC
    if let Err(e) = daemon::stop_daemon() {
        eprintln!("  Warning: daemon stop: {e}");
    }

    // 3. SIGTERM all remaining lean-ctx processes
    let killed = ipc::process::kill_all_by_name("lean-ctx");
    if killed > 0 {
        eprintln!("  Sent SIGTERM to {killed} process(es).");
    }

    std::thread::sleep(std::time::Duration::from_millis(500));

    // 4. Force-kill stragglers (but never MCP servers — IDE will respawn them)
    let remaining = ipc::process::find_killable_pids("lean-ctx");
    if !remaining.is_empty() {
        eprintln!("  Force-killing {} stubborn process(es)…", remaining.len());
        for &pid in &remaining {
            let _ = ipc::process::force_kill(pid);
        }
        std::thread::sleep(std::time::Duration::from_millis(300));
    }

    daemon::cleanup_daemon_files();

    let final_check = ipc::process::find_killable_pids("lean-ctx");
    if final_check.is_empty() {
        eprintln!("  ✓ All lean-ctx processes stopped.");
    } else {
        eprintln!(
            "  ✗ {} process(es) could not be killed: {:?}",
            final_check.len(),
            final_check
        );
        eprintln!(
            "    Try: sudo kill -9 {}",
            final_check
                .iter()
                .map(std::string::ToString::to_string)
                .collect::<Vec<_>>()
                .join(" ")
        );
        std::process::exit(1);
    }
}

fn cmd_restart() {
    use crate::daemon;
    use crate::ipc;

    eprintln!("Restarting lean-ctx…");

    // Stop autostart first to prevent respawning during restart
    crate::proxy_autostart::stop();

    if let Err(e) = daemon::stop_daemon() {
        eprintln!("  Warning: daemon stop: {e}");
    }

    let orphans = ipc::process::kill_all_by_name("lean-ctx");
    if orphans > 0 {
        eprintln!("  Terminated {orphans} orphan process(es).");
    }

    std::thread::sleep(std::time::Duration::from_millis(500));

    let remaining = ipc::process::find_pids_by_name("lean-ctx");
    if !remaining.is_empty() {
        eprintln!(
            "  Force-killing {} stubborn process(es): {:?}",
            remaining.len(),
            remaining
        );
        for &pid in &remaining {
            let _ = ipc::process::force_kill(pid);
        }
        std::thread::sleep(std::time::Duration::from_millis(300));
    }

    daemon::cleanup_daemon_files();

    // Re-enable autostart
    crate::proxy_autostart::start();

    match daemon::start_daemon(&[]) {
        Ok(()) => eprintln!("  ✓ Daemon restarted. Config changes are now active."),
        Err(e) => {
            eprintln!("  ✗ Daemon start failed: {e}");
            std::process::exit(1);
        }
    }
}

fn cmd_dev_install() {
    use crate::ipc;

    let cargo_root = find_cargo_project_root();
    let Some(cargo_root) = cargo_root else {
        eprintln!("Error: No Cargo.toml found. Run from the lean-ctx project directory.");
        std::process::exit(1);
    };

    eprintln!("Building release binary…");
    let build = std::process::Command::new("cargo")
        .args(["build", "--release"])
        .current_dir(&cargo_root)
        .status();

    match build {
        Ok(s) if s.success() => {}
        Ok(s) => {
            eprintln!("  Build failed with exit code {}", s.code().unwrap_or(-1));
            std::process::exit(1);
        }
        Err(e) => {
            eprintln!("  Build failed: {e}");
            std::process::exit(1);
        }
    }

    let built_binary = cargo_root.join("target/release/lean-ctx");
    if !built_binary.exists() {
        eprintln!(
            "  Error: Built binary not found at {}",
            built_binary.display()
        );
        std::process::exit(1);
    }

    let install_path = resolve_install_path();
    eprintln!("Installing to {}…", install_path.display());

    eprintln!("  Stopping all lean-ctx processes…");
    crate::proxy_autostart::stop();
    let _ = crate::daemon::stop_daemon();
    ipc::process::kill_all_by_name("lean-ctx");
    std::thread::sleep(std::time::Duration::from_millis(500));

    let remaining = ipc::process::find_pids_by_name("lean-ctx");
    if !remaining.is_empty() {
        eprintln!("  Force-killing {} stubborn process(es)…", remaining.len());
        for &pid in &remaining {
            let _ = ipc::process::force_kill(pid);
        }
        std::thread::sleep(std::time::Duration::from_millis(500));
    }

    let old_path = install_path.with_extension("old");
    if install_path.exists() {
        if let Err(e) = std::fs::rename(&install_path, &old_path) {
            eprintln!("  Warning: rename existing binary: {e}");
        }
    }

    match std::fs::copy(&built_binary, &install_path) {
        Ok(_) => {
            let _ = std::fs::remove_file(&old_path);
            #[cfg(unix)]
            {
                use std::os::unix::fs::PermissionsExt;
                let _ =
                    std::fs::set_permissions(&install_path, std::fs::Permissions::from_mode(0o755));
            }
            eprintln!("  ✓ Binary installed.");
        }
        Err(e) => {
            eprintln!("  Error: copy failed: {e}");
            if old_path.exists() {
                let _ = std::fs::rename(&old_path, &install_path);
                eprintln!("  Rolled back to previous binary.");
            }
            std::process::exit(1);
        }
    }

    let version = std::process::Command::new(&install_path)
        .arg("--version")
        .output()
        .map_or_else(
            |_| "unknown".to_string(),
            |o| String::from_utf8_lossy(&o.stdout).trim().to_string(),
        );

    eprintln!("  ✓ dev-install complete: {version}");

    eprintln!("  Re-enabling autostart…");
    crate::proxy_autostart::start();

    eprintln!("  Starting daemon…");
    match crate::daemon::start_daemon(&[]) {
        Ok(()) => {}
        Err(e) => eprintln!("  Warning: daemon start: {e} (will be started by editor)"),
    }
}

fn find_cargo_project_root() -> Option<std::path::PathBuf> {
    let mut dir = std::env::current_dir().ok()?;
    loop {
        if dir.join("Cargo.toml").exists() {
            return Some(dir);
        }
        if !dir.pop() {
            return None;
        }
    }
}

fn resolve_install_path() -> std::path::PathBuf {
    if let Ok(exe) = std::env::current_exe() {
        if let Ok(canonical) = exe.canonicalize() {
            let is_in_cargo_target = canonical.components().any(|c| c.as_os_str() == "target");
            if !is_in_cargo_target && canonical.exists() {
                return canonical;
            }
        }
    }

    if let Ok(home) = std::env::var("HOME") {
        let local_bin = std::path::PathBuf::from(&home).join(".local/bin/lean-ctx");
        if local_bin.parent().is_some_and(std::path::Path::exists) {
            return local_bin;
        }
    }

    std::path::PathBuf::from("/usr/local/bin/lean-ctx")
}

fn spawn_proxy_if_needed() {
    use std::net::TcpStream;
    use std::time::Duration;

    let cfg = core::config::Config::load();
    if cfg.proxy_enabled != Some(true) {
        return;
    }

    let port = crate::proxy_setup::default_port();
    let already_running = TcpStream::connect_timeout(
        &format!("127.0.0.1:{port}").parse().unwrap(),
        Duration::from_millis(200),
    )
    .is_ok();

    if already_running {
        tracing::debug!("proxy already running on port {port}");
        return;
    }

    let binary = core::portable_binary::resolve_portable_binary();

    match std::process::Command::new(&binary)
        .args(["proxy", "start", &format!("--port={port}")])
        .stdin(std::process::Stdio::null())
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::null())
        .spawn()
    {
        Ok(_) => tracing::info!("auto-started proxy on port {port}"),
        Err(e) => tracing::debug!("could not auto-start proxy: {e}"),
    }
}

fn resolve_worker_threads(parallelism: usize) -> usize {
    std::env::var("LEAN_CTX_WORKER_THREADS")
        .ok()
        .and_then(|v| v.parse::<usize>().ok())
        .unwrap_or_else(|| parallelism.clamp(1, 4))
}

#[cfg(test)]
mod tests {
    use super::*;
    use serial_test::serial;

    #[test]
    #[serial]
    fn worker_threads_default_clamps_low() {
        std::env::remove_var("LEAN_CTX_WORKER_THREADS");
        assert_eq!(resolve_worker_threads(1), 1);
    }

    #[test]
    #[serial]
    fn worker_threads_default_clamps_high() {
        std::env::remove_var("LEAN_CTX_WORKER_THREADS");
        assert_eq!(resolve_worker_threads(32), 4);
    }

    #[test]
    #[serial]
    fn worker_threads_default_passthrough() {
        std::env::remove_var("LEAN_CTX_WORKER_THREADS");
        assert_eq!(resolve_worker_threads(3), 3);
    }

    #[test]
    #[serial]
    fn worker_threads_env_override() {
        std::env::set_var("LEAN_CTX_WORKER_THREADS", "12");
        assert_eq!(resolve_worker_threads(2), 12);
        std::env::remove_var("LEAN_CTX_WORKER_THREADS");
    }

    #[test]
    #[serial]
    fn worker_threads_env_invalid_falls_back() {
        std::env::set_var("LEAN_CTX_WORKER_THREADS", "not_a_number");
        assert_eq!(resolve_worker_threads(3), 3);
        std::env::remove_var("LEAN_CTX_WORKER_THREADS");
    }
}
