// Rust language support: parser + resolver

use std::path::{Path, PathBuf};

use tree_sitter::Language;

use astmap_core::SymbolKind;

use super::{
    collect_type_refs_by_kind, find_enclosing_symbol, node_text, parse_with_tree_sitter,
    symbol_from_node, ExtractedCall, ExtractedImport, ExtractedSymbol, LanguageParser,
    LanguageResolver, LanguageSupport, ParseResult,
};

pub(super) fn lang() -> LanguageSupport {
    LanguageSupport {
        name: "rust",
        extensions: &["rs"],
        parser: &RustParser,
        resolver_factory: |_root| Box::new(RustResolver),
        config_files: &[],
        sibling_fn: rust_sibling_expansion,
    }
}

fn rust_sibling_expansion(rel_path: &str) -> Option<astmap_core::SiblingExpansion> {
    let stem = rel_path.strip_suffix(".rs")?;
    Some(astmap_core::SiblingExpansion {
        prefix: format!("{}/", stem),
        extension: String::new(), // match any file under the submodule dir
        root_only: false,
    })
}

// ── Parser ──

pub(crate) struct RustParser;

impl LanguageParser for RustParser {
    fn parse(&self, source: &str, _file_path: &Path) -> ParseResult {
        parse_rust_file(source)
    }

    fn language_name(&self) -> &str {
        "rust"
    }
}

fn rust_language() -> Language {
    tree_sitter_rust::LANGUAGE.into()
}

fn parse_rust_file(source: &str) -> ParseResult {
    parse_with_tree_sitter(
        source,
        &rust_language(),
        |root, src, symbols, imports| extract_from_node(root, src, symbols, imports, None),
        extract_calls,
        |root, src, symbols| {
            collect_type_refs_by_kind(root, src, symbols, "type_identifier", &is_builtin_type)
        },
    )
}

fn extract_from_node(
    node: tree_sitter::Node,
    source: &str,
    symbols: &mut Vec<ExtractedSymbol>,
    imports: &mut Vec<ExtractedImport>,
    parent_index: Option<usize>,
) {
    let kind = node.kind();

    match kind {
        "struct_item" | "enum_item" | "trait_item" | "impl_item" | "type_item" => {
            if let Some(sym) = extract_symbol(&node, source, kind, parent_index) {
                let idx = symbols.len();
                symbols.push(sym);
                for i in 0..node.child_count() {
                    if let Some(child) = node.child(i as u32) {
                        extract_from_node(child, source, symbols, imports, Some(idx));
                    }
                }
                return;
            }
        }
        "function_item" => {
            if let Some(sym) = extract_function(&node, source, parent_index) {
                symbols.push(sym);
                return;
            }
        }
        "function_signature_item" => {
            if let Some(sym) = extract_function(&node, source, parent_index) {
                symbols.push(sym);
                return;
            }
        }
        "mod_item" => {
            if let Some(sym) = extract_mod(&node, source, parent_index) {
                let idx = symbols.len();
                symbols.push(sym);
                for i in 0..node.child_count() {
                    if let Some(child) = node.child(i as u32) {
                        extract_from_node(child, source, symbols, imports, Some(idx));
                    }
                }
                return;
            }
        }
        "use_declaration" => {
            if let Some(imp) = extract_use(&node, source) {
                imports.push(imp);
            }
        }
        _ => {}
    }

    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            extract_from_node(child, source, symbols, imports, parent_index);
        }
    }
}

fn extract_symbol(
    node: &tree_sitter::Node,
    source: &str,
    node_kind: &str,
    parent_index: Option<usize>,
) -> Option<ExtractedSymbol> {
    let kind = match node_kind {
        "struct_item" => SymbolKind::Struct,
        "enum_item" => SymbolKind::Enum,
        "trait_item" => SymbolKind::Trait,
        "impl_item" => {
            let type_node = node.child_by_field_name("type")?;
            let type_name = node_text(&type_node, source);
            return Some(symbol_from_node(
                type_name,
                SymbolKind::Impl,
                node,
                source,
                parent_index,
            ));
        }
        "type_item" => SymbolKind::TypeAlias,
        _ => return None,
    };

    let name_node = node.child_by_field_name("name")?;
    let name = node_text(&name_node, source);
    Some(symbol_from_node(name, kind, node, source, parent_index))
}

fn extract_function(
    node: &tree_sitter::Node,
    source: &str,
    parent_index: Option<usize>,
) -> Option<ExtractedSymbol> {
    let name_node = node.child_by_field_name("name")?;
    let name = node_text(&name_node, source);
    let kind = if parent_index.is_some() {
        SymbolKind::Method
    } else {
        SymbolKind::Function
    };
    Some(symbol_from_node(name, kind, node, source, parent_index))
}

fn extract_mod(
    node: &tree_sitter::Node,
    source: &str,
    parent_index: Option<usize>,
) -> Option<ExtractedSymbol> {
    let name_node = node.child_by_field_name("name")?;
    let name = node_text(&name_node, source);
    Some(symbol_from_node(
        name,
        SymbolKind::Module,
        node,
        source,
        parent_index,
    ))
}

fn extract_use(node: &tree_sitter::Node, source: &str) -> Option<ExtractedImport> {
    let text = node_text(node, source);
    let path = text
        .strip_prefix("use ")
        .unwrap_or(&text)
        .trim_end_matches(';')
        .trim()
        .to_string();

    let imported_symbols = extract_imported_names_from_use(&path);

    Some(ExtractedImport {
        path,
        imported_symbols,
    })
}

/// Extract the specific symbol names from a Rust use path.
/// e.g. "crate::db::Repository" → ["Repository"]
///      "crate::db::{Database, DbError}" → ["Database", "DbError"]
///      "super::models::*" → []
fn extract_imported_names_from_use(path: &str) -> Vec<String> {
    // Handle group imports: crate::db::{Foo, Bar}
    if let Some(brace_start) = path.find('{') {
        if let Some(brace_end) = path.rfind('}') {
            let inner = &path[brace_start + 1..brace_end];
            return inner
                .split(',')
                .map(|s| resolve_use_alias(s.trim()))
                .filter(|s| !s.is_empty() && s != "*" && s != "self")
                .collect();
        }
    }
    // Handle wildcard
    if path.ends_with("::*") {
        return vec![];
    }
    // Simple import: take the last segment, handle "as" alias
    if let Some(last) = path.rsplit("::").next() {
        let name = resolve_use_alias(last.trim());
        if !name.is_empty() && name != "self" {
            return vec![name];
        }
    }
    vec![]
}

/// Resolve a Rust use item that may contain "as" alias or a nested path.
/// "Foo as Bar" → "Bar" (the local alias)
/// "hash_map::Entry" → "Entry" (last segment of nested path)
/// "Foo" → "Foo"
fn resolve_use_alias(item: &str) -> String {
    // Handle "as" alias: "Foo as Bar" → "Bar"
    if let Some(pos) = item.find(" as ") {
        return item[pos + 4..].trim().to_string();
    }
    // Handle nested path: "hash_map::Entry" → "Entry"
    if let Some(last) = item.rsplit("::").next() {
        return last.trim().to_string();
    }
    item.to_string()
}

fn extract_calls(
    root: tree_sitter::Node,
    source: &str,
    symbols: &[ExtractedSymbol],
) -> Vec<ExtractedCall> {
    let mut calls = Vec::new();
    collect_calls(root, source, symbols, &mut calls);
    calls
}

fn collect_calls(
    node: tree_sitter::Node,
    source: &str,
    symbols: &[ExtractedSymbol],
    calls: &mut Vec<ExtractedCall>,
) {
    if node.kind() == "call_expression" {
        if let Some(func_node) = node.child_by_field_name("function") {
            let callee_text = node_text(&func_node, source);
            let callee_name = callee_text.rsplit("::").next().unwrap_or(&callee_text);
            let callee_name = callee_name.rsplit('.').next().unwrap_or(callee_name);

            let call_line = node.start_position().row + 1;

            if let Some(caller) = find_enclosing_symbol(symbols, call_line) {
                calls.push(ExtractedCall {
                    caller_name: caller.to_string(),
                    callee_name: callee_name.to_string(),
                    line: call_line,
                });
            }
        }
    }

    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            collect_calls(child, source, symbols, calls);
        }
    }
}

fn is_builtin_type(name: &str) -> bool {
    matches!(
        name,
        "bool"
            | "char"
            | "str"
            | "i8"
            | "i16"
            | "i32"
            | "i64"
            | "i128"
            | "isize"
            | "u8"
            | "u16"
            | "u32"
            | "u64"
            | "u128"
            | "usize"
            | "f32"
            | "f64"
            | "String"
            | "Vec"
            | "Option"
            | "Result"
            | "Box"
            | "Rc"
            | "Arc"
            | "HashMap"
            | "HashSet"
            | "BTreeMap"
            | "BTreeSet"
            | "Path"
            | "PathBuf"
            | "Self"
    )
}

// ── Resolver ──

pub(crate) struct RustResolver;

impl LanguageResolver for RustResolver {
    fn resolve_import(
        &self,
        import_path: &str,
        source_file: &str,
        project_root: &Path,
    ) -> Option<PathBuf> {
        resolve_rust_use(import_path, source_file, project_root)
    }
}

/// Resolve a Rust use path to a file path relative to the project root.
///
/// `source_file` is relative to project_root (e.g., "astmap-core/src/db/mod.rs").
/// Returns an absolute path, or None for external crate imports.
pub fn resolve_rust_use(use_path: &str, source_file: &str, project_root: &Path) -> Option<PathBuf> {
    let normalized = normalize_use_path(use_path);
    let parts: Vec<&str> = normalized.split("::").collect();
    if parts.is_empty() {
        return None;
    }

    match parts[0] {
        "crate" => {
            let crate_src = find_crate_src_dir(source_file, project_root)?;
            resolve_module_path(&parts[1..], &crate_src)
        }
        "super" => {
            let source_path = project_root.join(source_file);
            let source_dir = source_path.parent()?.to_path_buf();
            let file_stem = source_path.file_stem()?.to_string_lossy();
            let base = if file_stem == "mod" {
                source_dir.parent()?.to_path_buf()
            } else {
                source_dir
            };
            resolve_module_path(&parts[1..], &base)
        }
        "self" => {
            let source_dir = project_root.join(source_file).parent()?.to_path_buf();
            resolve_module_path(&parts[1..], &source_dir)
        }
        _ => None,
    }
}

fn normalize_use_path(path: &str) -> String {
    let mut s = path.to_string();
    if let Some(brace_pos) = s.find('{') {
        s.truncate(brace_pos);
        s = s.trim_end_matches("::").to_string();
    }
    if s.ends_with("::*") {
        s.truncate(s.len() - 3);
    }
    s
}

fn find_crate_src_dir(source_file: &str, project_root: &Path) -> Option<PathBuf> {
    let abs = project_root.join(source_file);
    let mut current = abs.parent()?;
    while current.starts_with(project_root) {
        if current.file_name().is_some_and(|n| n == "src") {
            return Some(current.to_path_buf());
        }
        current = current.parent()?;
    }
    let fallback = project_root.join("src");
    if fallback.exists() {
        Some(fallback)
    } else {
        None
    }
}

fn resolve_module_path(parts: &[&str], base_dir: &Path) -> Option<PathBuf> {
    if parts.is_empty() {
        return None;
    }

    let mut current = base_dir.to_path_buf();
    for (i, part) in parts.iter().enumerate() {
        let as_file = current.join(format!("{}.rs", part));
        if as_file.exists() {
            return Some(as_file);
        }
        let as_mod = current.join(part).join("mod.rs");
        if as_mod.exists() {
            return Some(as_mod);
        }

        if i == parts.len() - 1 {
            let current_mod = current.join("mod.rs");
            if current_mod.exists() {
                return Some(current_mod);
            }
            if let Some(dir_name) = current.file_name() {
                let parent = current.parent()?;
                let as_parent_file = parent.join(format!("{}.rs", dir_name.to_string_lossy()));
                if as_parent_file.exists() {
                    return Some(as_parent_file);
                }
            }
            return None;
        }

        current = current.join(part);
    }
    None
}

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

    // ── Parser tests ──

    #[test]
    fn test_parse_struct() {
        let source = r#"
pub struct MyStruct {
    field: i32,
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        assert_eq!(result.symbols.len(), 1);
        assert_eq!(result.symbols[0].name, "MyStruct");
        assert_eq!(result.symbols[0].kind, SymbolKind::Struct);
    }

    #[test]
    fn test_parse_function() {
        let source = r#"
fn hello(name: &str) -> String {
    format!("Hello, {}", name)
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        assert_eq!(result.symbols.len(), 1);
        assert_eq!(result.symbols[0].name, "hello");
        assert_eq!(result.symbols[0].kind, SymbolKind::Function);
        assert!(result.symbols[0]
            .signature
            .as_ref()
            .unwrap()
            .contains("fn hello"));
    }

    #[test]
    fn test_parse_imports() {
        let source = r#"
use std::io::Read;
use crate::db::Database;
use super::models::Symbol;
"#;
        let result = parse_rust_file(source);
        assert_eq!(result.imports.len(), 3);
        assert_eq!(result.imports[0].path, "std::io::Read");
        assert_eq!(result.imports[1].path, "crate::db::Database");
        assert_eq!(result.imports[2].path, "super::models::Symbol");
    }

    #[test]
    fn test_parse_trait_and_impl() {
        let source = r#"
pub trait Greet {
    fn greet(&self) -> String;
}

pub struct Greeter;

impl Greet for Greeter {
    fn greet(&self) -> String {
        "hello".to_string()
    }
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        let names: Vec<&str> = result.symbols.iter().map(|s| s.name.as_str()).collect();
        assert!(names.contains(&"Greet"));
        assert!(names.contains(&"Greeter"));
        assert!(names.contains(&"greet"));
    }

    #[test]
    fn test_parse_enum() {
        let source = r#"
pub enum Color {
    Red,
    Green,
    Blue,
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        assert_eq!(result.symbols.len(), 1);
        assert_eq!(result.symbols[0].name, "Color");
        assert_eq!(result.symbols[0].kind, SymbolKind::Enum);
    }

    #[test]
    fn test_parse_empty_file() {
        let result = parse_rust_file("");
        assert!(!result.has_errors);
        assert_eq!(result.symbols.len(), 0);
        assert_eq!(result.imports.len(), 0);
    }

    #[test]
    fn test_extract_calls() {
        let source = r#"
fn helper() -> i32 { 42 }

fn main() {
    let x = helper();
    println!("{}", x);
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.calls.is_empty());
        let call_names: Vec<&str> = result
            .calls
            .iter()
            .map(|c| c.callee_name.as_str())
            .collect();
        assert!(call_names.contains(&"helper"), "calls: {:?}", result.calls);
    }

    #[test]
    fn test_extract_type_refs() {
        let source = r#"
struct Config {
    name: String,
}

struct Database {
    config: Config,
}

fn create_db(config: Config) -> Database {
    Database { config }
}
"#;
        let result = parse_rust_file(source);
        let ref_types: Vec<&str> = result
            .type_refs
            .iter()
            .map(|r| r.to_type.as_str())
            .collect();
        assert!(
            ref_types.contains(&"Config"),
            "type_refs: {:?}",
            result.type_refs
        );
        assert!(
            ref_types.contains(&"Database"),
            "type_refs: {:?}",
            result.type_refs
        );
    }

    #[test]
    fn test_method_calls() {
        let source = r#"
struct Foo;

impl Foo {
    fn bar(&self) -> i32 { 42 }
    fn baz(&self) {
        let x = self.bar();
    }
}
"#;
        let result = parse_rust_file(source);
        let call_names: Vec<&str> = result
            .calls
            .iter()
            .map(|c| c.callee_name.as_str())
            .collect();
        assert!(call_names.contains(&"bar"), "calls: {:?}", result.calls);
    }

    #[test]
    fn test_parse_type_alias() {
        let source = "type MyResult = Result<(), Box<dyn std::error::Error>>;";
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        assert_eq!(result.symbols.len(), 1);
        assert_eq!(result.symbols[0].name, "MyResult");
        assert_eq!(result.symbols[0].kind, SymbolKind::TypeAlias);
    }

    #[test]
    fn test_parse_module() {
        let source = r#"
mod inner {
    fn private_fn() {}
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        let names: Vec<&str> = result.symbols.iter().map(|s| s.name.as_str()).collect();
        assert!(names.contains(&"inner"));
        assert!(names.contains(&"private_fn"));
        let private_fn = result
            .symbols
            .iter()
            .find(|s| s.name == "private_fn")
            .unwrap();
        assert!(private_fn.parent_index.is_some());
    }

    #[test]
    fn test_parse_impl_methods_extracted() {
        let source = r#"
struct MyStruct;

impl MyStruct {
    fn new() -> Self { MyStruct }
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        let struct_sym = result
            .symbols
            .iter()
            .find(|s| s.name == "MyStruct")
            .unwrap();
        assert_eq!(struct_sym.kind, SymbolKind::Struct);
        let new_sym = result.symbols.iter().find(|s| s.name == "new").unwrap();
        assert!(new_sym.kind == SymbolKind::Function || new_sym.kind == SymbolKind::Method);
    }

    #[test]
    fn test_is_builtin_type_positive() {
        assert!(is_builtin_type("bool"));
        assert!(is_builtin_type("String"));
        assert!(is_builtin_type("Vec"));
        assert!(is_builtin_type("Option"));
        assert!(is_builtin_type("Result"));
        assert!(is_builtin_type("HashMap"));
        assert!(is_builtin_type("Self"));
        assert!(is_builtin_type("PathBuf"));
    }

    #[test]
    fn test_is_builtin_type_negative() {
        assert!(!is_builtin_type("MyStruct"));
        assert!(!is_builtin_type("Database"));
        assert!(!is_builtin_type("Scanner"));
    }

    #[test]
    fn test_parse_multiple_use_declarations() {
        let source = r#"
use std::collections::HashMap;
use crate::db::{Database, DbError};
use super::models::*;
"#;
        let result = parse_rust_file(source);
        assert_eq!(result.imports.len(), 3);
    }

    #[test]
    fn test_parse_trait_method_signature() {
        let source = r#"
pub trait Repository {
    fn open(&self) -> Result<(), Error>;
    fn close(&self);
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        let methods: Vec<&str> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Method)
            .map(|s| s.name.as_str())
            .collect();
        assert!(methods.contains(&"open"));
        assert!(methods.contains(&"close"));
    }

    #[test]
    fn test_path_qualified_call() {
        let source = r#"
fn caller() {
    std::io::stdout();
    Vec::new();
}
"#;
        let result = parse_rust_file(source);
        let callee_names: Vec<&str> = result
            .calls
            .iter()
            .map(|c| c.callee_name.as_str())
            .collect();
        assert!(callee_names.contains(&"stdout") || callee_names.contains(&"new"));
    }

    #[test]
    fn test_extract_imported_names_simple() {
        let names = extract_imported_names_from_use("crate::db::Repository");
        assert_eq!(names, vec!["Repository"]);
    }

    #[test]
    fn test_extract_imported_names_group() {
        let names = extract_imported_names_from_use("crate::db::{Database, DbError}");
        assert_eq!(names, vec!["Database", "DbError"]);
    }

    #[test]
    fn test_extract_imported_names_wildcard() {
        let names = extract_imported_names_from_use("super::models::*");
        assert!(names.is_empty());
    }

    #[test]
    fn test_extract_imported_names_as_alias() {
        let names = extract_imported_names_from_use("crate::db::Repository as Repo");
        assert_eq!(names, vec!["Repo"]);
    }

    #[test]
    fn test_extract_imported_names_group_with_alias() {
        let names = extract_imported_names_from_use("crate::db::{Repository as Repo, DbError}");
        assert_eq!(names, vec!["Repo", "DbError"]);
    }

    #[test]
    fn test_extract_imported_names_self_filtered() {
        let names = extract_imported_names_from_use("crate::db::{self, Database}");
        assert_eq!(names, vec!["Database"]);
    }

    #[test]
    fn test_extract_imported_names_nested_path() {
        let names = extract_imported_names_from_use("std::collections::{HashMap, hash_map::Entry}");
        assert_eq!(names, vec!["HashMap", "Entry"]);
    }

    #[test]
    fn test_import_extracts_imported_symbols() {
        let source = r#"
use crate::db::{Database, DbError};
use super::models::*;
use crate::scanner::Scanner as Sc;
"#;
        let result = parse_rust_file(source);
        assert_eq!(result.imports.len(), 3);
        assert_eq!(
            result.imports[0].imported_symbols,
            vec!["Database", "DbError"]
        );
        assert!(result.imports[1].imported_symbols.is_empty()); // wildcard
        assert_eq!(result.imports[2].imported_symbols, vec!["Sc"]);
    }

    // ── Resolver tests ──

    #[test]
    fn test_resolve_crate_path_simple() {
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("src");
        fs::create_dir_all(src.join("db")).unwrap();
        fs::write(src.join("db").join("mod.rs"), "").unwrap();
        fs::write(src.join("lib.rs"), "").unwrap();

        let result = resolve_rust_use("crate::db", "src/lib.rs", tmp.path());
        assert!(result.is_some());
        assert!(result.unwrap().ends_with("db/mod.rs"));
    }

    #[test]
    fn test_resolve_crate_path_workspace_subcrate() {
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("my-core").join("src");
        fs::create_dir_all(src.join("db")).unwrap();
        fs::write(src.join("db").join("mod.rs"), "").unwrap();
        fs::write(src.join("lib.rs"), "").unwrap();

        let result = resolve_rust_use("crate::db", "my-core/src/lib.rs", tmp.path());
        assert!(result.is_some());
        let path = result.unwrap();
        assert!(path.ends_with("db/mod.rs"), "got: {}", path.display());
    }

    #[test]
    fn test_resolve_crate_nested_module() {
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("src");
        fs::create_dir_all(&src).unwrap();
        fs::write(src.join("impact.rs"), "").unwrap();

        let result = resolve_rust_use("crate::impact", "src/db/mod.rs", tmp.path());
        assert!(result.is_some());
        assert!(result.unwrap().ends_with("impact.rs"));
    }

    #[test]
    fn test_resolve_super() {
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("src");
        fs::create_dir_all(src.join("db")).unwrap();
        fs::write(src.join("db").join("mod.rs"), "").unwrap();
        fs::write(src.join("db").join("models.rs"), "").unwrap();

        let result = resolve_rust_use("super::models", "src/db/queries.rs", tmp.path());
        assert!(result.is_some());
        assert!(result.unwrap().ends_with("models.rs"));
    }

    #[test]
    fn test_external_crate_returns_none() {
        let tmp = tempfile::tempdir().unwrap();
        assert_eq!(
            resolve_rust_use("std::io::Read", "src/lib.rs", tmp.path()),
            None
        );
        assert_eq!(
            resolve_rust_use("serde::Deserialize", "src/lib.rs", tmp.path()),
            None
        );
        assert_eq!(
            resolve_rust_use("tokio::spawn", "src/lib.rs", tmp.path()),
            None
        );
    }

    #[test]
    fn test_normalize_use_path_braces() {
        assert_eq!(
            normalize_use_path("crate::db::{Database, DbError}"),
            "crate::db"
        );
    }

    #[test]
    fn test_normalize_use_path_wildcard() {
        assert_eq!(
            normalize_use_path("crate::db::models::*"),
            "crate::db::models"
        );
    }

    #[test]
    fn test_normalize_use_path_plain() {
        assert_eq!(
            normalize_use_path("crate::db::Database"),
            "crate::db::Database"
        );
    }

    #[test]
    fn test_resolve_self() {
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("src").join("db");
        fs::create_dir_all(&src).unwrap();
        fs::write(src.join("models.rs"), "").unwrap();

        let result = resolve_rust_use("self::models", "src/db/mod.rs", tmp.path());
        assert!(result.is_some());
        assert!(result.unwrap().ends_with("models.rs"));
    }

    #[test]
    fn test_resolve_crate_symbol_name() {
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("src");
        fs::create_dir_all(src.join("db")).unwrap();
        fs::write(src.join("db").join("mod.rs"), "").unwrap();

        let result = resolve_rust_use("crate::db::Database", "src/lib.rs", tmp.path());
        assert!(result.is_some());
        assert!(
            result.unwrap().to_string_lossy().contains("db"),
            "should resolve to db module"
        );
    }

    #[test]
    fn test_resolve_empty_path() {
        let tmp = tempfile::tempdir().unwrap();
        let result = resolve_rust_use("", "src/lib.rs", tmp.path());
        assert!(result.is_none());
    }

    #[test]
    fn test_find_crate_src_dir_direct() {
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("src");
        fs::create_dir_all(&src).unwrap();
        fs::write(src.join("lib.rs"), "").unwrap();

        let result = find_crate_src_dir("src/lib.rs", tmp.path());
        assert!(result.is_some());
    }

    #[test]
    fn test_find_crate_src_dir_fallback() {
        let tmp = tempfile::tempdir().unwrap();
        let config_dir = tmp.path().join("config");
        fs::create_dir_all(&config_dir).unwrap();
        fs::write(config_dir.join("settings.rs"), "").unwrap();
        let src = tmp.path().join("src");
        fs::create_dir_all(&src).unwrap();

        let result = find_crate_src_dir("config/settings.rs", tmp.path());
        assert!(result.is_some());
        assert!(result.unwrap().ends_with("src"));
    }

    #[test]
    fn test_find_crate_src_dir_no_src() {
        let tmp = tempfile::tempdir().unwrap();
        let config_dir = tmp.path().join("config");
        fs::create_dir_all(&config_dir).unwrap();
        fs::write(config_dir.join("settings.rs"), "").unwrap();

        let result = find_crate_src_dir("config/settings.rs", tmp.path());
        assert!(result.is_none());
    }

    #[test]
    fn test_resolve_crate_only() {
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("src");
        fs::create_dir_all(&src).unwrap();
        fs::write(src.join("lib.rs"), "").unwrap();

        let result = resolve_rust_use("crate", "src/lib.rs", tmp.path());
        assert!(result.is_none());
    }

    #[test]
    fn test_resolve_module_path_as_rs_file() {
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("src");
        fs::create_dir_all(&src).unwrap();
        fs::write(src.join("utils.rs"), "").unwrap();
        fs::write(src.join("lib.rs"), "").unwrap();

        let result = resolve_rust_use("crate::utils", "src/lib.rs", tmp.path());
        assert!(result.is_some());
        assert!(result.unwrap().ends_with("utils.rs"));
    }

    #[test]
    fn test_resolve_deep_symbol_to_parent_rs() {
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("src");
        fs::create_dir_all(&src).unwrap();
        fs::write(src.join("utils.rs"), "").unwrap();
        fs::write(src.join("lib.rs"), "").unwrap();

        let result = resolve_rust_use("crate::utils::Helper", "src/lib.rs", tmp.path());
        assert!(result.is_some());
    }

    #[test]
    fn test_resolve_super_from_mod_rs() {
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("src");
        fs::create_dir_all(src.join("db")).unwrap();
        fs::write(src.join("db").join("mod.rs"), "").unwrap();
        fs::write(src.join("export.rs"), "").unwrap();

        let result = resolve_rust_use("super::export", "src/db/mod.rs", tmp.path());
        assert!(result.is_some());
        assert!(result.unwrap().ends_with("export.rs"));
    }

    #[test]
    fn test_resolve_super_from_new_style_module() {
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("src");
        fs::create_dir_all(src.join("db")).unwrap();
        fs::write(src.join("db.rs"), "").unwrap();
        fs::write(src.join("export.rs"), "").unwrap();

        let result = resolve_rust_use("super::export", "src/db.rs", tmp.path());
        assert!(result.is_some());
        assert!(result.unwrap().ends_with("export.rs"));
    }

    #[test]
    fn test_resolve_super_nonexistent() {
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("src");
        fs::create_dir_all(src.join("db")).unwrap();
        fs::write(src.join("db").join("queries.rs"), "").unwrap();

        let result = resolve_rust_use("super::nonexistent", "src/db/queries.rs", tmp.path());
        assert!(result.is_none());
    }

    // --- Additional coverage tests ---

    #[test]
    fn test_parse_trait_method_signatures_without_body() {
        // function_signature_item branch (trait methods without default impl)
        let source = r#"
pub trait Serializer {
    fn serialize(&self) -> Vec<u8>;
    fn deserialize(data: &[u8]) -> Self;
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        let methods: Vec<&str> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Method)
            .map(|s| s.name.as_str())
            .collect();
        assert!(methods.contains(&"serialize"), "methods: {:?}", methods);
        assert!(methods.contains(&"deserialize"), "methods: {:?}", methods);
        // They should be children of the trait
        let trait_idx = result
            .symbols
            .iter()
            .position(|s| s.name == "Serializer")
            .unwrap();
        for sym in result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Method)
        {
            assert_eq!(
                sym.parent_index,
                Some(trait_idx),
                "method '{}' should be child of Serializer",
                sym.name
            );
        }
    }

    #[test]
    fn test_parse_nested_module_with_items() {
        let source = r#"
mod outer {
    mod inner {
        fn deep_fn() {}
    }
    fn outer_fn() {}
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        let names: Vec<&str> = result.symbols.iter().map(|s| s.name.as_str()).collect();
        assert!(names.contains(&"outer"), "symbols: {:?}", names);
        assert!(names.contains(&"inner"), "symbols: {:?}", names);
        assert!(names.contains(&"deep_fn"), "symbols: {:?}", names);
        assert!(names.contains(&"outer_fn"), "symbols: {:?}", names);
        // inner should be child of outer
        let outer_idx = result
            .symbols
            .iter()
            .position(|s| s.name == "outer")
            .unwrap();
        let inner = result.symbols.iter().find(|s| s.name == "inner").unwrap();
        assert_eq!(inner.parent_index, Some(outer_idx));
    }

    #[test]
    fn test_parse_impl_for_trait_methods_extracted() {
        // impl with trait clause: methods inside should still be extracted
        // even though the impl block itself may not have a "name" field
        let source = r#"
trait Render {
    fn render(&self) -> String;
    fn refresh(&self);
}
struct Widget;
impl Render for Widget {
    fn render(&self) -> String { "widget".to_string() }
    fn refresh(&self) {}
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        let method_names: Vec<&str> = result
            .symbols
            .iter()
            .filter(|s| s.name == "render" || s.name == "refresh")
            .map(|s| s.name.as_str())
            .collect();
        // Each name appears twice: once in trait (signature_item) and once in impl
        assert!(
            method_names.iter().filter(|&&n| n == "render").count() >= 2,
            "render should appear in both trait and impl: {:?}",
            method_names
        );
        assert!(
            method_names.iter().filter(|&&n| n == "refresh").count() >= 2,
            "refresh should appear in both trait and impl: {:?}",
            method_names
        );
    }

    #[test]
    fn test_resolve_use_alias_plain() {
        // No "as" and no "::" => return as-is
        assert_eq!(resolve_use_alias("Foo"), "Foo");
    }

    #[test]
    fn test_resolve_use_alias_with_as() {
        assert_eq!(resolve_use_alias("Foo as Bar"), "Bar");
    }

    #[test]
    fn test_resolve_use_alias_nested() {
        assert_eq!(resolve_use_alias("hash_map::Entry"), "Entry");
    }

    #[test]
    fn test_normalize_use_path_combined_braces_and_suffix() {
        // Path with braces should strip everything from brace onwards
        assert_eq!(normalize_use_path("crate::foo::{A, B}"), "crate::foo");
    }

    #[test]
    fn test_extract_imported_names_empty_path() {
        let names = extract_imported_names_from_use("");
        assert!(names.is_empty());
    }

    #[test]
    fn test_parse_const_and_static() {
        // These should not be extracted (not in match arms)
        let source = r#"
const MAX: usize = 100;
static COUNTER: i32 = 0;
fn used_fn() {}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        // Only the function should be extracted
        let names: Vec<&str> = result.symbols.iter().map(|s| s.name.as_str()).collect();
        assert!(names.contains(&"used_fn"));
    }

    #[test]
    fn test_parse_struct_with_impl_methods() {
        let source = r#"
pub struct Config {
    pub name: String,
}

impl Config {
    pub fn new(name: &str) -> Self {
        Config { name: name.to_string() }
    }
    pub fn validate(&self) -> bool {
        !self.name.is_empty()
    }
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        // impl Config extracts as impl kind with "Config" name; methods are its children
        let all_names: Vec<&str> = result.symbols.iter().map(|s| s.name.as_str()).collect();
        assert!(
            all_names.contains(&"Config"),
            "struct Config should be extracted"
        );
        assert!(
            all_names.contains(&"new"),
            "new should be extracted: {:?}",
            all_names
        );
        assert!(
            all_names.contains(&"validate"),
            "validate should be extracted: {:?}",
            all_names
        );
    }

    #[test]
    fn test_resolve_module_path_intermediate_not_dir() {
        // When an intermediate part is not a directory, resolution should fail
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("src");
        fs::create_dir_all(&src).unwrap();
        // "deep" is a file, not a directory
        fs::write(src.join("deep"), "").unwrap();

        let result = resolve_rust_use("crate::deep::nested", "src/lib.rs", tmp.path());
        assert!(result.is_none(), "intermediate non-directory should fail");
    }

    #[test]
    fn test_parse_use_with_self() {
        let source = "use crate::db::{self, models};\n";
        let result = parse_rust_file(source);
        assert_eq!(result.imports.len(), 1);
        // "self" should be filtered, only "models" kept
        assert_eq!(result.imports[0].imported_symbols, vec!["models"]);
    }

    #[test]
    fn test_type_ref_builtin_types_filtered() {
        // All builtin types should NOT appear in type_refs
        let source = r#"
fn foo(a: bool, b: i32, c: u64, d: f32, e: String, f: Vec<i32>) {}
"#;
        let result = parse_rust_file(source);
        assert!(
            result.type_refs.is_empty(),
            "builtin types should be filtered: {:?}",
            result.type_refs
        );
    }

    #[test]
    fn test_call_inside_nested_module() {
        let source = r#"
mod setup {
    fn init() {
        helper();
    }
    fn helper() {}
}
"#;
        let result = parse_rust_file(source);
        // All calls should have a caller
        for call in &result.calls {
            assert!(
                !call.caller_name.is_empty(),
                "call to '{}' should have a caller",
                call.callee_name
            );
        }
    }

    #[test]
    fn test_extract_imported_names_single_no_path_separator() {
        let names = extract_imported_names_from_use("Foo");
        assert_eq!(names, vec!["Foo"]);
    }

    #[test]
    fn test_extract_imported_names_self_only() {
        let names = extract_imported_names_from_use("crate::db::self");
        assert!(
            names.is_empty(),
            "self-only import should return empty: {:?}",
            names
        );
    }

    #[test]
    fn test_type_refs_from_struct_fields() {
        let source = r#"
struct Config { name: String }
struct App { config: Config, count: i32 }
"#;
        let result = parse_rust_file(source);
        let type_names: Vec<&str> = result
            .type_refs
            .iter()
            .map(|r| r.to_type.as_str())
            .collect();
        assert!(
            type_names.contains(&"Config"),
            "Config type ref should be found: {:?}",
            type_names
        );
    }

    #[test]
    fn test_resolve_module_path_empty_parts() {
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("src");
        fs::create_dir_all(&src).unwrap();
        let result = resolve_module_path(&[], &src);
        assert!(result.is_none(), "empty parts should return None");
    }

    #[test]
    fn test_parse_use_with_wildcard_in_group() {
        let source = "use crate::db::{*, DbError};\n";
        let result = parse_rust_file(source);
        assert_eq!(result.imports.len(), 1);
        // "*" should be filtered, only "DbError" kept
        assert_eq!(result.imports[0].imported_symbols, vec!["DbError"]);
    }

    #[test]
    fn test_is_builtin_type_all_values() {
        // Exhaustively test all builtin types
        for ty in &[
            "bool", "char", "str", "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32",
            "u64", "u128", "usize", "f32", "f64", "String", "Vec", "Option", "Result", "Box", "Rc",
            "Arc", "HashMap", "HashSet", "BTreeMap", "BTreeSet", "Path", "PathBuf", "Self",
        ] {
            assert!(is_builtin_type(ty), "'{}' should be builtin", ty);
        }
    }

    // ── Additional edge case tests ──

    #[test]
    fn test_normalize_use_path_empty() {
        assert_eq!(normalize_use_path(""), "");
    }

    #[test]
    fn test_normalize_use_path_single_ident() {
        assert_eq!(normalize_use_path("std"), "std");
    }

    #[test]
    fn test_parse_multiple_impl_blocks() {
        let source = r#"
struct Widget;

impl Widget {
    fn new() -> Self { Widget }
    fn render(&self) {}
}

impl Widget {
    fn destroy(&self) {}
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        let names: Vec<&str> = result.symbols.iter().map(|s| s.name.as_str()).collect();
        assert!(names.contains(&"Widget"));
        assert!(names.contains(&"new"));
        assert!(names.contains(&"render"));
        assert!(names.contains(&"destroy"));
    }

    #[test]
    fn test_parse_generic_struct() {
        let source = r#"
pub struct Container<T: Clone> {
    inner: Vec<T>,
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        assert_eq!(result.symbols.len(), 1);
        assert_eq!(result.symbols[0].name, "Container");
        assert_eq!(result.symbols[0].kind, SymbolKind::Struct);
    }

    #[test]
    fn test_parse_async_function() {
        let source = "async fn fetch_data() -> Result<(), Error> {}\n";
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        assert_eq!(result.symbols.len(), 1);
        assert_eq!(result.symbols[0].name, "fetch_data");
        assert_eq!(result.symbols[0].kind, SymbolKind::Function);
    }

    #[test]
    fn test_type_refs_generic_parameters() {
        let source = r#"
struct Wrapper<T> { inner: T }
fn process(w: Wrapper<Config>) -> Wrapper<Config> { w }
"#;
        let result = parse_rust_file(source);
        let ref_types: Vec<&str> = result
            .type_refs
            .iter()
            .map(|r| r.to_type.as_str())
            .collect();
        assert!(
            ref_types.contains(&"Config"),
            "generic param should be extracted: {:?}",
            ref_types
        );
    }

    #[test]
    fn test_calls_chain() {
        let source = r#"
fn chain() {
    a();
    b();
    c();
}
fn a() {}
fn b() {}
fn c() {}
"#;
        let result = parse_rust_file(source);
        let callee_names: Vec<&str> = result
            .calls
            .iter()
            .map(|c| c.callee_name.as_str())
            .collect();
        assert!(callee_names.contains(&"a"));
        assert!(callee_names.contains(&"b"));
        assert!(callee_names.contains(&"c"));
        // All calls should have "chain" as caller
        for call in &result.calls {
            assert_eq!(call.caller_name, "chain");
        }
    }

    #[test]
    fn test_parse_malformed_file() {
        let result = parse_rust_file("fn broken( { }");
        assert!(result.has_errors);
    }

    #[test]
    fn test_parse_enum_with_variants_methods() {
        let source = r#"
enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
}

impl Shape {
    fn area(&self) -> f64 { 0.0 }
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        let names: Vec<&str> = result.symbols.iter().map(|s| s.name.as_str()).collect();
        assert!(names.contains(&"Shape"));
        assert!(names.contains(&"area"));
    }

    #[test]
    fn test_impl_item_methods_extracted_as_toplevel() {
        // impl blocks don't have a "name" field, so extract_symbol returns None.
        // Methods inside are extracted but fall through to the default recursion,
        // meaning they are extracted without a parent_index.
        let source = r#"
struct Foo;
impl Foo {
    fn bar(&self) {}
}
"#;
        let result = parse_rust_file(source);
        assert!(!result.has_errors);
        let bar = result.symbols.iter().find(|s| s.name == "bar").unwrap();
        // bar is a function (no parent since impl block is not extracted as a symbol)
        assert!(bar.kind == SymbolKind::Function || bar.kind == SymbolKind::Method);
    }

    #[test]
    fn test_resolve_self_module() {
        let tmp = tempfile::tempdir().unwrap();
        let src = tmp.path().join("src").join("db");
        fs::create_dir_all(&src).unwrap();
        fs::write(src.join("queries.rs"), "").unwrap();

        let result = resolve_rust_use("self::queries", "src/db/mod.rs", tmp.path());
        assert!(result.is_some());
        assert!(result.unwrap().ends_with("queries.rs"));
    }

    #[test]
    fn test_language_parser_trait_name() {
        let parser = RustParser;
        assert_eq!(parser.language_name(), "rust");
    }

    #[test]
    fn test_language_parser_parse_via_trait() {
        let parser = RustParser;
        let result = parser.parse("fn foo() {}", &PathBuf::from("test.rs"));
        assert!(!result.has_errors);
        assert_eq!(result.symbols.len(), 1);
        assert_eq!(result.symbols[0].name, "foo");
    }
}
