pub mod agent;
pub mod agent_shared;
pub mod agent_team;
pub mod ask;
pub mod background;
mod browser;
pub mod classification;
pub mod compact;
mod computer_use;
pub mod create_teammate;
mod file;
mod grep;
pub mod hook;
pub mod plan;
pub mod send_message;
mod shell;
pub mod skill;
pub mod task;
pub mod todo;
mod web_fetch;
mod web_search;
pub mod worktree;
use async_openai::types::chat::{ChatCompletionTool, ChatCompletionTools, FunctionObject};
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::Value;
use std::sync::{Arc, Mutex, atomic::AtomicBool, mpsc};

/// 从 `schemars::schema_for!` 生成的 root schema 中提取 OpenAI tool 需要的
/// `{"type":"object","properties":...,"required":...}` 格式。
///
/// schemars 0.8 生成的 root schema 自带 `$schema` / `title` / `definitions` 等顶层字段，
/// 但 LLM function calling 只需要核心的 object schema 部分。此函数做以下事情：
///  1. 剔除 `$schema`、`title`、`definitions`
///  2. 如果 schema 没有 `"type":"object"`，原样返回（不做裁剪）
pub fn schema_to_tool_params<T: JsonSchema>() -> Value {
    let root = schemars::schema_for!(T);
    let mut v = serde_json::to_value(root).unwrap_or_default();
    if let Some(obj) = v.as_object_mut() {
        obj.remove("$schema");
        obj.remove("title");
        obj.remove("definitions");
    }
    v
}

/// 便捷：解析 tool 参数 JSON 字符串为 `T`，返回友好错误
pub fn parse_tool_args<T: for<'de> Deserialize<'de>>(arguments: &str) -> Result<T, ToolResult> {
    serde_json::from_str::<T>(arguments).map_err(|e| ToolResult {
        output: format!("参数解析失败: {}", e),
        is_error: true,
        images: vec![],
    })
}

// ========== ToolResult ==========

/// 图片数据（用于多模态工具返回）
#[derive(Debug, Clone)]
pub struct ImageData {
    /// base64 编码的图片数据
    pub base64: String,
    /// MIME 类型（如 "image/png", "image/jpeg"）
    pub media_type: String,
}

/// 工具执行结果
pub struct ToolResult {
    /// 返回给 LLM 的内容
    pub output: String,
    /// 是否执行出错
    pub is_error: bool,
    /// 可选的图片数据（用于多模态模型，由 agent loop 决定是否注入）
    pub images: Vec<ImageData>,
}

// ========== Tool trait ==========

/// 工具 trait
pub trait Tool: Send + Sync {
    fn name(&self) -> &str;
    fn description(&self) -> &str;
    fn parameters_schema(&self) -> Value;
    /// 执行工具（同步）；cancelled 为取消信号，支持提前终止
    fn execute(&self, arguments: &str, cancelled: &Arc<AtomicBool>) -> ToolResult;
    /// 是否需要用户确认（shell 命令需要，文件读取不需要）
    fn requires_confirmation(&self) -> bool {
        false
    }
    /// 生成确认提示文字（供 TUI 展示）
    fn confirmation_message(&self, arguments: &str) -> String {
        format!("调用工具 {} 参数: {}", self.name(), arguments)
    }
}

// ========== ToolRegistry ==========

/// 工具注册表
pub struct ToolRegistry {
    tools: Vec<Box<dyn Tool>>,
    /// Todo 管理器（供外部获取以传入 agent loop）
    pub todo_manager: Arc<todo::TodoManager>,
    /// Plan Mode 状态（供外部检查当前是否处于 plan mode）
    pub plan_mode_state: Arc<plan::PlanModeState>,
    /// Worktree 状态（跨工具共享）
    #[allow(dead_code)]
    pub worktree_state: Arc<worktree::WorktreeState>,
}

impl ToolRegistry {
    /// 创建注册表（包含内置工具，以及当 skills 非空时注册 load_skill）
    pub fn new(
        skills: Vec<crate::command::chat::skill::Skill>,
        ask_tx: mpsc::Sender<crate::command::chat::app::AskRequest>,
        background_manager: Arc<background::BackgroundManager>,
        task_manager: Arc<task::TaskManager>,
        hook_manager: Arc<Mutex<crate::command::chat::hook::HookManager>>,
    ) -> Self {
        let todo_manager = Arc::new(todo::TodoManager::new());
        let plan_mode_state = Arc::new(plan::PlanModeState::new());
        let worktree_state = Arc::new(worktree::WorktreeState::new());

        let mut registry = Self {
            todo_manager: Arc::clone(&todo_manager),
            plan_mode_state: Arc::clone(&plan_mode_state),
            worktree_state: Arc::clone(&worktree_state),
            tools: vec![
                Box::new(shell::ShellTool {
                    manager: Arc::clone(&background_manager),
                }),
                Box::new(file::ReadFileTool),
                Box::new(file::WriteFileTool),
                Box::new(file::EditFileTool),
                Box::new(file::GlobTool),
                Box::new(grep::GrepTool),
                Box::new(web_fetch::WebFetchTool),
                Box::new(web_search::WebSearchTool),
                Box::new(browser::BrowserTool),
                Box::new(ask::AskTool {
                    ask_tx: ask_tx.clone(),
                }),
                // 后台任务工具
                Box::new(background::TaskOutputTool {
                    manager: Arc::clone(&background_manager),
                }),
                // 任务管理工具
                Box::new(task::TaskTool {
                    manager: Arc::clone(&task_manager),
                }),
                // Todo 工具
                Box::new(todo::TodoWriteTool {
                    manager: Arc::clone(&todo_manager),
                }),
                Box::new(todo::TodoReadTool {
                    manager: Arc::clone(&todo_manager),
                }),
                // Context compact 工具
                Box::new(compact::CompactTool),
                // Hook 管理工具
                Box::new(hook::RegisterHookTool { hook_manager }),
                // Computer Use 工具（aic 集成）
                Box::new(computer_use::ComputerUseTool::new()),
                // Plan Mode 工具
                Box::new(plan::EnterPlanModeTool {
                    plan_state: Arc::clone(&plan_mode_state),
                }),
                Box::new(plan::ExitPlanModeTool {
                    plan_state: Arc::clone(&plan_mode_state),
                    ask_tx,
                }),
                // Worktree 隔离工具
                Box::new(worktree::EnterWorktreeTool {
                    state: Arc::clone(&worktree_state),
                }),
                Box::new(worktree::ExitWorktreeTool {
                    state: Arc::clone(&worktree_state),
                }),
            ],
        };

        // 如果有 skills，注册统一的 LoadSkillTool
        if !skills.is_empty() {
            registry.register(Box::new(self::skill::LoadSkillTool { skills }));
        }

        registry
    }

    /// 注册一个工具
    pub fn register(&mut self, tool: Box<dyn Tool>) {
        self.tools.push(tool);
    }

    /// 按名称获取工具
    pub fn get(&self, name: &str) -> Option<&dyn Tool> {
        self.tools
            .iter()
            .find(|t| t.name() == name)
            .map(|t| t.as_ref())
    }

    /// 按名称执行工具，返回结果（可在任何线程调用，ToolRegistry: Send + Sync）
    /// 自动检查 plan mode：若 plan mode 激活且工具不在白名单中，返回错误
    pub fn execute(&self, name: &str, arguments: &str, cancelled: &Arc<AtomicBool>) -> ToolResult {
        // Plan mode 检查（原子获取 active + plan_file_path，避免竞态）
        let (is_active, plan_file_path) = self.plan_mode_state.get_state();
        if is_active && !plan::is_allowed_in_plan_mode(name) {
            // 允许 Write/Edit 工具写入 plan 文件
            let is_plan_file_write = (name == "Write" || name == "Edit") && {
                if let Some(ref plan_path) = plan_file_path {
                    // 从工具参数中提取目标路径
                    serde_json::from_str::<serde_json::Value>(arguments)
                        .ok()
                        .and_then(|v| {
                            v.get("path")
                                .or_else(|| v.get("file_path"))
                                .and_then(|p| p.as_str())
                                .map(|p| {
                                    // 规范化路径比较：支持相对路径和绝对路径
                                    let input_path = std::path::Path::new(p);
                                    let plan_path_buf = std::path::Path::new(&plan_path);

                                    // 直接比较
                                    if p == plan_path {
                                        return true;
                                    }

                                    // 尝试将相对路径转为绝对路径后比较
                                    if input_path.is_relative()
                                        && let Ok(cwd) = std::env::current_dir()
                                    {
                                        let absolute_path = cwd.join(input_path);
                                        if let Ok(canonical_input) = absolute_path.canonicalize()
                                            && let Ok(canonical_plan) = plan_path_buf.canonicalize()
                                        {
                                            return canonical_input == canonical_plan;
                                        }
                                    }

                                    false
                                })
                        })
                        .unwrap_or(false)
                } else {
                    false
                }
            };

            if !is_plan_file_write {
                return ToolResult {
                    output: format!(
                        "Tool '{}' is not available in plan mode. Only read-only tools are allowed. \
                         Use ExitPlanMode to exit plan mode first.",
                        name
                    ),
                    is_error: true,
                    images: vec![],
                };
            }
        }

        match self.get(name) {
            Some(tool) => tool.execute(arguments, cancelled),
            None => ToolResult {
                output: format!("未知工具: {}", name),
                is_error: true,
                images: vec![],
            },
        }
    }

    /// 构建工具摘要列表，用于系统提示词的 {{.tools}} 占位符（Markdown 格式）
    /// 当 disabled 非空时，过滤掉其中列出的工具
    pub fn build_tools_summary(&self, disabled: &[String]) -> String {
        let mut md = String::new();
        for t in self
            .tools
            .iter()
            .filter(|t| !disabled.iter().any(|d| d == t.name()))
        {
            let name = t.name();
            md.push_str(&format!("<{}>\n", name));
            md.push_str(&format!("description:\n{}\n", t.description().trim()));
            md.push_str(&json_schema_to_xml_params(&t.parameters_schema()));
            md.push_str(&format!("<{}/>\n\n", name));
        }
        md.trim_end().to_string()
    }

    /// 生成过滤后的 ChatCompletionTools 列表（排除 disabled 中的工具）
    pub fn to_openai_tools_filtered(&self, disabled: &[String]) -> Vec<ChatCompletionTools> {
        self.tools
            .iter()
            .filter(|t| !disabled.iter().any(|d| d == t.name()))
            .map(|t| {
                ChatCompletionTools::Function(ChatCompletionTool {
                    function: FunctionObject {
                        name: t.name().to_string(),
                        description: Some(t.description().trim().to_string()),
                        parameters: Some(t.parameters_schema()),
                        strict: None,
                    },
                })
            })
            .collect()
    }

    /// 返回所有注册的工具名称（供 UI 使用）
    pub fn tool_names(&self) -> Vec<&str> {
        self.tools.iter().map(|t| t.name()).collect()
    }
}

// ========== Helper functions ==========

/// 展开路径中的 ~ 为用户 home 目录
pub fn expand_tilde(path: &str) -> String {
    if path == "~" {
        std::env::var("HOME").unwrap_or_else(|_| "~".to_string())
    } else if let Some(rest) = path.strip_prefix("~/") {
        match std::env::var("HOME") {
            Ok(home) => format!("{}/{}", home, rest),
            Err(_) => path.to_string(),
        }
    } else {
        path.to_string()
    }
}

/// 将 JSON Schema 转为 Markdown 参数列表
fn json_schema_to_xml_params(schema: &Value) -> String {
    let properties = match schema.get("properties").and_then(|p| p.as_object()) {
        Some(p) => p,
        None => return String::new(),
    };
    let required: Vec<&str> = schema
        .get("required")
        .and_then(|r| r.as_array())
        .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
        .unwrap_or_default();

    let mut md = String::from("parameter schema:\n");
    for (name, prop) in properties {
        let type_str = prop
            .get("type")
            .and_then(|t| t.as_str())
            .unwrap_or("string");
        let desc = prop
            .get("description")
            .and_then(|d| d.as_str())
            .unwrap_or("");
        let req = if required.contains(&name.as_str()) {
            ", required"
        } else {
            ""
        };
        md.push_str(&format!("- `{}` ({}{}) — {}\n", name, type_str, req, desc));
    }
    md
}

/// 简单的危险命令过滤
pub fn is_dangerous_command(cmd: &str) -> bool {
    let cmd_lower = cmd.to_lowercase();
    let tokens = shell_words(&cmd_lower);

    // 无令牌则放行
    if tokens.is_empty() {
        return false;
    }

    let first = &tokens[0];

    // ---- 按命令精确判断 ----

    // mkfs 系列
    if first.starts_with("mkfs") || first.starts_with("mkfs.") {
        return true;
    }

    // dd：只有 dd if=xxx of=/dev/xxx 这种写磁盘设备才危险
    if first == "dd"
        && tokens
            .iter()
            .any(|t| t.starts_with("of=/dev/") && !t.starts_with("of=/dev/null"))
    {
        return true;
    }

    // fork bomb
    if cmd_lower.contains(":(){:|:&};:") || cmd_lower.contains(":(){ :|:& };:") {
        return true;
    }

    // chmod -R 777 /：仅当目标是根目录
    if first == "chmod" {
        let has_recursive = tokens.iter().any(|t| t == "-r" || t == "-R");
        if has_recursive && cmd_lower.contains("777") && tokens.last().is_some_and(|t| t == "/") {
            return true;
        }
    }

    // chown -R：仅当目标是根目录时危险（普通目录递归 chown 合法）
    if first == "chown" && cmd_lower.contains("-r") && tokens.last().is_some_and(|t| t == "/") {
        return true;
    }

    // 直接写块设备
    if tokens.iter().any(|t| t == ">" || t == ">>")
        && tokens.iter().any(|t| {
            t.starts_with("/dev/sd") || t.starts_with("/dev/nvme") || t.starts_with("/dev/disk")
        })
    {
        return true;
    }

    // curl/wget 管道到 shell：精确检测管道组合
    if (first == "curl" || first == "wget")
        && (cmd_lower.contains("| sh")
            || cmd_lower.contains("| bash")
            || cmd_lower.contains("| zsh"))
    {
        return true;
    }

    // alias shell 内建命令：只拦截裸 alias 命令（无参数或赋值）
    if first == "alias" {
        // alias 不带参数 = 列出所有别名，无害但无意义
        // alias xxx=yyy 在 bash -c 中设置别名也无效（子 shell）
        // 只拦截裸 alias 命令
        if tokens.len() == 1 {
            return true;
        }
    }

    // rm -rf /：仅当目标是根目录时拦截，rm -rf /aaa/bbb 放行
    if first == "rm" {
        let has_recursive = tokens.iter().any(|t| {
            t == "-r" || t == "-rf" || t == "-fr" || t.starts_with("-r") || t.starts_with("-f")
        });
        let targets_root = tokens.iter().any(|t| t == "/" || t == "/*");
        if has_recursive && targets_root {
            return true;
        }
    }

    false
}

/// 简单的 shell 单词拆分（处理引号，不做完整 shell 解析）
fn shell_words(input: &str) -> Vec<String> {
    let mut words = Vec::new();
    let mut current = String::new();
    let mut in_single = false;
    let mut in_double = false;

    for c in input.chars() {
        match c {
            '\'' if !in_double => {
                in_single = !in_single;
            }
            '"' if !in_single => {
                in_double = !in_double;
            }
            ' ' | '\t' if !in_single && !in_double => {
                if !current.is_empty() {
                    words.push(std::mem::take(&mut current));
                }
            }
            _ => {
                current.push(c);
            }
        }
    }
    if !current.is_empty() {
        words.push(current);
    }
    words
}

/// 常见阻塞式/交互式命令检测（非后台运行时阻断）
/// 返回 Some(提示信息) 表示检测到阻塞命令，None 表示放行
///
/// 检测策略：不仅匹配命令名，还分析参数判断是否真的处于交互/阻塞模式。
/// 例如 `python3 -c 'code'` 不会触发，但裸 `python3` 会。
pub fn check_blocking_command(cmd: &str) -> Option<&'static str> {
    let cmd_trimmed = cmd.trim();

    // 处理管道和分号：只检查最后一段管道前的命令（管道后通常是消费者，如 | grep）
    // 分号/&&/|| 后面的段也需要检查
    let segments = split_command_segments(cmd_trimmed);

    for segment in &segments {
        if let Some(msg) = check_single_segment(segment) {
            return Some(msg);
        }
    }
    None
}

/// 将命令按 ; && || 拆分成独立段（保留管道内部不拆）
fn split_command_segments(cmd: &str) -> Vec<&str> {
    let mut segments = Vec::new();
    let mut start = 0;
    let mut in_single = false;
    let mut in_double = false;

    for (i, c) in cmd.char_indices() {
        match c {
            '\'' if !in_double => in_single = !in_single,
            '"' if !in_single => in_double = !in_double,
            ';' if !in_single && !in_double => {
                let seg = cmd[start..i].trim();
                if !seg.is_empty() {
                    segments.push(seg);
                }
                start = i + ';'.len_utf8();
            }
            '&' if !in_single && !in_double => {
                let rest = &cmd[i + '&'.len_utf8()..];
                if rest.starts_with('&') {
                    let seg = cmd[start..i].trim();
                    if !seg.is_empty() {
                        segments.push(seg);
                    }
                    start = i + "&&".len();
                }
            }
            '|' if !in_single && !in_double => {
                let rest = &cmd[i + '|'.len_utf8()..];
                if rest.starts_with('|') {
                    let seg = cmd[start..i].trim();
                    if !seg.is_empty() {
                        segments.push(seg);
                    }
                    start = i + "||".len();
                }
            }
            _ => {}
        }
    }
    let last = cmd[start..].trim();
    if !last.is_empty() {
        segments.push(last);
    }
    if segments.is_empty() {
        segments.push(cmd);
    }
    segments
}

/// 检测单个命令段（管道链只检查第一个命令）
fn check_single_segment(segment: &str) -> Option<&'static str> {
    // 取管道链的第一个命令
    let first_cmd = split_at_pipe(segment);
    let tokens = shell_words(first_cmd);
    if tokens.is_empty() {
        return None;
    }

    let first = tokens[0].as_str();

    // ---- SSH / 远程登录 ----
    if first == "ssh" {
        // ssh host 'command' 形式放行：至少3个token（ssh + host + command）
        // ssh -p 22 host 'command' 也放行
        // 裸 ssh / ssh host 是交互式，拦截
        let non_flag_args: Vec<&String> = tokens
            .iter()
            .skip(1)
            .filter(|t| !t.starts_with('-'))
            .collect();
        // 需要 host + command 至少2个非flag参数才算非交互
        if non_flag_args.len() >= 2 {
            return None;
        }
        return Some(
            "SSH 是交互式会话，不支持前台运行。如需远程执行命令，请用 ssh host 'command' 形式并设置 run_in_background: true",
        );
    }
    if first == "telnet" || first == "mosh" {
        return Some(
            "telnet/mosh 是交互式会话，不支持前台运行。如需远程执行命令，请用 ssh host 'command' 形式并设置 run_in_background: true",
        );
    }

    // ---- 编辑器 ----
    if matches!(first, "vim" | "vi" | "nano" | "emacs" | "micro" | "pico") {
        // 编辑器没有任何非交互用法，直接拦截
        return Some(
            "交互式编辑器不支持前台运行。请使用 Edit/Write 工具编辑文件，或使用 sed 进行文本替换",
        );
    }
    if first == "code" {
        // code --diff, code --version 等非交互用法放行
        let has_non_interactive_flag = tokens.iter().skip(1).any(|t| {
            t.starts_with("--diff")
                || t.starts_with("--version")
                || t.starts_with("--list-extensions")
                || t.starts_with("--install-extension")
                || t.starts_with("--uninstall-extension")
        });
        if !has_non_interactive_flag {
            return Some(
                "交互式编辑器不支持前台运行。请使用 Edit/Write 工具编辑文件，或使用 sed 进行文本替换",
            );
        }
        return None;
    }

    // ---- 分页器 ----
    if matches!(first, "less" | "more" | "most") {
        return Some(
            "分页器不支持前台运行。请直接运行命令（输出会自动捕获），或使用 Read 工具查看文件",
        );
    }

    // ---- REPL / 交互式 shell ----
    if matches!(first, "ipython" | "pry" | "groovysh") {
        // 这些几乎没有非交互用法
        return Some(
            "交互式 REPL 不支持前台运行。请用 -c 参数执行单条命令，或设置 run_in_background: true",
        );
    }
    if matches!(first, "python" | "python3" | "python2") {
        // 有 -c / 文件参数则放行
        let has_script = tokens
            .iter()
            .skip(1)
            .any(|t| t == "-c" || t == "-m" || !t.starts_with('-'));
        if !has_script {
            return Some(
                "交互式 Python REPL 不支持前台运行。请用 -c 参数执行单条命令（如 python3 -c 'code'），或设置 run_in_background: true",
            );
        }
        return None;
    }
    if first == "node" {
        // node -e / node script.js 放行
        let has_script = tokens
            .iter()
            .skip(1)
            .any(|t| t == "-e" || t == "--eval" || !t.starts_with('-'));
        if !has_script {
            return Some(
                "交互式 Node REPL 不支持前台运行。请用 -e 参数执行单条命令（如 node -e 'code'），或设置 run_in_background: true",
            );
        }
        return None;
    }
    if first == "irb" {
        return Some(
            "交互式 Ruby REPL 不支持前台运行。请用 ruby -e 'code' 执行单条命令，或设置 run_in_background: true",
        );
    }
    if first == "lua" {
        // lua script.lua 或 lua -e 放行
        let has_script = tokens
            .iter()
            .skip(1)
            .any(|t| t == "-e" || !t.starts_with('-'));
        if !has_script {
            return Some(
                "交互式 Lua REPL 不支持前台运行。请用 -e 参数执行单条命令，或设置 run_in_background: true",
            );
        }
        return None;
    }
    if first == "php" {
        // php -a 是交互式，php script.php / php -r 放行
        if tokens
            .iter()
            .skip(1)
            .any(|t| t == "-a" || t == "--interactive")
        {
            return Some(
                "交互式 PHP REPL 不支持前台运行。请用 -r 参数执行单条命令，或设置 run_in_background: true",
            );
        }
        return None;
    }
    if first == "r" || first == "R" {
        // R CMD batch / Rscript 放行，裸 R 拦截
        if tokens.len() > 1 && (tokens[1] == "CMD" || tokens[1] == "cmd") {
            return None;
        }
        return Some(
            "交互式 R 不支持前台运行。请用 R CMD batch 或 Rscript 运行脚本，或设置 run_in_background: true",
        );
    }
    if first == "scala" {
        // scala -e / scala script.scala 放行
        let has_script = tokens
            .iter()
            .skip(1)
            .any(|t| t == "-e" || !t.starts_with('-'));
        if !has_script {
            return Some(
                "交互式 Scala REPL 不支持前台运行。请用 -e 参数执行单条命令，或设置 run_in_background: true",
            );
        }
        return None;
    }

    // ---- 监控/持续运行 ----
    if matches!(first, "top" | "htop" | "btop" | "glances") {
        return Some(
            "持续监控命令不支持前台运行。请用单次快照方式执行（如 ps aux），或设置 run_in_background: true",
        );
    }
    if first == "watch" {
        // watch 没有非交互用法
        return Some(
            "watch 持续刷新不支持前台运行。请直接执行命令获取单次输出，或设置 run_in_background: true",
        );
    }

    // ---- 调试器 ----
    if matches!(first, "gdb" | "lldb" | "pdb") {
        // pdb 可能被 python -m pdb 调用，但首词是 pdb 就拦截
        // gdb/lldb 有 --batch 等非交互模式
        if first == "gdb" && tokens.iter().any(|t| t == "--batch" || t == "-batch") {
            return None;
        }
        if first == "lldb"
            && tokens
                .iter()
                .any(|t| t == "--batch" || t == "-batch" || t == "-o")
        {
            return None;
        }
        return Some(
            "调试器不支持前台运行。请使用 --batch 非交互模式，或设置 run_in_background: true",
        );
    }
    if matches!(first, "strace" | "ltrace") {
        // strace -p / strace cmd 是非交互的，放行
        return None;
    }

    // ---- 包管理器 ----
    if matches!(first, "apt" | "apt-get" | "yum" | "dnf" | "pacman") {
        // 检测是否带 -y 标志
        let has_yes = tokens
            .iter()
            .any(|t| t == "-y" || t == "--yes" || t == "--assumeyes" || t == "--noconfirm");
        if !has_yes {
            return Some(
                "包管理器通常需要交互确认。请加 -y/--yes 标志（如 apt-get install -y pkg），或设置 run_in_background: true",
            );
        }
        return None;
    }
    if first == "brew" {
        // brew 很少真正阻塞，大部分命令自动执行；仅对 brew install 等无 -y 时提示
        // Homebrew 默认不需要确认，放行
        return None;
    }

    // ---- Docker 交互 ----
    if first == "docker" {
        // docker run -it / docker exec -it 拦截
        let has_it = tokens
            .iter()
            .any(|t| t == "-it" || t == "-ti" || t == "-i" || t == "--interactive");
        if has_it {
            let subcmd = tokens.get(1).map(|s| s.as_str()).unwrap_or("");
            if matches!(subcmd, "run" | "exec") {
                return Some(
                    "交互式 Docker 命令不支持前台运行。请去掉 -i/-t 标志，或设置 run_in_background: true",
                );
            }
        }
        return None;
    }

    None
}

/// 取管道链的第一个命令（第一个 | 之前的部分，忽略引号内的管道符）
fn split_at_pipe(segment: &str) -> &str {
    let mut in_single = false;
    let mut in_double = false;
    for (i, c) in segment.char_indices() {
        match c {
            '\'' if !in_double => in_single = !in_single,
            '"' if !in_single => in_double = !in_double,
            '|' if !in_single && !in_double => return segment[..i].trim(),
            _ => {}
        }
    }
    segment.trim()
}

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

    // ========== is_dangerous_command 测试 ==========

    #[test]
    fn test_dangerous_mkfs() {
        assert!(is_dangerous_command("mkfs.ext4 /dev/sda1"));
        assert!(is_dangerous_command("mkfs /dev/sda1"));
    }

    #[test]
    fn test_dangerous_dd() {
        assert!(is_dangerous_command("dd if=/dev/zero of=/dev/sda"));
        assert!(is_dangerous_command("dd if=/dev/zero of=/dev/nvme0n1"));
        // dd 写入 /dev/null 安全
        assert!(!is_dangerous_command(
            "dd if=/dev/zero of=/dev/null bs=1M count=100"
        ));
        // dd 写入普通文件安全
        assert!(!is_dangerous_command("dd if=input.img of=output.img"));
    }

    #[test]
    fn test_dangerous_fork_bomb() {
        assert!(is_dangerous_command(":(){:|:&};:"));
        assert!(is_dangerous_command(":(){ :|:& };:"));
    }

    #[test]
    fn test_dangerous_chmod() {
        // chmod -R 777 / 危险
        assert!(is_dangerous_command("chmod -R 777 /"));
        // chmod -R 777 /home/user 安全（非根目录）
        assert!(!is_dangerous_command("chmod -R 777 /home/user"));
        // 普通 chmod 安全
        assert!(!is_dangerous_command("chmod 755 /usr/local/bin/app"));
    }

    #[test]
    fn test_dangerous_chown() {
        // chown -R root / 危险
        assert!(is_dangerous_command("chown -R root /"));
        // chown -R user ./dir 安全
        assert!(!is_dangerous_command("chown -R user ./dir"));
        // chown -R user /home/user 安全
        assert!(!is_dangerous_command("chown -R user /home/user"));
    }

    #[test]
    fn test_dangerous_rm() {
        // rm -rf / 危险
        assert!(is_dangerous_command("rm -rf /"));
        assert!(is_dangerous_command("rm -rf /*"));
        // rm -rf /aaa/bbb 安全
        assert!(!is_dangerous_command("rm -rf /aaa/bbb"));
        assert!(!is_dangerous_command("rm -rf /tmp/build"));
        // rm 普通文件安全
        assert!(!is_dangerous_command("rm /tmp/test.txt"));
    }

    #[test]
    fn test_dangerous_curl_pipe() {
        assert!(is_dangerous_command("curl http://x.com | sh"));
        assert!(is_dangerous_command("curl http://x.com | bash"));
        assert!(is_dangerous_command("wget -O- http://x.com | sh"));
        // 普通 curl 安全
        assert!(!is_dangerous_command("curl http://x.com/api/data"));
        assert!(!is_dangerous_command("curl http://x.com | jq '.name'"));
    }

    #[test]
    fn test_dangerous_alias() {
        // 裸 alias 拦截
        assert!(is_dangerous_command("alias"));
        // alias 赋值在 bash -c 无意义但不应崩溃——放行
        assert!(!is_dangerous_command("alias ll='ls -la'"));
    }

    #[test]
    fn test_dangerous_not_triggered() {
        // 日常安全命令
        assert!(!is_dangerous_command("ls -la"));
        assert!(!is_dangerous_command("git status"));
        assert!(!is_dangerous_command("cargo build"));
        assert!(!is_dangerous_command("rm -rf /tmp/test"));
        assert!(!is_dangerous_command("grep -r pattern src/"));
    }

    // ========== check_blocking_command 测试 ==========

    #[test]
    fn test_blocking_ssh() {
        // 裸 SSH 阻塞
        assert!(check_blocking_command("ssh user@host").is_some());
        // ssh 带远程命令放行
        assert!(check_blocking_command("ssh user@host 'ls -la'").is_none());
    }

    #[test]
    fn test_blocking_editors() {
        assert!(check_blocking_command("vim file.txt").is_some());
        assert!(check_blocking_command("vi file.txt").is_some());
        assert!(check_blocking_command("nano file.txt").is_some());
        assert!(check_blocking_command("emacs file.txt").is_some());
        // code 非交互标志放行
        assert!(check_blocking_command("code --diff a.txt b.txt").is_none());
        assert!(check_blocking_command("code --version").is_none());
        assert!(check_blocking_command("code --install-extension ms-python.python").is_none());
        // code 无标志阻塞
        assert!(check_blocking_command("code .").is_some());
    }

    #[test]
    fn test_blocking_pagers() {
        assert!(check_blocking_command("less file.txt").is_some());
        assert!(check_blocking_command("more file.txt").is_some());
    }

    #[test]
    fn test_blocking_python() {
        // 裸 python 阻塞
        assert!(check_blocking_command("python3").is_some());
        assert!(check_blocking_command("python").is_some());
        // python -c 放行
        assert!(check_blocking_command("python3 -c 'print(1)'").is_none());
        // python script.py 放行
        assert!(check_blocking_command("python3 main.py").is_none());
        // python -m 放行
        assert!(check_blocking_command("python3 -m pytest").is_none());
    }

    #[test]
    fn test_blocking_node() {
        // 裸 node 阻塞
        assert!(check_blocking_command("node").is_some());
        // node -e 放行
        assert!(check_blocking_command("node -e 'console.log(1)'").is_none());
        // node script.js 放行
        assert!(check_blocking_command("node app.js").is_none());
    }

    #[test]
    fn test_blocking_php() {
        // php -a 阻塞
        assert!(check_blocking_command("php -a").is_some());
        // php script.php 放行
        assert!(check_blocking_command("php script.php").is_none());
        // php -r 放行
        assert!(check_blocking_command("php -r 'echo 1;'").is_none());
    }

    #[test]
    fn test_blocking_r() {
        // 裸 R 阻塞
        assert!(check_blocking_command("R").is_some());
        // R CMD 放行
        assert!(check_blocking_command("R CMD batch script.R").is_none());
    }

    #[test]
    fn test_blocking_lua() {
        assert!(check_blocking_command("lua").is_some());
        assert!(check_blocking_command("lua script.lua").is_none());
        assert!(check_blocking_command("lua -e 'print(1)'").is_none());
    }

    #[test]
    fn test_blocking_top() {
        assert!(check_blocking_command("top").is_some());
        assert!(check_blocking_command("htop").is_some());
        assert!(check_blocking_command("watch ls").is_some());
    }

    #[test]
    fn test_blocking_debuggers() {
        assert!(check_blocking_command("gdb ./a.out").is_some());
        assert!(check_blocking_command("lldb ./a.out").is_some());
        // gdb --batch 放行
        assert!(check_blocking_command("gdb --batch -ex run ./a.out").is_none());
        // lldb -o 放行
        assert!(check_blocking_command("lldb -o run ./a.out").is_none());
        // strace 非交互，放行
        assert!(check_blocking_command("strace ls").is_none());
    }

    #[test]
    fn test_blocking_package_managers() {
        // 无 -y 阻塞
        assert!(check_blocking_command("apt-get install pkg").is_some());
        assert!(check_blocking_command("apt install pkg").is_some());
        assert!(check_blocking_command("yum install pkg").is_some());
        // 带 -y 放行
        assert!(check_blocking_command("apt-get install -y pkg").is_none());
        assert!(check_blocking_command("apt install -y pkg").is_none());
        assert!(check_blocking_command("yum install -y pkg").is_none());
        // brew 默认放行
        assert!(check_blocking_command("brew install pkg").is_none());
    }

    #[test]
    fn test_blocking_docker() {
        // docker run -it 阻塞
        assert!(check_blocking_command("docker run -it ubuntu bash").is_some());
        assert!(check_blocking_command("docker exec -it container_id bash").is_some());
        // docker run 无 -it 放行
        assert!(check_blocking_command("docker run ubuntu echo hello").is_none());
        assert!(check_blocking_command("docker ps").is_none());
        assert!(check_blocking_command("docker build -t img .").is_none());
    }

    #[test]
    fn test_blocking_safe_commands() {
        // 安全命令不触发
        assert!(check_blocking_command("ls -la").is_none());
        assert!(check_blocking_command("git status").is_none());
        assert!(check_blocking_command("cargo build").is_none());
        assert!(check_blocking_command("echo hello").is_none());
        assert!(check_blocking_command("ps aux").is_none());
    }

    #[test]
    fn test_blocking_pipeline() {
        // 管道中第一个命令为阻塞命令应检测到
        assert!(check_blocking_command("vim file.txt | cat").is_some());
        // 管道中第一个命令安全
        assert!(check_blocking_command("echo hello | less").is_none());
    }

    #[test]
    fn test_blocking_semicolon() {
        // 分号后跟阻塞命令也应检测到
        assert!(check_blocking_command("echo hello; vim file.txt").is_some());
        // 两个安全命令
        assert!(check_blocking_command("echo hello; echo world").is_none());
    }

    // ========== shell_words 测试 ==========

    #[test]
    fn test_shell_words_basic() {
        assert_eq!(shell_words("ls -la /tmp"), vec!["ls", "-la", "/tmp"]);
    }

    #[test]
    fn test_shell_words_quotes() {
        assert_eq!(
            shell_words("echo 'hello world'"),
            vec!["echo", "hello world"]
        );
        assert_eq!(
            shell_words("echo \"hello world\""),
            vec!["echo", "hello world"]
        );
    }

    #[test]
    fn test_shell_words_mixed() {
        assert_eq!(
            shell_words("python3 -c 'import os; print(os.getcwd())'"),
            vec!["python3", "-c", "import os; print(os.getcwd())"]
        );
    }
}
