//! AST 代码智能 — tree-sitter 符号解析(read_symbol 三态)
//!
//! 治 aichat read_file 全文回灌 prompt 爆(信息密度驱动 ≠ 压缩,见设计文档):
//! read_symbol 按语义层级返符号 **骨架**(默认)/ **全文**(full),替代物理读全文件。
//! LLM 默认拿骨架(签名 + 行范围 + 内部调用)→ 要细节再 full 取完整定义体 → 进 prompt 的都是主动要的,天然高密度。
//! 设计:docs/02-架构设计/专项设计/AST符号解析-设计-2026-06-24.md
//!
//! **不报错底线**:无 grammar / 解析失败 / symbol 未找到 / Vue 无 script / 任何大小函数 →
//! 返兜底提示(grep + read_file),不 Err / 不 panic / 不阻断。

use std::collections::HashSet;

use tree_sitter::{Language, Node, Parser};

/// Phase1 支持的定义节点 kind(Rust + TS/JS 通用,Vue 借 TS)。
///
/// tree-sitter 各 grammar 的定义节点 kind 基本一致:
/// - Rust:function_item / struct_item / impl_item 等(`_item` 后缀)
/// - TS/JS:function_declaration / class_declaration / interface_declaration / method_definition 等
/// 统一放一张表(find_definition 命中即提取),跨语言零分支。
const DEFINITION_KINDS: &[&str] = &[
    // Rust
    "function_item",
    "function_signature_item",
    "struct_item",
    "enum_item",
    "impl_item",
    "trait_item",
    "mod_item",
    "const_item",
    "static_item",
    "type_item",
    "macro_definition",
    // TS / JS(与 Rust kind 不冲突)
    "function_declaration",
    "generator_function_declaration",
    "class_declaration",
    "abstract_class_declaration",
    "interface_declaration",
    "enum_declaration",
    "method_definition",
    // TS/JS const/let/var 声明(如 const fn = () => {})
    "variable_declarator",
    // Go
    "method_declaration", // Go method(receiver func)
    "type_declaration",   // Go type Xxx struct/interface/func
    // Java
    "constructor_declaration",          // Java constructor
    "annotation_type_declaration",      // @interface
    "record_declaration",               // Java record(Java 16+)
    // Python(tree-sitter-python 用 _definition 后缀)
    "function_definition",
    "class_definition",
];

/// 文件后缀 → tree-sitter grammar 集中 lookup(静态编译,不动态加载 / 不抽 trait)。
///
/// 设计(grammar 策略 2026-06-24 定稿,详见插件机制设计文档):
/// **6 个 grammar 覆盖 9 类文件**,架构成本最低。
/// - `.rs` → tree-sitter-rust
/// - `.ts` → tree-sitter-typescript(language_typescript)
/// - `.tsx` → tree-sitter-typescript(language_tsx)
/// - `.js` / `.jsx` → tree-sitter-javascript
/// - `.go` → tree-sitter-go
/// - `.java` → tree-sitter-java
/// - `.py` → tree-sitter-python
/// - `.vue` → 返回 None 标记"需切 script 段"(Vue 借 TS,逻辑在 script,read_symbol 内 extract_vue_script 切段喂 ts)
///
/// 未来加语言 = 此处加一行 + 可能补 DEFINITION_KINDS(表驱动,~几行),不返工调用方。
/// tree-sitter 动态加载技术上可行(Helix/Neovim 先例)但跨平台分发 + ABI 坑,当前不值(见插件设计)。
pub fn grammar_for(ext: &str) -> Option<Language> {
    match ext {
        "rs" => Some(Language::from(tree_sitter_rust::LANGUAGE)),
        "ts" => Some(Language::from(tree_sitter_typescript::LANGUAGE_TYPESCRIPT)),
        "tsx" => Some(Language::from(tree_sitter_typescript::LANGUAGE_TSX)),
        "js" | "jsx" => Some(Language::from(tree_sitter_javascript::LANGUAGE)),
        "go" => Some(Language::from(tree_sitter_go::LANGUAGE)),
        "java" => Some(Language::from(tree_sitter_java::LANGUAGE)),
        "py" => Some(Language::from(tree_sitter_python::LANGUAGE)),
        // Vue 借 TS:返 None 标记需切 script 段(read_symbol 内 extract_vue_script + 喂 ts)
        "vue" => None,
        _ => None,
    }
}

/// 判断 ext 是否为支持语言(含 Vue 借 TS)。
///
/// 预留:当前 handler 直接调 read_symbol(内部已兜底 ext 判断),本函数供测试 + 未来
/// handler 层提前判 ext 省一次 fs read(不支持直接返兜底不读文件)。测试消费,非真死代码。
#[allow(dead_code)]
pub fn is_supported_ext(ext: &str) -> bool {
    matches!(ext, "rs" | "ts" | "tsx" | "js" | "jsx" | "vue" | "go" | "java" | "py")
}

/// 提取 Vue SFC 的 `<script>` 段内容(借 TS 解析)。
///
/// Vue 单文件组件逻辑在 `<script setup lang="ts">` 内,template/style 不做符号提取。
/// 切出 `<script>...</script>` 内容喂 tree-sitter-typescript:
/// - 省 1 个 grammar(tree-sitter-vue 不引,降依赖体积)
/// - 避 SFC 嵌套复杂度(tree-sitter-vue 解析 template/style/script 混合,符号提取只关心 script)
///
/// 多个 script 段取首个(`<script setup>` 主逻辑);无 script 返 None(走兜底)。
/// 返回 (script 内容, script 在原 source 中的字节起始偏移)用于行号映射。
fn extract_vue_script(source: &str) -> Option<(String, usize)> {
    let start_tag = source.find("<script")?;
    // 跳过 <script ...> 的属性(到 >),取标签体开始
    let tag_close = source[start_tag..].find('>')? + start_tag + 1;
    let end = source[tag_close..].find("</script>")?;
    Some((source[tag_close..tag_close + end].to_string(), tag_close))
}

/// read_symbol 三态读取(信息密度驱动)。
///
/// **不按物理行返、不截断**,按语义结构层级返(消解"截断多少行"争论):
/// - `full=false`(默认 骨架态):返签名 + kind + 行范围 + 内部调用列表 + 总行数(极小,高密度)
/// - `full=true`(全文态):返完整定义体(语法边界 100% 准)
///
/// **不报错底线**:无 grammar / 解析失败 / symbol 未找到 / Vue 无 script → 返兜底提示(grep + read_file),不 panic。
///
/// 参数:
/// - `source`:文件全文(由 handler 读 + 算 hash 后传入,纯函数不碰 fs)
/// - `file_hash`:文件 hash(handler 算,回传 LLM 供后续定位 / 兜底回取)
/// - `path`:路径(取 ext + 兜底消息展示)
/// - `symbol`:目标符号名(精确匹配定义节点的 name field)
/// - `full`:true=完整定义体,false=骨架
/// - `kind_hint`:可选 kind 过滤(如 "function"/"struct"),None 匹配任意定义 kind
/// - `drill`:可选子定义名(如 "parse");Some(name)=在目标符号节点子树内递归找名为 name 的
///   嵌套定义(局部函数/impl 方法),命中即返该子定义的 full 态;未命中走兜底(reason=drill_not_found)。
///   None=不下钻,走原 full/kind_hint 三态逻辑。
pub fn read_symbol(
    source: &str,
    file_hash: &str,
    path: &str,
    symbol: &str,
    full: bool,
    kind_hint: Option<&str>,
    drill: Option<&str>,
) -> serde_json::Value {
    let ext = path.rsplit('.').next().unwrap_or("").to_lowercase();
    let is_vue = ext == "vue";

    // Vue 借 TS:切 script 段(Vue 逻辑在 script,template/style 不做符号)
    let (parse_source, parse_ext, line_offset): (String, &str, usize) = if is_vue {
        match extract_vue_script(source) {
            Some((script, byte_off)) => {
                // Vue 切段后节点行号是 script 内相对行,加原文件中 script 起始行偏移映射回原行号
                let off_lines = source[..byte_off].matches('\n').count();
                (script, "ts", off_lines)
            }
            None => return fallback(symbol, path, file_hash, "vue_no_script",
                "Vue 文件无 <script> 段(纯 template/style 无可提取符号)"),
        }
    } else {
        (source.to_string(), ext.as_str(), 0)
    };

    let grammar = match grammar_for(parse_ext) {
        Some(g) => g,
        None => {
            return fallback(symbol, path, file_hash, "no_grammar",
                &format!("不支持 {} 文件符号解析(支持 .rs/.ts/.tsx/.js/.jsx/.vue/.go/.java/.py)", ext));
        }
    };

    let mut parser = Parser::new();
    if parser.set_language(&grammar).is_err() {
        return fallback(symbol, path, file_hash, "parse_failed",
            "grammar 加载失败(tree-sitter 版本/ABI 不兼容,疑似环境问题)");
    }
    let tree = match parser.parse(&parse_source, None) {
        Some(t) => t,
        None => return fallback(symbol, path, file_hash, "parse_failed", "解析失败(源码可能有语法错误)"),
    };

    let root = tree.root_node();
    let mut found: Option<Node> = None;
    find_definition(&root, symbol, kind_hint, parse_source.as_bytes(), &mut found);

    // drill:在目标符号节点子树内递归找名为 drill 的嵌套定义(局部函数/impl 方法)。
    // 命中即返该子定义 full 态;未命中走兜底(reason=drill_not_found,不 panic)。
    if let Some(drill_name) = drill {
        if let Some(sym_node) = found {
            let mut drill_found: Option<Node> = None;
            find_definition(&sym_node, drill_name, None, parse_source.as_bytes(), &mut drill_found);
            return match drill_found {
                Some(dn) => extract_symbol(dn, parse_source.as_bytes(), source, path, file_hash, true, line_offset),
                None => fallback(drill_name, path, file_hash, "drill_not_found",
                    &format!("符号「{}」内未找到下钻子定义「{}」(可能非嵌套定义/作用域差异)", symbol, drill_name)),
            };
        }
        // found 本身为 None 时 drill 无意义,落入下方 not_found 兜底
    }

    match found {
        Some(node) => extract_symbol(node, parse_source.as_bytes(), source, path, file_hash, full, line_offset),
        None => fallback(symbol, path, file_hash, "not_found",
            &format!("文件内未找到符号「{}」(可能拼写/作用域差异,试 grep 搜定义)", symbol)),
    }
}

/// 递归遍历找匹配 symbol 名的定义节点。
///
/// 命中条件:node.kind() 在 DEFINITION_KINDS 内 + name field 文本 == symbol + kind_hint(若传)匹配。
/// 首个命中即返(find_definition 调用方短路)。
fn find_definition<'a>(
    node: &Node<'a>,
    symbol: &str,
    kind_hint: Option<&str>,
    source: &[u8],
    found: &mut Option<Node<'a>>,
) {
    if found.is_some() {
        return;
    }
    if DEFINITION_KINDS.contains(&node.kind()) {
        let kind_ok = kind_hint.map_or(true, |h| kind_matches(node.kind(), h));
        if kind_ok {
            if let Some(name_node) = node.child_by_field_name("name") {
                let name = node_text(name_node, source);
                if name == symbol {
                    *found = Some(*node);
                    return;
                }
            }
        }
    }
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        find_definition(&child, symbol, kind_hint, source, found);
        if found.is_some() {
            return;
        }
    }
}

/// kind_hint 宽松匹配:user 传 "function" 命中 function_item/function_declaration/method_definition 等。
fn kind_matches(node_kind: &str, hint: &str) -> bool {
    let h = hint.to_lowercase();
    node_kind.contains(&h)
        || (h == "function" && (node_kind.contains("function") || node_kind == "method_definition"))
        || (h == "struct" && node_kind.contains("struct"))
        || (h == "class" && node_kind.contains("class"))
        || (h == "enum" && node_kind.contains("enum"))
}

/// 取节点文本(字节切片避免 utf8_text 的 lifetime/Result 坑)。
/// 返 owned String(符号体不大,避免借用 lifetime 纠缠调用方)。
fn node_text(node: Node<'_>, source: &[u8]) -> String {
    std::str::from_utf8(&source[node.start_byte()..node.end_byte()])
        .unwrap_or("")
        .to_string()
}

/// 提取符号三态信息,组装返回 Value。
fn extract_symbol(
    node: Node<'_>,
    source: &[u8],
    _orig_source: &str,
    path: &str,
    file_hash: &str,
    full: bool,
    line_offset: usize,
) -> serde_json::Value {
    let name = node
        .child_by_field_name("name")
        .map(|n| node_text(n, source).to_string())
        .unwrap_or_default();
    let kind = node.kind().to_string();

    // 行号:script 内相对行(0-based)+ Vue 偏移 → 原文件 1-based 行
    let start_row = node.start_position().row + 1 + line_offset;
    let end_row = node.end_position().row + 1 + line_offset;
    let body_lines = end_row - start_row + 1;

    let node_src = node_text(node, source);
    let signature = extract_signature(&node_src);
    let calls = collect_calls(node, source, line_offset);

    if full {
        serde_json::json!({
            "symbol": name,
            "kind": kind,
            "path": path,
            "file_hash": file_hash,
            "line_start": start_row,
            "line_end": end_row,
            "body_lines": body_lines,
            "signature": signature,
            "calls": calls,
            "mode": "full",
            "content": node_src,
        })
    } else {
        serde_json::json!({
            "symbol": name,
            "kind": kind,
            "path": path,
            "file_hash": file_hash,
            "line_start": start_row,
            "line_end": end_row,
            "body_lines": body_lines,
            "signature": signature,
            "calls": calls,
            "mode": "skeleton",
            "hint": "骨架态:传 full=true 取完整定义体(语法边界精准);传 drill=\"子定义名\" 下钻符号内嵌套定义(局部函数/impl 方法)",
        })
    }
}

/// 提取签名:节点文本到首个 `{`(函数/结构体体开始)或 `;`(声明)之前的部分。
/// 无 `{`/`;` 取首行。trim 收尾空白 + 注释。
fn extract_signature(node_src: &str) -> String {
    let cut = node_src
        .find('{')
        .or_else(|| node_src.find(';'))
        .unwrap_or_else(|| node_src.len());
    let sig = node_src[..cut].trim();
    if sig.is_empty() {
        node_src.lines().next().unwrap_or("").trim().to_string()
    } else {
        sig.to_string()
    }
}

/// 收集节点内所有 call_expression 的被调用者(同文件静态调用点)。
///
/// Phase1:扁平收集去重(Phase2 按分支结构组织调用图)。
/// Rust/TS/JS 的 call_expression 都有 "function" field(callee),统一提取。
/// Phase2:每项带行号(call_expression 所在行,1-based + Vue 偏移,与 start_row 同口径)。
fn collect_calls(node: Node, source: &[u8], line_offset: usize) -> Vec<serde_json::Value> {
    let mut calls = Vec::new();
    let mut seen = HashSet::new();
    collect_calls_inner(node, source, line_offset, &mut calls, &mut seen);
    calls
}

fn collect_calls_inner(
    node: Node,
    source: &[u8],
    line_offset: usize,
    out: &mut Vec<serde_json::Value>,
    seen: &mut HashSet<String>,
) {
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        if child.kind() == "call_expression" {
            if let Some(func) = child.child_by_field_name("function") {
                let name = node_text(func, source).to_string();
                if !name.is_empty() && seen.insert(name.clone()) {
                    // 行号:调用语句所在行(call_expression 节点本身的 start_position),1-based + Vue 偏移
                    let line = child.start_position().row + 1 + line_offset;
                    out.push(serde_json::json!({ "name": name, "line": line }));
                }
            }
        }
        collect_calls_inner(child, source, line_offset, out, seen);
    }
}

/// 兜底返回(不报错底线):任何失败路径统一返此结构,LLM 据 fallback/suggestion 回退 grep/read_file。
fn fallback(symbol: &str, path: &str, file_hash: &str, reason: &str, suggestion: &str) -> serde_json::Value {
    serde_json::json!({
        "symbol": symbol,
        "path": path,
        "file_hash": file_hash,
        "fallback": true,
        "reason": reason,
        "suggestion": suggestion,
        "tip": "回退:用 grep(pattern=symbol) 搜定义位置 → read_file(path, offset=行号) 精准读取",
    })
}

// ============================================================
// 单测:三态提取 + 兜底 + grammar lookup + Vue 切段
// ============================================================

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

    const RUST_FIXTURE: &str = r#"use std::io;

pub fn read_symbol(path: &str) -> Result<(), io::Error> {
    let content = load(path)?;
    parse(&content);
    validate(path);
    Ok(())
}

fn parse(content: &str) {
    helper(content);
}

fn helper(_x: &str) {}

struct Config {
    name: String,
}
"#;

    #[test]
    fn test_grammar_for_all_exts() {
        assert!(grammar_for("rs").is_some());
        assert!(grammar_for("ts").is_some());
        assert!(grammar_for("tsx").is_some());
        assert!(grammar_for("js").is_some());
        assert!(grammar_for("jsx").is_some());
        // Phase1 扩展:Go / Java / Python(静态编译,表驱动加表项)
        assert!(grammar_for("go").is_some());
        assert!(grammar_for("java").is_some());
        assert!(grammar_for("py").is_some());
        // Vue 借 TS:grammar 本身返 None(切段后用 ts)
        assert!(grammar_for("vue").is_none());
        // 不支持语言
        assert!(grammar_for("").is_none());
    }

    #[test]
    fn test_is_supported_ext() {
        assert!(is_supported_ext("rs"));
        assert!(is_supported_ext("go"));
        assert!(is_supported_ext("java"));
        assert!(is_supported_ext("py"));
        assert!(is_supported_ext("vue"));
        // 不支持语言
        assert!(!is_supported_ext("rb"));
        assert!(!is_supported_ext("md"));
    }

    #[test]
    fn test_read_symbol_rust_skeleton() {
        let r = read_symbol(RUST_FIXTURE, "hash1", "test.rs", "read_symbol", false, None, None);
        assert_eq!(r["fallback"], serde_json::Value::Null);
        assert_eq!(r["symbol"], "read_symbol");
        assert_eq!(r["kind"], "function_item");
        assert_eq!(r["mode"], "skeleton");
        // 骨架态不返 content,返 signature + calls
        assert!(r["content"].is_null());
        assert!(r["signature"].as_str().unwrap().contains("read_symbol"));
        // calls 含同文件静态调用(parse/validate)+ load(跨文件也命中名,Phase1 扁平)
        // Phase2:每项是 {name, line} 对象
        let calls = r["calls"].as_array().unwrap();
        assert!(calls.iter().any(|c| c["name"] == "parse"));
        assert!(calls.iter().any(|c| c["name"] == "validate"));
        assert_eq!(r["body_lines"].as_u64().unwrap(), 6);
    }

    #[test]
    fn test_read_symbol_rust_full() {
        let r = read_symbol(RUST_FIXTURE, "hash1", "test.rs", "read_symbol", true, None, None);
        assert_eq!(r["mode"], "full");
        let content = r["content"].as_str().unwrap();
        // full 态返完整定义体(含函数体 {})
        assert!(content.contains("pub fn read_symbol"));
        assert!(content.contains("let content = load(path)?;"));
        assert!(content.trim_end().ends_with('}'));
    }

    #[test]
    fn test_read_symbol_struct() {
        let r = read_symbol(RUST_FIXTURE, "h", "test.rs", "Config", false, None, None);
        assert_eq!(r["symbol"], "Config");
        assert_eq!(r["kind"], "struct_item");
    }

    #[test]
    fn test_read_symbol_kind_hint_filter() {
        // kind_hint="struct" 时,"read_symbol"(function)不应被误匹配为 struct
        let r = read_symbol(RUST_FIXTURE, "h", "test.rs", "Config", false, Some("struct"), None);
        assert_eq!(r["symbol"], "Config");
        assert_eq!(r["kind"], "struct_item");
    }

    #[test]
    fn test_read_symbol_not_found_fallback() {
        let r = read_symbol(RUST_FIXTURE, "h", "test.rs", "nonexistent_fn", false, None, None);
        assert_eq!(r["fallback"], true);
        assert_eq!(r["reason"], "not_found");
        assert!(r["suggestion"].as_str().unwrap().contains("grep"));
    }

    #[test]
    fn test_read_symbol_no_grammar_fallback() {
        // Ruby 无 grammar(.rb 未集成 tree-sitter-ruby),走兜底。
        // 注:原用 .py 做"无 grammar"样例,Phase1 扩展加 Python 支持后失效。
        let src = "def hello\n  puts 'hi'\nend\n";
        let r = read_symbol(src, "h", "test.rb", "hello", false, None, None);
        assert_eq!(r["fallback"], true);
        assert_eq!(r["reason"], "no_grammar");
        assert!(r["suggestion"].as_str().unwrap().contains("不支持"));
    }

    #[test]
    fn test_read_symbol_vue_script_extraction() {
        let vue = r#"<template><div>{{ msg }}</div></template>

<script setup lang="ts">
function greet(name: string) {
    console.log(name);
    helper();
}

function helper() {}
</script>

<style scoped></style>
"#;
        let r = read_symbol(vue, "h", "App.vue", "greet", false, None, None);
        assert_eq!(r["fallback"], serde_json::Value::Null);
        assert_eq!(r["symbol"], "greet");
        assert_eq!(r["kind"], "function_declaration");
        // Vue 切段后行号映射回原文件(greet 在原文件第 5 行附近)
        let start = r["line_start"].as_u64().unwrap();
        assert!(start >= 4, "Vue 行号应映射回原文件(script 段偏移后),实际 {}", start);
        let calls = r["calls"].as_array().unwrap();
        // Phase2:calls 每项是 {name, line} 对象
        assert!(calls.iter().any(|c| c["name"] == "helper"));
    }

    #[test]
    fn test_read_symbol_vue_no_script_fallback() {
        let vue = "<template><div>hi</div></template>\n<style></style>";
        let r = read_symbol(vue, "h", "App.vue", "greet", false, None, None);
        assert_eq!(r["fallback"], true);
        assert_eq!(r["reason"], "vue_no_script");
    }

    /// Phase2 drill:下钻目标符号(read_symbol)内的嵌套子定义(parse)。
    /// RUST_FIXTURE 中 read_symbol 函数体内调用了 parse,但 parse 是顶层函数(非 read_symbol 子树内),
    /// 故用带嵌套局部函数的 fixture 验证:outer 内定义 inner,drill="inner" 应命中并返 full 态。
    #[test]
    fn test_read_symbol_drill_hits_nested() {
        let src = r#"
fn outer() {
    fn inner(x: i32) -> i32 {
        x + 1
    }
    inner(42)
}
"#;
        // 外层骨架态,确认 outer 能解析到
        let skeleton = read_symbol(src, "h", "t.rs", "outer", false, None, None);
        assert_eq!(skeleton["symbol"], "outer");
        // 下钻 inner:应在 outer 子树内命中,返 full 态
        let drill = read_symbol(src, "h", "t.rs", "outer", false, None, Some("inner"));
        assert_eq!(drill["fallback"], serde_json::Value::Null, "drill 命中应返符号非兜底");
        assert_eq!(drill["symbol"], "inner");
        assert_eq!(drill["mode"], "full", "drill 命中走 full 态(完整定义体)");
        let content = drill["content"].as_str().unwrap();
        assert!(content.contains("fn inner"), "drill content 应含 inner 定义体");
    }

    /// Phase2 drill 未命中兜底:下钻外层符号内不存在的子定义名 → fallback reason=drill_not_found。
    #[test]
    fn test_read_symbol_drill_not_found_fallback() {
        let src = "fn outer() {\n    fn inner() {}\n}\n";
        let drill = read_symbol(src, "h", "t.rs", "outer", false, None, Some("nonexistent"));
        assert_eq!(drill["fallback"], true);
        assert_eq!(drill["reason"], "drill_not_found");
        assert!(drill["suggestion"].as_str().unwrap().contains("nonexistent"));
    }

    #[test]
    fn test_extract_vue_script() {
        let vue = "a\nb\n<script>let x = 1;</script>";
        let (script, off) = extract_vue_script(vue).unwrap();
        assert_eq!(script, "let x = 1;");
        // script 在第 3 行(0-indexed offset 后),行偏移应 = 2(\n 数)
        assert_eq!(vue[..off].matches('\n').count(), 2);
    }

    #[test]
    fn test_extract_signature() {
        // function 带 body
        let s = extract_signature("pub fn foo(x: i32) -> i32 {\n    x + 1\n}");
        assert_eq!(s, "pub fn foo(x: i32) -> i32");
        // struct 带 body
        let s = extract_signature("struct Config {\n    name: String,\n}");
        assert_eq!(s, "struct Config");
        // 声明以 ; 结尾
        let s = extract_signature("fn foo();");
        assert_eq!(s, "fn foo()");
    }

    #[test]
    fn test_collect_calls_dedup() {
        let src = "fn a() { b(); b(); c(); }";
        let mut parser = Parser::new();
        parser.set_language(&Language::from(tree_sitter_rust::LANGUAGE)).unwrap();
        let tree = parser.parse(src, None).unwrap();
        let calls = collect_calls(tree.root_node(), src.as_bytes(), 0);
        // b 去重只出现一次(Phase2:每项是 {name, line} 对象)
        assert!(calls.iter().any(|c| c["name"] == "b"));
        assert_eq!(calls.iter().filter(|c| c["name"] == "b").count(), 1);
        assert!(calls.iter().any(|c| c["name"] == "c"));
        // line 应为调用所在行(src 单行,a 在第 1 行)
        assert_eq!(calls[0]["line"].as_u64().unwrap(), 1);
    }

    /// 多角度验证(用户要求:实现后看效果 + 多角度测试):
    /// 解析 devflow 真实源码 code_intel.rs 自身,验骨架态密度提升(设计目标:降一个量级)。
    /// 对比 read_symbol 骨架(单符号签名+calls)vs read_file 全文(整个文件所有符号)的体积。
    #[test]
    fn test_read_symbol_real_devflow_token_density() {
        let manifest = env!("CARGO_MANIFEST_DIR"); // src-tauri/
        let path_str = format!("{}/src/commands/ai/code_intel.rs", manifest);
        let source = std::fs::read_to_string(&path_str)
            .expect("应能读到 devflow 自身 code_intel.rs");
        let full_bytes = source.len();

        // 骨架态:解析自身 read_symbol 函数(只给该符号,非整个文件)
        let skeleton = read_symbol(&source, "h0", "code_intel.rs", "read_symbol", false, None, None);
        assert_ne!(skeleton["fallback"], true, "真实文件应解析出 read_symbol 符号");
        assert_eq!(skeleton["symbol"], "read_symbol");
        assert_eq!(skeleton["kind"], "function_item");
        let skeleton_str = serde_json::to_string(&skeleton).unwrap();
        let skeleton_bytes = skeleton_str.len();

        // 密度断言:read_symbol 骨架(单符号)<< read_file 全文(整个文件)。
        // 正是治 prompt 爆的核心:LLM 要理解 read_symbol,工具只给该符号骨架,不给全文件。
        let ratio = full_bytes as f64 / skeleton_bytes.max(1) as f64;
        eprintln!(
            "[密度效果] code_intel.rs 全文 {} 字节 vs read_symbol 骨架 {} 字节 → 降 {:.1}x",
            full_bytes, skeleton_bytes, ratio
        );
        assert!(ratio > 5.0, "骨架密度应显著高于全文(降 >5x),实际 {:.1}x", ratio);

        // full 态:完整定义体(语法边界 100% 准)
        let full_sym = read_symbol(&source, "h1", "code_intel.rs", "read_symbol", true, None, None);
        let content = full_sym["content"].as_str().expect("full 态应返 content");
        assert!(content.contains("pub fn read_symbol"));
        // calls 应含 read_symbol 内部调用的函数(grammar_for/extract_symbol/fallback 等)
        let calls = skeleton["calls"].as_array().expect("骨架应含 calls");
        assert!(!calls.is_empty(), "应提取出内部调用");
    }
}
