// Kotlin language support: parser + resolver

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

use tree_sitter::Language;

use astmap_core::SymbolKind;

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

pub(super) fn lang() -> LanguageSupport {
    LanguageSupport {
        name: "kotlin",
        extensions: &["kt", "kts"],
        parser: &KotlinParser,
        resolver_factory: |root| Box::new(KotlinResolver::new(root)),
        config_files: &["build.gradle.kts", "build.gradle"],
        sibling_fn: |_| None, // Kotlin has no sibling expansion
    }
}

// ── Parser ──

pub(crate) struct KotlinParser;

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

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

fn kotlin_language() -> Language {
    tree_sitter_kotlin_ng::LANGUAGE.into()
}

fn parse_kotlin_file(source: &str) -> ParseResult {
    parse_with_tree_sitter(
        source,
        &kotlin_language(),
        |root, src, symbols, imports| extract_from_node(root, src, symbols, imports, None),
        extract_calls,
        extract_type_refs,
    )
}

// ---------------------------------------------------------------------------
// 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_declaration(&node, source, parent_index) {
                let idx = symbols.len();
                symbols.push(sym);
                recurse_into_class_body(&node, source, symbols, imports, idx);
                return;
            }
        }
        "object_declaration" => {
            if let Some(sym) = extract_object_declaration(&node, source, parent_index) {
                let idx = symbols.len();
                symbols.push(sym);
                // Recurse into class_body child (no "body" field in this grammar)
                if let Some(body) = find_child_by_kind(&node, "class_body") {
                    recurse_children(&body, source, symbols, imports, Some(idx));
                }
                return;
            }
        }
        "companion_object" => {
            let sym = extract_companion_object(&node, source, parent_index);
            let idx = symbols.len();
            symbols.push(sym);
            // Recurse into class_body child
            if let Some(body) = find_child_by_kind(&node, "class_body") {
                recurse_children(&body, source, symbols, imports, Some(idx));
            }
            return;
        }
        "function_declaration" => {
            if let Some(sym) = extract_function(&node, source, parent_index) {
                symbols.push(sym);
                return;
            }
        }
        "property_declaration" => {
            if let Some(sym) = extract_property(&node, source, parent_index) {
                symbols.push(sym);
                return;
            }
        }
        "type_alias" => {
            if let Some(sym) = extract_type_alias(&node, source) {
                symbols.push(sym);
                return;
            }
        }
        // tree-sitter-kotlin-ng uses "import" as the node kind for each import statement
        "import" => {
            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);
        }
    }
}

/// Find a direct child node with the given kind.
fn find_child_by_kind<'a>(
    node: &'a tree_sitter::Node<'a>,
    kind: &str,
) -> Option<tree_sitter::Node<'a>> {
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            if child.kind() == kind {
                return Some(child);
            }
        }
    }
    None
}

fn recurse_children(
    body: &tree_sitter::Node,
    source: &str,
    symbols: &mut Vec<ExtractedSymbol>,
    imports: &mut Vec<ExtractedImport>,
    parent_index: Option<usize>,
) {
    for i in 0..body.child_count() {
        if let Some(child) = body.child(i as u32) {
            extract_from_node(child, source, symbols, imports, parent_index);
        }
    }
}

fn recurse_into_class_body(
    node: &tree_sitter::Node,
    source: &str,
    symbols: &mut Vec<ExtractedSymbol>,
    imports: &mut Vec<ExtractedImport>,
    class_idx: usize,
) {
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            match child.kind() {
                "class_body" => {
                    recurse_children(&child, source, symbols, imports, Some(class_idx));
                    return;
                }
                "enum_class_body" => {
                    for j in 0..child.child_count() {
                        if let Some(entry) = child.child(j as u32) {
                            if entry.kind() == "enum_entry" {
                                if let Some(sym) =
                                    extract_enum_entry(&entry, source, Some(class_idx))
                                {
                                    symbols.push(sym);
                                }
                            } else {
                                extract_from_node(entry, source, symbols, imports, Some(class_idx));
                            }
                        }
                    }
                    return;
                }
                _ => {}
            }
        }
    }
}

/// Disambiguate class_declaration into Class, Enum, Interface, or Annotation.
///
/// tree-sitter-kotlin-ng uses `class_declaration` for all class-like types.
/// We check:
/// 1. `enum_class_body` child → Enum
/// 2. `modifiers` child containing "enum" → Enum
/// 3. `modifiers` child containing "annotation" → Annotation
/// 4. `interface` keyword child → Interface
/// 5. Otherwise → Class
fn classify_class_declaration(node: &tree_sitter::Node, source: &str) -> SymbolKind {
    // 1. Check for enum_class_body child → Enum
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            if child.kind() == "enum_class_body" {
                return SymbolKind::Enum;
            }
        }
    }

    // 2. Check for "interface" keyword child (direct child of class_declaration)
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            if child.kind() == "interface" {
                return SymbolKind::Interface;
            }
        }
    }

    // 3. Check modifiers for "annotation" or "enum" (enum without enum_class_body)
    if let Some(modifiers) = find_child_by_kind(node, "modifiers") {
        let mods_text = node_text(&modifiers, source);
        if mods_text.contains("annotation") {
            return SymbolKind::Annotation;
        }
        if mods_text.contains("enum") {
            return SymbolKind::Enum;
        }
    }

    // Default: Class (covers class, data class, sealed class, inner class, value class)
    SymbolKind::Class
}

fn extract_class_declaration(
    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 = classify_class_declaration(node, source);
    Some(symbol_from_node(name, kind, node, source, parent_index))
}

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

fn extract_companion_object(
    node: &tree_sitter::Node,
    source: &str,
    parent_index: Option<usize>,
) -> ExtractedSymbol {
    // companion object may optionally have a name field
    let name = node
        .child_by_field_name("name")
        .map(|n| node_text(&n, source))
        .unwrap_or_else(|| "Companion".to_string());
    symbol_from_node(name, SymbolKind::Module, 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_property(
    node: &tree_sitter::Node,
    source: &str,
    parent_index: Option<usize>,
) -> Option<ExtractedSymbol> {
    // property_declaration has no "name" field in tree-sitter-kotlin-ng.
    // Instead, it has a `variable_declaration` child containing an `identifier` child.
    let name = find_property_name(node, source)?;

    // Determine kind: top-level val → Const, otherwise Variable
    let kind = if parent_index.is_none() {
        if has_val_keyword(node) {
            SymbolKind::Const
        } else {
            SymbolKind::Variable
        }
    } else {
        SymbolKind::Variable
    };

    Some(symbol_from_node(name, kind, node, source, parent_index))
}

/// Find the property name from a property_declaration node.
///
/// The structure is: property_declaration → variable_declaration → identifier
fn find_property_name(node: &tree_sitter::Node, source: &str) -> Option<String> {
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            if child.kind() == "variable_declaration" {
                // Find the identifier inside variable_declaration
                for j in 0..child.child_count() {
                    if let Some(id) = child.child(j as u32) {
                        if id.kind() == "identifier" || id.kind() == "simple_identifier" {
                            return Some(node_text(&id, source));
                        }
                    }
                }
            }
        }
    }
    None
}

/// Check if a property_declaration has a `val` keyword child (vs `var`).
fn has_val_keyword(node: &tree_sitter::Node) -> bool {
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            if child.kind() == "val" {
                return true;
            }
            if child.kind() == "var" {
                return false;
            }
        }
    }
    false
}

fn extract_type_alias(node: &tree_sitter::Node, source: &str) -> Option<ExtractedSymbol> {
    // type_alias has a "type" field (not "name"!) for the identifier in tree-sitter-kotlin-ng.
    // The structure: typealias + identifier + = + user_type
    let name = node
        .child_by_field_name("type")
        .map(|n| node_text(&n, source))
        .or_else(|| {
            // Fallback: find the identifier child that is not a keyword
            for i in 0..node.child_count() {
                if let Some(child) = node.child(i as u32) {
                    if child.kind() == "identifier" {
                        return Some(node_text(&child, source));
                    }
                }
            }
            None
        })?;

    Some(symbol_from_node(
        name,
        SymbolKind::TypeAlias,
        node,
        source,
        None,
    ))
}

fn extract_enum_entry(
    node: &tree_sitter::Node,
    source: &str,
    parent_index: Option<usize>,
) -> Option<ExtractedSymbol> {
    // enum_entry has an `identifier` child
    let name = find_first_identifier(node, source)?;
    Some(symbol_from_node(
        name,
        SymbolKind::Const,
        node,
        source,
        parent_index,
    ))
}

/// Find the first `identifier` child of a node.
fn find_first_identifier(node: &tree_sitter::Node, source: &str) -> Option<String> {
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            if child.kind() == "identifier" || child.kind() == "simple_identifier" {
                return Some(node_text(&child, source));
            }
        }
    }
    None
}

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

fn extract_import(node: &tree_sitter::Node, source: &str) -> Option<ExtractedImport> {
    // tree-sitter-kotlin-ng "import" node structure:
    //   import (keyword) + qualified_identifier + optional (. + *) + optional (as + identifier)
    //
    // We extract the qualified_identifier text, check for wildcard and alias children.

    let mut qualified_path: Option<String> = None;
    let mut is_wildcard = false;
    let mut alias: Option<String> = None;
    let mut saw_as = false;

    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            match child.kind() {
                "qualified_identifier" => {
                    qualified_path = Some(node_text(&child, source));
                }
                "*" => {
                    is_wildcard = true;
                }
                "as" => {
                    saw_as = true;
                }
                "identifier" if saw_as => {
                    alias = Some(node_text(&child, source));
                }
                _ => {}
            }
        }
    }

    let dot_path = qualified_path?;
    let slash_path = dot_path.replace('.', "/");

    if is_wildcard {
        Some(ExtractedImport {
            path: format!("{}::*", slash_path),
            imported_symbols: vec![],
        })
    } else if let Some(alias_name) = alias {
        Some(ExtractedImport {
            path: slash_path,
            imported_symbols: vec![alias_name],
        })
    } else {
        // Regular import: use last segment as imported symbol
        let last_segment = dot_path.rsplit('.').next().unwrap_or(&dot_path).to_string();
        Some(ExtractedImport {
            path: slash_path,
            imported_symbols: vec![last_segment],
        })
    }
}

// ---------------------------------------------------------------------------
// 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>,
) {
    if node.kind() == "call_expression" {
        // call_expression: callee_expression + value_arguments
        // The first child is the function/method being called
        if let Some(callee_node) = node.child(0) {
            let callee_name = extract_callee_name(&callee_node, source);
            if !is_kotlin_call_builtin(&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,
                        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);
        }
    }
}

/// Extract the callee name from the function part of a call_expression.
/// Handles simple identifiers and navigation expressions (foo.bar).
fn extract_callee_name(node: &tree_sitter::Node, source: &str) -> String {
    match node.kind() {
        "navigation_expression" => {
            // foo.bar — extract the last identifier segment
            let count = node.child_count();
            if count > 0 {
                if let Some(last) = node.child((count - 1) as u32) {
                    let text = node_text(&last, source);
                    // navigation_suffix may have a leading dot
                    return text.trim_start_matches('.').to_string();
                }
            }
            // Fallback: last dot-segment of full text
            let full = node_text(node, source);
            full.rsplit('.').next().unwrap_or(&full).to_string()
        }
        "identifier" | "simple_identifier" => node_text(node, source),
        _ => {
            let full = node_text(node, source);
            full.rsplit('.').next().unwrap_or(&full).to_string()
        }
    }
}

fn is_kotlin_call_builtin(name: &str) -> bool {
    matches!(
        name,
        "println"
            | "print"
            | "require"
            | "requireNotNull"
            | "check"
            | "checkNotNull"
            | "error"
            | "assert"
            | "TODO"
            | "run"
            | "let"
            | "also"
            | "apply"
            | "with"
            | "lazy"
            | "repeat"
            | "buildString"
            | "buildList"
            | "buildSet"
            | "buildMap"
            | "listOf"
            | "setOf"
            | "mapOf"
            | "mutableListOf"
            | "mutableSetOf"
            | "mutableMapOf"
            | "arrayOf"
            | "intArrayOf"
            | "longArrayOf"
            | "emptyList"
            | "emptySet"
            | "emptyMap"
            | "listOfNotNull"
            | "to"
            | "arrayOfNulls"
            | "hashMapOf"
            | "hashSetOf"
            | "linkedMapOf"
            | "linkedSetOf"
            | "sortedMapOf"
            | "sortedSetOf"
    )
}

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

fn extract_type_refs(
    root: tree_sitter::Node,
    source: &str,
    symbols: &[ExtractedSymbol],
) -> Vec<ExtractedTypeRef> {
    let mut refs = Vec::new();
    walk_kotlin_type_refs(root, source, symbols, &mut refs);
    refs
}

fn walk_kotlin_type_refs(
    node: tree_sitter::Node,
    source: &str,
    symbols: &[ExtractedSymbol],
    refs: &mut Vec<ExtractedTypeRef>,
) {
    if node.kind() == "user_type" {
        // user_type: identifier (+ optional type_arguments)
        // The first child is the type name identifier
        if let Some(id) = node.child(0) {
            if id.kind() == "identifier"
                || id.kind() == "type_identifier"
                || id.kind() == "simple_identifier"
            {
                let type_name = node_text(&id, source);
                let line = node.start_position().row + 1;
                if !is_kotlin_builtin(&type_name) {
                    if let Some(from_sym) = find_enclosing_symbol(symbols, line) {
                        refs.push(ExtractedTypeRef {
                            from_symbol: from_sym.to_string(),
                            to_type: type_name,
                            line,
                        });
                    }
                }
            }
        }
        return; // Don't recurse into user_type children (generic params etc.)
    }

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

fn is_kotlin_builtin(name: &str) -> bool {
    matches!(
        name,
        "Int"
            | "Long"
            | "Float"
            | "Double"
            | "Boolean"
            | "Char"
            | "Byte"
            | "Short"
            | "String"
            | "Unit"
            | "Nothing"
            | "Any"
            | "Array"
            | "List"
            | "Set"
            | "Map"
            | "MutableList"
            | "MutableSet"
            | "MutableMap"
            | "Pair"
            | "Triple"
            | "Sequence"
            | "Iterable"
            | "Comparable"
            | "Throwable"
            | "Exception"
            | "RuntimeException"
            | "Error"
            | "Annotation"
            | "Deprecated"
            | "Suppress"
            | "JvmStatic"
            | "JvmField"
            | "JvmOverloads"
    )
}

// ── Resolver ──

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

impl KotlinResolver {
    fn new(root: &Path) -> Self {
        Self {
            source_roots: detect_jvm_source_roots(root),
        }
    }
}

impl LanguageResolver for KotlinResolver {
    fn resolve_import(
        &self,
        import_path: &str,
        _source_file: &str,
        project_root: &Path,
    ) -> Option<PathBuf> {
        // Strip wildcard suffix if present
        let effective_path = import_path.strip_suffix("::*").unwrap_or(import_path);

        let project_root_buf = project_root.to_path_buf();
        for root in self
            .source_roots
            .iter()
            .chain(std::iter::once(&project_root_buf))
        {
            // Try .kt extension
            let kt_path = root.join(format!("{}.kt", effective_path));
            if kt_path.exists() {
                return kt_path.canonicalize().ok();
            }

            // Try .kts extension
            let kts_path = root.join(format!("{}.kts", effective_path));
            if kts_path.exists() {
                return kts_path.canonicalize().ok();
            }

            // Try .java extension (cross-language interop)
            let java_path = root.join(format!("{}.java", effective_path));
            if java_path.exists() {
                return java_path.canonicalize().ok();
            }

            // Try as directory with package-level file
            let dir = root.join(effective_path);
            if dir.is_dir() {
                if let Ok(entries) = std::fs::read_dir(&dir) {
                    for entry in entries.flatten() {
                        let path = entry.path();
                        if path.extension().is_some_and(|e| e == "kt") {
                            return path.canonicalize().ok();
                        }
                    }
                }
            }
        }

        // External dependency: skip
        None
    }
}

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

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

    fn parse(source: &str) -> ParseResult {
        KotlinParser.parse(source, &PathBuf::from("test.kt"))
    }

    // --- Symbol extraction: classes ---

    #[test]
    fn test_parse_class() {
        let result = parse("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);
    }

    #[test]
    fn test_parse_data_class() {
        let result = parse("data class User(val name: String, val age: Int)");
        assert!(!result.has_errors);
        let classes: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Class)
            .collect();
        assert_eq!(classes.len(), 1);
        assert_eq!(classes[0].name, "User");
    }

    #[test]
    fn test_parse_sealed_class() {
        let result = parse("sealed class Result {}");
        assert!(!result.has_errors);
        let classes: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Class)
            .collect();
        assert_eq!(classes.len(), 1);
        assert_eq!(classes[0].name, "Result");
    }

    #[test]
    fn test_parse_enum_class() {
        let result = parse(
            r#"enum class 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");

        // Enum entries should be extracted as Const
        let consts: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Const)
            .collect();
        assert!(
            consts.len() >= 3,
            "expected 3 enum entries, got {:?}",
            consts
        );
    }

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

    #[test]
    fn test_parse_interface() {
        // Use multiline body so the parser does not produce errors
        let result = parse("interface Drawable {\n    fun draw()\n}");
        assert!(!result.has_errors);
        let interfaces: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Interface)
            .collect();
        assert_eq!(interfaces.len(), 1);
        assert_eq!(interfaces[0].name, "Drawable");
    }

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

    #[test]
    fn test_parse_value_class() {
        let result = parse("@JvmInline\nvalue class Password(val value: String)");
        assert!(!result.has_errors);
        let classes: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Class)
            .collect();
        assert_eq!(classes.len(), 1);
        assert_eq!(classes[0].name, "Password");
    }

    #[test]
    fn test_parse_inner_class() {
        let result = parse(
            r#"class Outer {
    inner class Inner {}
}"#,
        );
        assert!(!result.has_errors);
        let classes: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Class)
            .collect();
        assert!(classes.len() >= 2, "expected Outer and Inner classes");

        let inner = result.symbols.iter().find(|s| s.name == "Inner").unwrap();
        assert_eq!(inner.kind, SymbolKind::Class);
        assert!(inner.parent_index.is_some(), "Inner should have parent");
    }

    // --- Symbol extraction: objects ---

    #[test]
    fn test_parse_object() {
        let result = parse("object Singleton {}");
        assert!(!result.has_errors);
        let objs: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.name == "Singleton")
            .collect();
        assert_eq!(objs.len(), 1);
        assert_eq!(objs[0].kind, SymbolKind::Class);
    }

    #[test]
    fn test_parse_companion_object_default_name() {
        let result = parse(
            r#"class Foo {
    companion object {
        fun create(): Foo = Foo()
    }
}"#,
        );
        let companions: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Module)
            .collect();
        assert_eq!(companions.len(), 1);
        assert_eq!(companions[0].name, "Companion");
        assert!(companions[0].parent_index.is_some());
    }

    #[test]
    fn test_parse_companion_object_named() {
        let result = parse(
            r#"class Bar {
    companion object Factory {
        fun create(): Bar = Bar()
    }
}"#,
        );
        let companions: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Module)
            .collect();
        assert_eq!(companions.len(), 1);
        assert_eq!(companions[0].name, "Factory");
    }

    // --- Symbol extraction: functions ---

    #[test]
    fn test_parse_top_level_function() {
        let result = parse("fun hello() {}");
        assert!(!result.has_errors);
        let funcs: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Function)
            .collect();
        assert_eq!(funcs.len(), 1);
        assert_eq!(funcs[0].name, "hello");
        assert!(funcs[0].parent_index.is_none());
    }

    #[test]
    fn test_parse_method_in_class() {
        let result = parse(
            r#"class Greeter {
    fun greet(name: String): String {
        return "Hello, $name"
    }
}"#,
        );
        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, "greet");
        assert!(methods[0].parent_index.is_some());
    }

    #[test]
    fn test_parse_extension_function() {
        let result = parse("fun String.addExclamation(): String = this + \"!\"");
        assert!(!result.has_errors);
        let funcs: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Function)
            .collect();
        assert_eq!(funcs.len(), 1);
        assert_eq!(funcs[0].name, "addExclamation");
    }

    // --- Symbol extraction: properties ---

    #[test]
    fn test_parse_top_level_val_as_const() {
        let result = parse("val MAX_SIZE = 100");
        assert!(!result.has_errors);
        let consts: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Const)
            .collect();
        assert_eq!(consts.len(), 1);
        assert_eq!(consts[0].name, "MAX_SIZE");
    }

    #[test]
    fn test_parse_top_level_var_as_variable() {
        let result = parse("var counter = 0");
        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, "counter");
    }

    #[test]
    fn test_parse_class_property_as_variable() {
        let result = parse(
            r#"class Foo {
    val name: String = "test"
}"#,
        );
        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, "name");
        assert!(vars[0].parent_index.is_some());
    }

    // --- Symbol extraction: type alias ---

    #[test]
    fn test_parse_type_alias() {
        let result = parse("typealias StringList = List<String>");
        assert!(!result.has_errors);
        let aliases: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::TypeAlias)
            .collect();
        assert_eq!(aliases.len(), 1);
        assert_eq!(aliases[0].name, "StringList");
    }

    // --- Symbol extraction: enum entries ---

    #[test]
    fn test_parse_enum_entries_as_const() {
        let result = parse(
            r#"enum class Direction {
    NORTH,
    SOUTH,
    EAST,
    WEST
}"#,
        );
        assert!(!result.has_errors);
        let consts: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Const)
            .collect();
        assert!(consts.len() >= 4, "expected 4 enum entries");

        let names: Vec<&str> = consts.iter().map(|s| s.name.as_str()).collect();
        assert!(names.contains(&"NORTH"));
        assert!(names.contains(&"SOUTH"));
        assert!(names.contains(&"EAST"));
        assert!(names.contains(&"WEST"));

        // All entries should have parent_index pointing to the enum
        for c in &consts {
            assert!(
                c.parent_index.is_some(),
                "enum entry {} should have parent",
                c.name
            );
        }
    }

    // --- Nested declarations ---

    #[test]
    fn test_nested_class_parent_index() {
        let result = parse(
            r#"class Outer {
    class Nested {
        fun inner() {}
    }
    fun outer() {}
}"#,
        );
        assert!(!result.has_errors);

        let outer = result.symbols.iter().find(|s| s.name == "Outer").unwrap();
        assert!(outer.parent_index.is_none());

        let nested = result.symbols.iter().find(|s| s.name == "Nested").unwrap();
        assert_eq!(nested.parent_index, Some(0));

        let inner_fn = result.symbols.iter().find(|s| s.name == "inner").unwrap();
        assert_eq!(inner_fn.kind, SymbolKind::Method);

        let outer_fn = result.symbols.iter().find(|s| s.name == "outer").unwrap();
        assert_eq!(outer_fn.kind, SymbolKind::Method);
        assert_eq!(outer_fn.parent_index, Some(0));
    }

    // --- Import extraction ---

    #[test]
    fn test_import_regular() {
        let result = parse("import com.example.Foo\n");
        assert!(!result.has_errors);
        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.*\n");
        assert!(!result.has_errors);
        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_alias() {
        let result = parse("import com.example.Foo as Bar\n");
        assert!(!result.has_errors);
        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_multiple_imports() {
        let result = parse(
            r#"import com.example.Foo
import com.example.Bar
import org.other.*
"#,
        );
        assert!(!result.has_errors);
        assert!(
            result.imports.len() >= 3,
            "expected at least 3 imports, got {}",
            result.imports.len()
        );
    }

    // --- Call extraction ---

    #[test]
    fn test_call_simple() {
        let result = parse(
            r#"fun main() {
    doWork()
}"#,
        );
        assert!(!result.has_errors);
        let calls: Vec<_> = result
            .calls
            .iter()
            .filter(|c| c.callee_name == "doWork")
            .collect();
        assert_eq!(calls.len(), 1);
        assert_eq!(calls[0].caller_name, "main");
    }

    #[test]
    fn test_call_navigation() {
        let result = parse(
            r#"fun process() {
    service.execute()
}"#,
        );
        assert!(!result.has_errors);
        let calls: Vec<_> = result
            .calls
            .iter()
            .filter(|c| c.callee_name == "execute")
            .collect();
        assert_eq!(calls.len(), 1);
        assert_eq!(calls[0].caller_name, "process");
    }

    #[test]
    fn test_call_builtin_filtered() {
        let result = parse(
            r#"fun main() {
    println("hello")
    listOf(1, 2, 3)
}"#,
        );
        assert!(!result.has_errors);
        let non_builtins: Vec<_> = result
            .calls
            .iter()
            .filter(|c| c.callee_name == "println" || c.callee_name == "listOf")
            .collect();
        assert_eq!(non_builtins.len(), 0);
    }

    // --- Type reference extraction ---

    #[test]
    fn test_type_ref_parameter() {
        let result = parse("fun process(input: CustomType): ResultType { return ResultType() }");
        assert!(!result.has_errors);
        let type_names: Vec<&str> = result
            .type_refs
            .iter()
            .map(|r| r.to_type.as_str())
            .collect();
        assert!(
            type_names.contains(&"CustomType"),
            "should reference CustomType, got {:?}",
            type_names
        );
        assert!(
            type_names.contains(&"ResultType"),
            "should reference ResultType, got {:?}",
            type_names
        );
    }

    #[test]
    fn test_type_ref_builtin_filtered() {
        let result = parse("fun count(items: List<String>): Int { return items.size }");
        assert!(!result.has_errors);
        let refs: Vec<&str> = result
            .type_refs
            .iter()
            .map(|r| r.to_type.as_str())
            .collect();
        assert!(
            !refs.contains(&"Int"),
            "Int should be filtered, got {:?}",
            refs
        );
        assert!(
            !refs.contains(&"String"),
            "String should be filtered, got {:?}",
            refs
        );
        assert!(
            !refs.contains(&"List"),
            "List should be filtered, got {:?}",
            refs
        );
    }

    #[test]
    fn test_type_ref_class_property() {
        let result = parse(
            r#"class Container {
    val service: MyService = MyService()
}"#,
        );
        assert!(!result.has_errors);
        let refs: Vec<&str> = result
            .type_refs
            .iter()
            .map(|r| r.to_type.as_str())
            .collect();
        assert!(
            refs.contains(&"MyService"),
            "should reference MyService, got {:?}",
            refs
        );
    }

    // --- Modifier-based class disambiguation ---

    #[test]
    fn test_classify_class_kinds() {
        let result = parse(
            r#"class Regular {}
data class Data(val x: Int)
sealed class Sealed {}
interface Iface {
    fun draw()
}
sealed interface SealedIface {}
annotation class Anno
enum class E { A, B }
"#,
        );
        assert!(!result.has_errors);

        let regular = result.symbols.iter().find(|s| s.name == "Regular").unwrap();
        assert_eq!(regular.kind, SymbolKind::Class);

        let data = result.symbols.iter().find(|s| s.name == "Data").unwrap();
        assert_eq!(data.kind, SymbolKind::Class);

        let sealed = result.symbols.iter().find(|s| s.name == "Sealed").unwrap();
        assert_eq!(sealed.kind, SymbolKind::Class);

        let iface = result.symbols.iter().find(|s| s.name == "Iface").unwrap();
        assert_eq!(iface.kind, SymbolKind::Interface);

        let sealed_iface = result
            .symbols
            .iter()
            .find(|s| s.name == "SealedIface")
            .unwrap();
        assert_eq!(sealed_iface.kind, SymbolKind::Interface);

        let anno = result.symbols.iter().find(|s| s.name == "Anno").unwrap();
        assert_eq!(anno.kind, SymbolKind::Annotation);

        let e = result.symbols.iter().find(|s| s.name == "E").unwrap();
        assert_eq!(e.kind, SymbolKind::Enum);
    }

    // --- Resolver ---

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

        let resolver = KotlinResolver::new(tmp.path());
        let result = resolver.resolve_import("com/example/Foo", "Main.kt", tmp.path());
        assert!(result.is_some(), "should resolve com/example/Foo.kt");
    }

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

        let resolver = KotlinResolver::new(tmp.path());
        let result = resolver.resolve_import("com/example/Foo::*", "Main.kt", tmp.path());
        assert!(result.is_some(), "should resolve wildcard import");
    }

    #[test]
    fn test_resolver_kts_file() {
        let tmp = tempfile::tempdir().unwrap();
        let src_dir = tmp.path().join("src/main/kotlin/com/example");
        std::fs::create_dir_all(&src_dir).unwrap();
        std::fs::write(src_dir.join("build.kts"), "// build script").unwrap();

        let resolver = KotlinResolver::new(tmp.path());
        let result = resolver.resolve_import("com/example/build", "Main.kt", tmp.path());
        assert!(result.is_some(), "should resolve .kts file");
    }

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

        let resolver = KotlinResolver::new(tmp.path());
        let result = resolver.resolve_import("com/example/Legacy", "Main.kt", tmp.path());
        assert!(
            result.is_some(),
            "should resolve .java file for cross-language interop"
        );
    }

    #[test]
    fn test_resolver_external_not_found() {
        let tmp = tempfile::tempdir().unwrap();
        let resolver = KotlinResolver::new(tmp.path());
        let result = resolver.resolve_import("org/external/Lib", "Main.kt", tmp.path());
        assert!(result.is_none(), "external imports should return None");
    }

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

        let roots = detect_jvm_source_roots(tmp.path());
        assert!(
            roots.len() >= 2,
            "should detect src/main/kotlin and src/test/kotlin"
        );
    }

    #[test]
    fn test_detect_jvm_source_roots_fallback() {
        let tmp = tempfile::tempdir().unwrap();
        let roots = detect_jvm_source_roots(tmp.path());
        assert_eq!(roots.len(), 1);
        assert_eq!(roots[0], tmp.path().to_path_buf());
    }

    // --- Empty and malformed files ---

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

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

    // --- Complex scenarios ---

    #[test]
    fn test_object_with_methods() {
        let result = parse(
            r#"object Utils {
    fun helper(): String = "help"
    val VERSION = "1.0"
}"#,
        );
        assert!(!result.has_errors);

        let obj = result.symbols.iter().find(|s| s.name == "Utils").unwrap();
        assert_eq!(obj.kind, SymbolKind::Class);

        let method = result.symbols.iter().find(|s| s.name == "helper").unwrap();
        assert_eq!(method.kind, SymbolKind::Method);
        assert!(method.parent_index.is_some());
    }

    #[test]
    fn test_abstract_class() {
        let result = parse(
            r#"abstract class Shape {
    abstract fun area(): Double
}"#,
        );
        assert!(!result.has_errors);
        let shape = result.symbols.iter().find(|s| s.name == "Shape").unwrap();
        assert_eq!(shape.kind, SymbolKind::Class);
    }

    #[test]
    fn test_private_top_level_function() {
        let result = parse("private fun helper() {}");
        assert!(!result.has_errors);
        let funcs: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Function)
            .collect();
        assert_eq!(funcs.len(), 1);
        assert_eq!(funcs[0].name, "helper");
    }

    #[test]
    fn test_const_val_property() {
        let result = parse("const val MAX = 100");
        assert!(!result.has_errors);
        let consts: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Const)
            .collect();
        assert_eq!(consts.len(), 1);
        assert_eq!(consts[0].name, "MAX");
    }

    #[test]
    fn test_generic_class() {
        let result = parse("class Box<T>(val value: T) {}");
        assert!(!result.has_errors);
        let classes: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Class)
            .collect();
        assert_eq!(classes.len(), 1);
        assert_eq!(classes[0].name, "Box");
    }

    #[test]
    fn test_suspend_function() {
        let result = parse("suspend fun fetchData(): String { return \"data\" }");
        assert!(!result.has_errors);
        let funcs: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Function)
            .collect();
        assert_eq!(funcs.len(), 1);
        assert_eq!(funcs[0].name, "fetchData");
    }

    #[test]
    fn test_interface_with_methods() {
        let result = parse(
            r#"interface Repository {
    fun findAll(): List<String>
    fun save(item: String)
}"#,
        );
        assert!(!result.has_errors);
        let iface = result
            .symbols
            .iter()
            .find(|s| s.name == "Repository")
            .unwrap();
        assert_eq!(iface.kind, SymbolKind::Interface);

        let methods: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Method)
            .collect();
        assert_eq!(methods.len(), 2);
    }

    #[test]
    fn test_enum_with_methods() {
        let result = parse(
            r#"enum class Planet {
    EARTH,
    MARS;

    fun description(): String = name
}"#,
        );
        assert!(!result.has_errors);

        let e = result.symbols.iter().find(|s| s.name == "Planet").unwrap();
        assert_eq!(e.kind, SymbolKind::Enum);

        let entries: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Const)
            .collect();
        assert!(entries.len() >= 2);
    }

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

    #[test]
    fn test_open_class() {
        let result = parse("open class Base {}");
        assert!(!result.has_errors);
        let base = result.symbols.iter().find(|s| s.name == "Base").unwrap();
        assert_eq!(base.kind, SymbolKind::Class);
    }
}
