// Java language support: parser + resolver

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

use tree_sitter::Language;

use astmap_core::SymbolKind;

use super::{
    collect_type_refs_by_kind, detect_jvm_source_roots, 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: "java",
        extensions: &["java"],
        parser: &JavaParser,
        resolver_factory: |root| Box::new(JavaResolver::new(root)),
        config_files: &["pom.xml", "build.gradle", "build.gradle.kts"],
        sibling_fn: java_sibling_expansion,
    }
}

fn java_sibling_expansion(_rel_path: &str) -> Option<astmap_core::SiblingExpansion> {
    // Java files within the same package directory are related, but unlike Go
    // they don't share a flat namespace by default. Return None for now.
    None
}

// ── Parser ──

pub(crate) struct JavaParser;

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

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

fn java_language() -> Language {
    tree_sitter_java::LANGUAGE.into()
}

fn parse_java_file(source: &str) -> ParseResult {
    parse_with_tree_sitter(
        source,
        &java_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_java_builtin)
        },
    )
}

// ---------------------------------------------------------------------------
// Symbol extraction
// ---------------------------------------------------------------------------

fn extract_from_node(
    node: tree_sitter::Node,
    source: &str,
    symbols: &mut Vec<ExtractedSymbol>,
    imports: &mut Vec<ExtractedImport>,
    parent_index: Option<usize>,
) {
    match node.kind() {
        "class_declaration" => {
            if let Some(sym) = extract_class_like(&node, source, SymbolKind::Class, parent_index) {
                let idx = symbols.len();
                symbols.push(sym);
                recurse_into_body(&node, source, symbols, imports, idx);
                return;
            }
        }
        "interface_declaration" => {
            if let Some(sym) =
                extract_class_like(&node, source, SymbolKind::Interface, parent_index)
            {
                let idx = symbols.len();
                symbols.push(sym);
                recurse_into_body(&node, source, symbols, imports, idx);
                return;
            }
        }
        "enum_declaration" => {
            if let Some(sym) = extract_class_like(&node, source, SymbolKind::Enum, parent_index) {
                let idx = symbols.len();
                symbols.push(sym);
                recurse_into_enum_body(&node, source, symbols, imports, idx);
                return;
            }
        }
        "record_declaration" => {
            if let Some(sym) = extract_class_like(&node, source, SymbolKind::Class, parent_index) {
                let idx = symbols.len();
                symbols.push(sym);
                recurse_into_body(&node, source, symbols, imports, idx);
                return;
            }
        }
        "annotation_type_declaration" => {
            if let Some(sym) =
                extract_class_like(&node, source, SymbolKind::Annotation, parent_index)
            {
                let idx = symbols.len();
                symbols.push(sym);
                recurse_into_body(&node, source, symbols, imports, idx);
                return;
            }
        }
        "method_declaration" => {
            if let Some(sym) = extract_method(&node, source, parent_index) {
                symbols.push(sym);
                return;
            }
        }
        "constructor_declaration" => {
            if let Some(sym) = extract_constructor(&node, source, parent_index) {
                symbols.push(sym);
                return;
            }
        }
        "field_declaration" => {
            extract_fields(&node, source, symbols, parent_index);
            return;
        }
        "import_declaration" => {
            if let Some(imp) = extract_import(&node, source) {
                imports.push(imp);
            }
            return;
        }
        _ => {}
    }

    // Default: recurse into children
    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_class_like(
    node: &tree_sitter::Node,
    source: &str,
    kind: SymbolKind,
    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, kind, node, source, parent_index))
}

/// Recurse into the body of a class-like declaration to extract members.
fn recurse_into_body(
    node: &tree_sitter::Node,
    source: &str,
    symbols: &mut Vec<ExtractedSymbol>,
    imports: &mut Vec<ExtractedImport>,
    parent_idx: usize,
) {
    if let Some(body) = node.child_by_field_name("body") {
        for i in 0..body.child_count() {
            if let Some(child) = body.child(i as u32) {
                extract_from_node(child, source, symbols, imports, Some(parent_idx));
            }
        }
    }
}

/// Recurse into the body of an enum declaration, also extracting enum_constant nodes.
fn recurse_into_enum_body(
    node: &tree_sitter::Node,
    source: &str,
    symbols: &mut Vec<ExtractedSymbol>,
    imports: &mut Vec<ExtractedImport>,
    parent_idx: usize,
) {
    if let Some(body) = node.child_by_field_name("body") {
        for i in 0..body.child_count() {
            if let Some(child) = body.child(i as u32) {
                if child.kind() == "enum_constant" {
                    if let Some(sym) = extract_enum_constant(&child, source, parent_idx) {
                        symbols.push(sym);
                    }
                } else {
                    extract_from_node(child, source, symbols, imports, Some(parent_idx));
                }
            }
        }
    }
}

fn extract_enum_constant(
    node: &tree_sitter::Node,
    source: &str,
    parent_idx: usize,
) -> Option<ExtractedSymbol> {
    // enum_constant has a child identifier for the name
    let name_node = node.child_by_field_name("name").or_else(|| {
        (0..node.child_count())
            .filter_map(|i| node.child(i as u32))
            .find(|c| c.kind() == "identifier")
    })?;
    let name = node_text(&name_node, source);
    Some(symbol_from_node(
        name,
        SymbolKind::Const,
        node,
        source,
        Some(parent_idx),
    ))
}

fn extract_method(
    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::Method,
        node,
        source,
        parent_index,
    ))
}

fn extract_constructor(
    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::Method,
        node,
        source,
        parent_index,
    ))
}

/// Extract field declarations.
///
/// A `field_declaration` may have modifiers. If modifiers contain both "static" and
/// "final", it's a constant (Const), otherwise a variable (Variable).
///
/// The actual variable names are inside `variable_declarator` children of the
/// `declarator` field.
fn extract_fields(
    node: &tree_sitter::Node,
    source: &str,
    symbols: &mut Vec<ExtractedSymbol>,
    parent_index: Option<usize>,
) {
    let kind = if has_static_final_modifiers(node, source) {
        SymbolKind::Const
    } else {
        SymbolKind::Variable
    };

    // Collect variable names from variable_declarator children
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            if child.kind() == "variable_declarator" {
                if let Some(name_node) = child.child_by_field_name("name") {
                    let name = node_text(&name_node, source);
                    symbols.push(symbol_from_node(name, kind, node, source, parent_index));
                }
            }
        }
    }
}

/// Check if a node has `modifiers` child containing both "static" and "final".
fn has_static_final_modifiers(node: &tree_sitter::Node, source: &str) -> bool {
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            if child.kind() == "modifiers" {
                let mut has_static = false;
                let mut has_final = false;
                for j in 0..child.child_count() {
                    if let Some(mod_child) = child.child(j as u32) {
                        let text = node_text(&mod_child, source);
                        if text == "static" {
                            has_static = true;
                        }
                        if text == "final" {
                            has_final = true;
                        }
                    }
                }
                return has_static && has_final;
            }
        }
    }
    false
}

// ---------------------------------------------------------------------------
// Import extraction
// ---------------------------------------------------------------------------

/// Extract a single `import_declaration` node into an `ExtractedImport`.
///
/// Examples:
/// - `import com.example.Foo;` → path="com/example/Foo", imported_symbols=["Foo"]
/// - `import com.example.*;` → path="com/example::*", imported_symbols=[]
/// - `import static com.example.Foo.bar;` → path="com/example/Foo", imported_symbols=["bar"]
/// - `import static com.example.Foo.*;` → path="com/example/Foo::*", imported_symbols=[]
fn extract_import(node: &tree_sitter::Node, source: &str) -> Option<ExtractedImport> {
    // Detect static import
    let is_static = (0..node.child_count())
        .filter_map(|i| node.child(i as u32))
        .any(|c| node_text(&c, source) == "static");

    // Detect wildcard import (asterisk child)
    let has_asterisk = (0..node.child_count())
        .filter_map(|i| node.child(i as u32))
        .any(|c| c.kind() == "asterisk");

    // Get the full import path from scoped_identifier or identifier child
    let path_text = (0..node.child_count())
        .filter_map(|i| node.child(i as u32))
        .find(|c| c.kind() == "scoped_identifier" || c.kind() == "identifier")
        .map(|c| node_text(&c, source))?;

    // Convert dots to slashes
    let slash_path = path_text.replace('.', "/");

    if is_static {
        if has_asterisk {
            // import static com.example.Foo.* → path="com/example/Foo::*"
            Some(ExtractedImport {
                path: format!("{}::*", slash_path),
                imported_symbols: vec![],
            })
        } else {
            // import static com.example.Foo.bar → path="com/example/Foo", symbols=["bar"]
            let (parent, member) = split_last_segment(&slash_path)?;
            Some(ExtractedImport {
                path: parent.to_string(),
                imported_symbols: vec![member.to_string()],
            })
        }
    } else if has_asterisk {
        // import com.example.* → path="com/example::*"
        Some(ExtractedImport {
            path: format!("{}::*", slash_path),
            imported_symbols: vec![],
        })
    } else {
        // import com.example.Foo → path="com/example/Foo", symbols=["Foo"]
        let last = slash_path.rsplit('/').next().unwrap_or(&slash_path);
        Some(ExtractedImport {
            path: slash_path.clone(),
            imported_symbols: vec![last.to_string()],
        })
    }
}

/// Split "com/example/Foo" into ("com/example", "Foo").
fn split_last_segment(path: &str) -> Option<(&str, &str)> {
    let idx = path.rfind('/')?;
    Some((&path[..idx], &path[idx + 1..]))
}

// ---------------------------------------------------------------------------
// Call extraction
// ---------------------------------------------------------------------------

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>,
) {
    match node.kind() {
        "method_invocation" => {
            if let Some(name_node) = node.child_by_field_name("name") {
                let callee_name = node_text(&name_node, source);
                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,
                        line: call_line,
                    });
                }
            }
        }
        "object_creation_expression" => {
            // new Foo(...) — extract type name as constructor call
            if let Some(type_node) = node.child_by_field_name("type") {
                let callee_name = node_text(&type_node, source);
                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,
                        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);
        }
    }
}

// ---------------------------------------------------------------------------
// Type reference extraction
// ---------------------------------------------------------------------------

fn is_java_builtin(name: &str) -> bool {
    matches!(
        name,
        "int"
            | "long"
            | "float"
            | "double"
            | "boolean"
            | "char"
            | "byte"
            | "short"
            | "void"
            | "String"
            | "Object"
            | "Integer"
            | "Long"
            | "Float"
            | "Double"
            | "Boolean"
            | "Character"
            | "Byte"
            | "Short"
            | "Void"
            | "Number"
            | "Class"
            | "Enum"
            | "Throwable"
            | "Exception"
            | "RuntimeException"
            | "Error"
            | "Override"
            | "Deprecated"
            | "SuppressWarnings"
            | "FunctionalInterface"
            | "SafeVarargs"
    )
}

// ── Resolver ──

pub(crate) struct JavaResolver {
    source_roots: Vec<PathBuf>,
}

impl JavaResolver {
    fn new(root: &Path) -> Self {
        let source_roots = detect_jvm_source_roots(root);
        Self { source_roots }
    }
}

impl LanguageResolver for JavaResolver {
    fn resolve_import(
        &self,
        import_path: &str,
        _source_file: &str,
        _project_root: &Path,
    ) -> Option<PathBuf> {
        // Strip ::* suffix for wildcard imports (resolve to directory-level)
        let effective_path = import_path.strip_suffix("::*").unwrap_or(import_path);

        // Convert slashes to platform path separators
        let rel_path = PathBuf::from(effective_path.replace('/', std::path::MAIN_SEPARATOR_STR));

        for root in &self.source_roots {
            let candidate = root.join(&rel_path).with_extension("java");
            if candidate.exists() {
                return Some(candidate);
            }
        }

        // External dependency or JDK class — not resolvable locally
        None
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

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

    fn parse(source: &str) -> ParseResult {
        JavaParser.parse(source, &PathBuf::from("Test.java"))
    }

    // --- Symbol extraction: class-like declarations ---

    #[test]
    fn test_parse_class() {
        let result = parse("public class Foo {}");
        assert!(!result.has_errors);
        assert_eq!(result.symbols.len(), 1);
        assert_eq!(result.symbols[0].name, "Foo");
        assert_eq!(result.symbols[0].kind, SymbolKind::Class);
        assert!(result.symbols[0].parent_index.is_none());
    }

    #[test]
    fn test_parse_interface() {
        let result = parse("public interface Service { void run(); }");
        assert!(!result.has_errors);
        let ifaces: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Interface)
            .collect();
        assert_eq!(ifaces.len(), 1);
        assert_eq!(ifaces[0].name, "Service");
    }

    #[test]
    fn test_parse_enum() {
        let result = parse("public enum Color { RED, GREEN, BLUE }");
        assert!(!result.has_errors);
        let enums: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Enum)
            .collect();
        assert_eq!(enums.len(), 1);
        assert_eq!(enums[0].name, "Color");
    }

    #[test]
    fn test_parse_record() {
        let result = parse("public record Point(int x, int y) {}");
        assert!(!result.has_errors);
        let records: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.name == "Point")
            .collect();
        assert_eq!(records.len(), 1);
        assert_eq!(records[0].kind, SymbolKind::Class);
    }

    #[test]
    fn test_parse_annotation_type() {
        let result = parse("public @interface MyAnnotation {}");
        assert!(!result.has_errors);
        let annots: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Annotation)
            .collect();
        assert_eq!(annots.len(), 1);
        assert_eq!(annots[0].name, "MyAnnotation");
    }

    // --- Methods and constructors ---

    #[test]
    fn test_parse_method() {
        let result = parse(
            r#"public class Foo {
    public void doStuff() {}
    private int compute(int x) { return x * 2; }
}"#,
        );
        assert!(!result.has_errors);
        let methods: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Method)
            .collect();
        assert_eq!(methods.len(), 2);
        assert_eq!(methods[0].name, "doStuff");
        assert_eq!(methods[1].name, "compute");
        // Methods should have Foo as parent
        let foo_idx = result.symbols.iter().position(|s| s.name == "Foo").unwrap();
        assert_eq!(methods[0].parent_index, Some(foo_idx));
        assert_eq!(methods[1].parent_index, Some(foo_idx));
    }

    #[test]
    fn test_parse_constructor() {
        let result = parse(
            r#"public class Foo {
    public Foo(int x) {}
}"#,
        );
        assert!(!result.has_errors);
        let constructors: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Method && s.name == "Foo")
            .collect();
        // class Foo + constructor Foo
        assert_eq!(constructors.len(), 1);
        let foo_idx = result
            .symbols
            .iter()
            .position(|s| s.kind == SymbolKind::Class && s.name == "Foo")
            .unwrap();
        assert_eq!(constructors[0].parent_index, Some(foo_idx));
    }

    // --- Fields ---

    #[test]
    fn test_parse_field_variable() {
        let result = parse(
            r#"public class Foo {
    private int count;
    protected String name;
}"#,
        );
        assert!(!result.has_errors);
        let vars: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Variable)
            .collect();
        assert_eq!(vars.len(), 2);
        assert_eq!(vars[0].name, "count");
        assert_eq!(vars[1].name, "name");
    }

    #[test]
    fn test_parse_field_static_final_is_const() {
        let result = parse(
            r#"public class Config {
    public static final int MAX_SIZE = 100;
    public static final String DEFAULT_NAME = "foo";
}"#,
        );
        assert!(!result.has_errors);
        let consts: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Const)
            .collect();
        assert_eq!(consts.len(), 2);
        assert_eq!(consts[0].name, "MAX_SIZE");
        assert_eq!(consts[1].name, "DEFAULT_NAME");
    }

    #[test]
    fn test_parse_field_static_only_is_variable() {
        let result = parse(
            r#"public class Foo {
    private static int instanceCount;
}"#,
        );
        assert!(!result.has_errors);
        let vars: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Variable)
            .collect();
        assert_eq!(vars.len(), 1);
        assert_eq!(vars[0].name, "instanceCount");
    }

    #[test]
    fn test_parse_field_final_only_is_variable() {
        let result = parse(
            r#"public class Foo {
    private final int id;
}"#,
        );
        assert!(!result.has_errors);
        let vars: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Variable)
            .collect();
        assert_eq!(vars.len(), 1);
        assert_eq!(vars[0].name, "id");
    }

    // --- Enum constants ---

    #[test]
    fn test_parse_enum_constants() {
        let result = parse("public enum Status { ACTIVE, INACTIVE, PENDING }");
        assert!(!result.has_errors);
        let consts: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Const)
            .collect();
        assert_eq!(consts.len(), 3);
        assert_eq!(consts[0].name, "ACTIVE");
        assert_eq!(consts[1].name, "INACTIVE");
        assert_eq!(consts[2].name, "PENDING");
        // All should have the enum as parent
        let enum_idx = result
            .symbols
            .iter()
            .position(|s| s.kind == SymbolKind::Enum)
            .unwrap();
        for c in &consts {
            assert_eq!(c.parent_index, Some(enum_idx));
        }
    }

    // --- Nested class ---

    #[test]
    fn test_parse_nested_class() {
        let result = parse(
            r#"public class Outer {
    public class Inner {
        public void innerMethod() {}
    }
}"#,
        );
        assert!(!result.has_errors);
        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.kind, SymbolKind::Class);
        assert_eq!(inner.parent_index, Some(outer_idx));
        let inner_idx = result
            .symbols
            .iter()
            .position(|s| s.name == "Inner")
            .unwrap();
        let inner_method = result
            .symbols
            .iter()
            .find(|s| s.name == "innerMethod")
            .unwrap();
        assert_eq!(inner_method.parent_index, Some(inner_idx));
    }

    // --- Import extraction ---

    #[test]
    fn test_import_regular() {
        let result = parse("import com.example.Foo;");
        assert_eq!(result.imports.len(), 1);
        assert_eq!(result.imports[0].path, "com/example/Foo");
        assert_eq!(result.imports[0].imported_symbols, vec!["Foo"]);
    }

    #[test]
    fn test_import_wildcard() {
        let result = parse("import com.example.*;");
        assert_eq!(result.imports.len(), 1);
        assert_eq!(result.imports[0].path, "com/example::*");
        assert!(result.imports[0].imported_symbols.is_empty());
    }

    #[test]
    fn test_import_static() {
        let result = parse("import static com.example.Foo.bar;");
        assert_eq!(result.imports.len(), 1);
        assert_eq!(result.imports[0].path, "com/example/Foo");
        assert_eq!(result.imports[0].imported_symbols, vec!["bar"]);
    }

    #[test]
    fn test_import_static_wildcard() {
        let result = parse("import static com.example.Foo.*;");
        assert_eq!(result.imports.len(), 1);
        assert_eq!(result.imports[0].path, "com/example/Foo::*");
        assert!(result.imports[0].imported_symbols.is_empty());
    }

    #[test]
    fn test_import_multiple() {
        let result = parse(
            r#"import java.util.List;
import java.util.Map;
import java.io.IOException;"#,
        );
        assert_eq!(result.imports.len(), 3);
        let paths: Vec<&str> = result.imports.iter().map(|i| i.path.as_str()).collect();
        assert!(paths.contains(&"java/util/List"));
        assert!(paths.contains(&"java/util/Map"));
        assert!(paths.contains(&"java/io/IOException"));
    }

    // --- Call extraction ---

    #[test]
    fn test_extract_method_call() {
        let result = parse(
            r#"public class Foo {
    public void caller() {
        helper();
    }
    public void helper() {}
}"#,
        );
        let call = result
            .calls
            .iter()
            .find(|c| c.callee_name == "helper")
            .expect("should find call to helper");
        assert_eq!(call.caller_name, "caller");
    }

    #[test]
    fn test_extract_qualified_method_call() {
        let result = parse(
            r#"public class Foo {
    public void run() {
        bar.process();
    }
}"#,
        );
        let call = result
            .calls
            .iter()
            .find(|c| c.callee_name == "process")
            .expect("should find call to process");
        assert_eq!(call.caller_name, "run");
    }

    #[test]
    fn test_extract_constructor_call() {
        let result = parse(
            r#"public class Foo {
    public void create() {
        Bar b = new Bar();
    }
}"#,
        );
        let call = result
            .calls
            .iter()
            .find(|c| c.callee_name == "Bar")
            .expect("should find constructor call to Bar");
        assert_eq!(call.caller_name, "create");
    }

    #[test]
    fn test_extract_chained_method_calls() {
        let result = parse(
            r#"public class Foo {
    public void run() {
        builder.setName("x").build();
    }
}"#,
        );
        let names: Vec<&str> = result
            .calls
            .iter()
            .map(|c| c.callee_name.as_str())
            .collect();
        assert!(names.contains(&"setName"), "calls: {:?}", names);
        assert!(names.contains(&"build"), "calls: {:?}", names);
    }

    // --- Type reference extraction ---

    #[test]
    fn test_type_ref_parameter() {
        let result = parse(
            r#"public class Foo {
    public void process(Config cfg) {}
}"#,
        );
        let refs: Vec<&str> = result
            .type_refs
            .iter()
            .map(|r| r.to_type.as_str())
            .collect();
        assert!(refs.contains(&"Config"), "type_refs: {:?}", refs);
    }

    #[test]
    fn test_type_ref_return_type() {
        let result = parse(
            r#"public class Foo {
    public Response handle() { return null; }
}"#,
        );
        let refs: Vec<&str> = result
            .type_refs
            .iter()
            .map(|r| r.to_type.as_str())
            .collect();
        assert!(refs.contains(&"Response"), "type_refs: {:?}", refs);
    }

    #[test]
    fn test_type_ref_field_type() {
        let result = parse(
            r#"public class Foo {
    private AppConfig config;
}"#,
        );
        let refs: Vec<&str> = result
            .type_refs
            .iter()
            .map(|r| r.to_type.as_str())
            .collect();
        assert!(refs.contains(&"AppConfig"), "type_refs: {:?}", refs);
    }

    #[test]
    fn test_type_ref_builtin_filtered() {
        let result = parse(
            r#"public class Foo {
    public void process(int x, String s, boolean b) {}
}"#,
        );
        let refs: Vec<&str> = result
            .type_refs
            .iter()
            .map(|r| r.to_type.as_str())
            .collect();
        assert!(!refs.contains(&"String"), "builtins should be filtered");
    }

    // --- Resolver ---

    #[test]
    fn test_resolve_import_maven_layout() {
        let tmp = tempfile::tempdir().unwrap();
        let src_dir = tmp.path().join("src/main/java/com/example");
        std::fs::create_dir_all(&src_dir).unwrap();
        std::fs::write(src_dir.join("Foo.java"), "public class Foo {}").unwrap();

        let resolver = JavaResolver::new(tmp.path());
        let resolved = resolver.resolve_import("com/example/Foo", "App.java", tmp.path());
        assert!(resolved.is_some(), "should resolve to Foo.java");
        assert!(resolved.unwrap().ends_with("Foo.java"));
    }

    #[test]
    fn test_resolve_import_wildcard() {
        let tmp = tempfile::tempdir().unwrap();
        let src_dir = tmp.path().join("src/main/java/com/example");
        std::fs::create_dir_all(&src_dir).unwrap();
        std::fs::write(src_dir.join("Foo.java"), "public class Foo {}").unwrap();

        let resolver = JavaResolver::new(tmp.path());
        // Wildcard import: com/example::* — should strip ::* and try com/example.java (won't exist)
        let resolved = resolver.resolve_import("com/example::*", "App.java", tmp.path());
        // Wildcard resolves to com/example.java which doesn't exist
        assert!(
            resolved.is_none(),
            "wildcard import resolves to directory, not a file"
        );
    }

    #[test]
    fn test_resolve_import_not_found() {
        let tmp = tempfile::tempdir().unwrap();
        let src_dir = tmp.path().join("src/main/java");
        std::fs::create_dir_all(&src_dir).unwrap();

        let resolver = JavaResolver::new(tmp.path());
        let resolved = resolver.resolve_import("com/example/Missing", "App.java", tmp.path());
        assert!(resolved.is_none());
    }

    #[test]
    fn test_resolve_import_flat_src_layout() {
        let tmp = tempfile::tempdir().unwrap();
        let src_dir = tmp.path().join("src/com/example");
        std::fs::create_dir_all(&src_dir).unwrap();
        std::fs::write(src_dir.join("Bar.java"), "public class Bar {}").unwrap();

        let resolver = JavaResolver::new(tmp.path());
        let resolved = resolver.resolve_import("com/example/Bar", "App.java", tmp.path());
        assert!(resolved.is_some(), "should resolve via src/ fallback");
        assert!(resolved.unwrap().ends_with("Bar.java"));
    }

    #[test]
    fn test_resolve_import_no_source_dirs() {
        let tmp = tempfile::tempdir().unwrap();
        // No src/ or src/main/java — falls back to project root
        std::fs::create_dir_all(tmp.path().join("com/example")).unwrap();
        std::fs::write(
            tmp.path().join("com/example/Baz.java"),
            "public class Baz {}",
        )
        .unwrap();

        let resolver = JavaResolver::new(tmp.path());
        let resolved = resolver.resolve_import("com/example/Baz", "App.java", tmp.path());
        assert!(resolved.is_some(), "should resolve from project root");
        assert!(resolved.unwrap().ends_with("Baz.java"));
    }

    // --- Edge cases ---

    #[test]
    fn test_empty_class() {
        let result = parse("public class Empty {}");
        assert!(!result.has_errors);
        assert_eq!(result.symbols.len(), 1);
        assert_eq!(result.symbols[0].name, "Empty");
        assert_eq!(result.symbols[0].kind, SymbolKind::Class);
    }

    #[test]
    fn test_interface_with_default_method() {
        let result = parse(
            r#"public interface Service {
    void run();
    default void stop() {}
}"#,
        );
        assert!(!result.has_errors);
        let iface = result.symbols.iter().find(|s| s.name == "Service").unwrap();
        assert_eq!(iface.kind, SymbolKind::Interface);
        let methods: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Method)
            .collect();
        // run() and stop() are both method_declarations
        assert_eq!(methods.len(), 2);
    }

    #[test]
    fn test_empty_file() {
        let result = parse("");
        assert!(!result.has_errors);
        assert!(result.symbols.is_empty());
    }

    #[test]
    fn test_malformed_file() {
        let result = parse("public class {{ invalid }}");
        assert!(result.has_errors);
    }

    #[test]
    fn test_class_with_generics() {
        let result = parse("public class Box<T> { private T value; }");
        assert!(!result.has_errors);
        let cls = result.symbols.iter().find(|s| s.name == "Box").unwrap();
        assert_eq!(cls.kind, SymbolKind::Class);
    }

    #[test]
    fn test_abstract_class_with_abstract_method() {
        let result = parse(
            r#"public abstract class Shape {
    public abstract double area();
    public void describe() {}
}"#,
        );
        assert!(!result.has_errors);
        let methods: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Method)
            .collect();
        assert_eq!(methods.len(), 2);
        assert_eq!(methods[0].name, "area");
        assert_eq!(methods[1].name, "describe");
    }

    #[test]
    fn test_enum_with_methods() {
        let result = parse(
            r#"public enum Planet {
    EARTH, MARS;
    public double mass() { return 0.0; }
}"#,
        );
        assert!(!result.has_errors);
        let consts: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Const)
            .collect();
        assert_eq!(consts.len(), 2);
        let methods: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Method)
            .collect();
        assert_eq!(methods.len(), 1);
        assert_eq!(methods[0].name, "mass");
    }

    #[test]
    fn test_multiple_classes_in_file() {
        let result = parse(
            r#"class Foo { void foo() {} }
class Bar { void bar() {} }"#,
        );
        assert!(!result.has_errors);
        let classes: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Class)
            .collect();
        assert_eq!(classes.len(), 2);
    }

    #[test]
    fn test_package_declaration_ignored() {
        let result = parse(
            r#"package com.example;
public class Foo {}"#,
        );
        assert!(!result.has_errors);
        // package_declaration should not produce symbols
        assert_eq!(result.symbols.len(), 1);
        assert_eq!(result.symbols[0].name, "Foo");
    }

    #[test]
    fn test_is_java_builtin_positive() {
        assert!(is_java_builtin("int"));
        assert!(is_java_builtin("String"));
        assert!(is_java_builtin("Object"));
        assert!(is_java_builtin("Override"));
        assert!(is_java_builtin("Exception"));
        assert!(is_java_builtin("Boolean"));
    }

    #[test]
    fn test_is_java_builtin_negative() {
        assert!(!is_java_builtin("MyClass"));
        assert!(!is_java_builtin("AppConfig"));
        assert!(!is_java_builtin(""));
    }

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

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

    #[test]
    fn test_detect_jvm_source_roots_maven() {
        let tmp = tempfile::tempdir().unwrap();
        std::fs::create_dir_all(tmp.path().join("src/main/java")).unwrap();
        std::fs::create_dir_all(tmp.path().join("src/test/java")).unwrap();
        let roots = detect_jvm_source_roots(tmp.path());
        assert!(roots.iter().any(|r| r.ends_with("src/main/java")));
        assert!(roots.iter().any(|r| r.ends_with("src/test/java")));
    }

    #[test]
    fn test_detect_jvm_source_roots_fallback() {
        let tmp = tempfile::tempdir().unwrap();
        // No src/ directories at all — should fall back to project root
        let roots = detect_jvm_source_roots(tmp.path());
        assert_eq!(roots.len(), 1);
        assert_eq!(roots[0], tmp.path().to_path_buf());
    }

    #[test]
    fn test_class_with_implements() {
        let result = parse(
            r#"public class Server implements Runnable {
    public void run() {}
}"#,
        );
        assert!(!result.has_errors);
        let cls = result.symbols.iter().find(|s| s.name == "Server").unwrap();
        assert_eq!(cls.kind, SymbolKind::Class);
        let sig = cls.signature.as_ref().unwrap();
        assert!(
            sig.contains("Runnable"),
            "signature should mention interface: {}",
            sig
        );
    }

    #[test]
    fn test_interface_extends() {
        let result = parse("public interface Worker extends Runnable {}");
        assert!(!result.has_errors);
        let iface = result.symbols.iter().find(|s| s.name == "Worker").unwrap();
        assert_eq!(iface.kind, SymbolKind::Interface);
    }

    #[test]
    fn test_type_ref_generic_parameter() {
        let result = parse(
            r#"public class Foo {
    public void process(List<Config> items) {}
}"#,
        );
        let refs: Vec<&str> = result
            .type_refs
            .iter()
            .map(|r| r.to_type.as_str())
            .collect();
        assert!(refs.contains(&"Config"), "type_refs: {:?}", refs);
    }

    #[test]
    fn test_call_inside_constructor() {
        let result = parse(
            r#"public class Foo {
    public Foo() {
        init();
    }
    private void init() {}
}"#,
        );
        let call = result
            .calls
            .iter()
            .find(|c| c.callee_name == "init")
            .expect("should find call to init from constructor");
        assert_eq!(call.caller_name, "Foo");
    }

    #[test]
    fn test_static_method() {
        let result = parse(
            r#"public class Utils {
    public static void helper() {}
}"#,
        );
        assert!(!result.has_errors);
        let methods: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Method)
            .collect();
        assert_eq!(methods.len(), 1);
        assert_eq!(methods[0].name, "helper");
    }

    #[test]
    fn test_split_last_segment() {
        assert_eq!(
            split_last_segment("com/example/Foo"),
            Some(("com/example", "Foo"))
        );
        assert_eq!(split_last_segment("Foo"), None);
    }

    #[test]
    fn test_resolve_test_source_root() {
        let tmp = tempfile::tempdir().unwrap();
        let test_dir = tmp.path().join("src/test/java/com/example");
        std::fs::create_dir_all(&test_dir).unwrap();
        std::fs::write(test_dir.join("FooTest.java"), "public class FooTest {}").unwrap();

        let resolver = JavaResolver::new(tmp.path());
        let resolved = resolver.resolve_import("com/example/FooTest", "Test.java", tmp.path());
        assert!(resolved.is_some(), "should resolve from test source root");
        assert!(resolved.unwrap().ends_with("FooTest.java"));
    }
}
