//! 路径授权预校验（文件工具路径提取 + 白/黑名单判定）。
//!
//! 第六批从 audit/mod.rs 抽离，行为零变更。包含：
//! - `extract_file_tool_paths`：从工具参数中提取文件路径
//! - `FileToolAuthOutcome`：授权判定结果枚举
//! - `check_file_tool_auth`：对单条文件工具调用做路径授权预校验
//!
//! process_tool_calls 通过 `use path_auth::check_file_tool_auth` 裸名调用。
//! 测试模块通过 `super::check_file_tool_auth` 引用。

use super::PathAuthRequest;

/// 提取文件工具的路径参数（用于路径授权预校验）。
///
/// 仅对走 resolve_workspace_path 校验的文件工具返回路径；非文件工具返回空 Vec（不预校验）。
/// - 单路径工具（read_file/write_file/list_directory/patch_file/file_info/append_file/delete_file/search_files/grep）
///   取 args["path"]
/// - rename_file 双路径取 args["from"] + args["to"](两个都需授权)
/// - run_command 不返回（它只走 validate_path 黑名单，不限定 workspace，High risk 靠人工审批）
pub(crate) fn extract_file_tool_paths(tool_name: &str, args: &serde_json::Value) -> Vec<String> {
    match tool_name {
        "rename_file" => {
            let mut v = Vec::new();
            if let Some(from) = args.get("from").and_then(|x| x.as_str()) {
                v.push(from.to_string());
            }
            if let Some(to) = args.get("to").and_then(|x| x.as_str()) {
                v.push(to.to_string());
            }
            v
        }
        "read_file" | "write_file" | "list_directory" | "patch_file" | "file_info"
        | "append_file" | "delete_file" | "search_files" | "grep" => {
            args.get("path").and_then(|x| x.as_str()).map(|s| vec![s.to_string()]).unwrap_or_default()
        }
        _ => Vec::new(),
    }
}

/// 路径授权预校验结果。
pub(crate) enum FileToolAuthOutcome {
    /// 全部已授权 → 走原 Low/Med/High 流程
    Authorized,
    /// 未授权 → 附带 PathAuthRequest，供挂起弹窗
    NeedsAuth(PathAuthRequest),
    /// 命中黑名单 → 硬拒
    Denied(String),
}

/// 对单条文件工具调用做路径授权预校验，返回是否需挂起/拒绝/放行。
///
/// - 路径任一命中黑名单 → `Denied(reason)`：硬拒（工具返 Err tool_result，不挂起）
/// - 路径任一未命中白名单（persistent + session_dirs）且非黑名单 → `NeedsAuth`：
///   附带 PathAuthRequest（规范化父目录 + 原始路径列表），供挂起弹窗
/// - 全部已授权 → `Authorized`：走原 Low/Med/High 流程
pub(crate) fn check_file_tool_auth(
    tool_name: &str,
    args: &serde_json::Value,
    allowed: &crate::state::AllowedDirs,
) -> FileToolAuthOutcome {
    use crate::state::{check_path_authorization, PathAuthDecision};
    let paths = extract_file_tool_paths(tool_name, args);
    if paths.is_empty() {
        // 非文件工具或无路径参数（如缺 path 的异常调用）→ 放行，走原流程（handler 内会因缺参 Err）
        return FileToolAuthOutcome::Authorized;
    }
    // L1 补丁（rename_file 双路径漏校）：收集**所有**未授权父目录（去重），
    // 不只首个。rename_file 的 from/to 若分属不同未授权目录，两个 dir 都需挂起授权。
    let mut pending_dirs: Vec<std::path::PathBuf> = Vec::new();
    for p in &paths {
        match check_path_authorization(p, allowed) {
            PathAuthDecision::Denied { reason } => return FileToolAuthOutcome::Denied(reason),
            PathAuthDecision::NeedsAuthorization { dir } => {
                if !pending_dirs.contains(&dir) {
                    pending_dirs.push(dir);
                }
            }
            PathAuthDecision::Authorized => {}
        }
    }
    if pending_dirs.is_empty() {
        FileToolAuthOutcome::Authorized
    } else if tool_name == "search_files" {
        // 核心设计5：search_files 兜底 — 盲搜语义应拒不应询。
        // search_files 仅在已授权目录可用；用户已通过 @项目 提供项目上下文，无需搜索文件系统。
        // （黑名单已在上方 for 循环内短路返 Denied，走到此处均为 NeedsAuthorization 场景。）
        // 其他文件工具保持原 NeedsAuth 弹窗逻辑完全不变。
        FileToolAuthOutcome::Denied(
            "search_files 仅在已授权目录可用；请用 @[项目] 引用或提供绝对路径，不要盲搜文件系统。".to_string(),
        )
    } else {
        FileToolAuthOutcome::NeedsAuth(PathAuthRequest { dirs: pending_dirs, raw_paths: paths })
    }
}
