// Go language support: parser + resolver

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

use tracing::warn;
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: "go",
        extensions: &["go"],
        parser: &GoParser,
        resolver_factory: |root| Box::new(GoResolver::new(root)),
        config_files: &["go.mod"],
        sibling_fn: go_sibling_expansion,
    }
}

fn go_sibling_expansion(rel_path: &str) -> Option<astmap_core::SiblingExpansion> {
    if !rel_path.ends_with(".go") {
        return None;
    }
    match rel_path.rsplit_once('/') {
        Some((dir, _)) => Some(astmap_core::SiblingExpansion {
            prefix: format!("{}/", dir),
            extension: "go".to_string(),
            root_only: false,
        }),
        None => Some(astmap_core::SiblingExpansion {
            prefix: String::new(),
            extension: "go".to_string(),
            root_only: true,
        }),
    }
}

// ── Parser ──

pub(crate) struct GoParser;

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

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

fn go_language() -> Language {
    tree_sitter_go::LANGUAGE.into()
}

fn parse_go_file(source: &str) -> ParseResult {
    let mut result = parse_with_tree_sitter(
        source,
        &go_language(),
        |root, src, symbols, imports| {
            extract_from_node(root, src, symbols, imports);
        },
        extract_calls,
        |root, src, symbols| {
            collect_type_refs_by_kind(root, src, symbols, "type_identifier", &is_go_builtin)
        },
    );

    // 2-pass: link methods to their receiver types
    link_methods_to_receivers(source, &mut result.symbols);

    // Emit synthetic package import for same-package cross-file resolution.
    // The "::*" suffix triggers wildcard detection in imports.rs:103.
    result.imports.push(ExtractedImport {
        path: ".::*".to_string(),
        imported_symbols: vec![],
    });

    result
}

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

fn extract_from_node(
    node: tree_sitter::Node,
    source: &str,
    symbols: &mut Vec<ExtractedSymbol>,
    imports: &mut Vec<ExtractedImport>,
) {
    match node.kind() {
        "function_declaration" => {
            if let Some(sym) = extract_function(&node, source) {
                symbols.push(sym);
                return;
            }
        }
        "method_declaration" => {
            // Methods are linked to receiver types in pass 2
            if let Some(sym) = extract_method(&node, source) {
                symbols.push(sym);
                return;
            }
        }
        "type_declaration" => {
            extract_type_declaration(&node, source, symbols);
            return;
        }
        "const_declaration" => {
            extract_const_or_var(&node, source, symbols, SymbolKind::Const);
            return;
        }
        "var_declaration" => {
            extract_const_or_var(&node, source, symbols, SymbolKind::Variable);
            return;
        }
        "import_declaration" => {
            imports.extend(extract_imports(&node, source));
            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);
        }
    }
}

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

fn extract_method(node: &tree_sitter::Node, source: &str) -> Option<ExtractedSymbol> {
    let name_node = node.child_by_field_name("name")?;
    let name = node_text(&name_node, source);
    // parent_index will be set in pass 2 (link_methods_to_receivers)
    Some(symbol_from_node(
        name,
        SymbolKind::Method,
        node,
        source,
        None,
    ))
}

/// Extract the receiver type name from a method_declaration node.
/// Handles both value receivers `(r Type)` and pointer receivers `(r *Type)`.
fn receiver_type_name(node: &tree_sitter::Node, source: &str) -> Option<String> {
    let receiver = node.child_by_field_name("receiver")?;
    // receiver is a parameter_list: (r *Type) or (r Type)
    // Walk children to find a type_identifier or pointer_type→type_identifier
    for i in 0..receiver.child_count() {
        if let Some(child) = receiver.child(i as u32) {
            if let Some(name) = find_type_identifier(&child, source) {
                return Some(name);
            }
        }
    }
    None
}

fn find_type_identifier(node: &tree_sitter::Node, source: &str) -> Option<String> {
    if node.kind() == "type_identifier" {
        return Some(node_text(node, source));
    }
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            if let Some(name) = find_type_identifier(&child, source) {
                return Some(name);
            }
        }
    }
    None
}

fn extract_type_declaration(
    node: &tree_sitter::Node,
    source: &str,
    symbols: &mut Vec<ExtractedSymbol>,
) {
    // type_declaration can contain:
    // - direct type_spec / type_alias children (single type decl)
    // - type_spec_list wrapping multiple type_spec / type_alias (grouped)
    collect_type_specs(node, source, symbols);
}

fn collect_type_specs(node: &tree_sitter::Node, source: &str, symbols: &mut Vec<ExtractedSymbol>) {
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            match child.kind() {
                "type_spec" => {
                    if let Some(sym) = extract_type_spec(&child, source) {
                        symbols.push(sym);
                    }
                }
                "type_alias" => {
                    if let Some(sym) = extract_type_alias(&child, source) {
                        symbols.push(sym);
                    }
                }
                _ => {
                    // Recurse into wrappers (e.g., type_spec_list)
                    collect_type_specs(&child, source, symbols);
                }
            }
        }
    }
}

fn extract_type_spec(node: &tree_sitter::Node, source: &str) -> Option<ExtractedSymbol> {
    // type_spec: type_identifier + type (struct_type, interface_type, etc.)
    let name = first_type_identifier(node, source)?;
    let kind = determine_type_kind(node);
    Some(symbol_from_node(name, kind, node, source, None))
}

fn extract_type_alias(node: &tree_sitter::Node, source: &str) -> Option<ExtractedSymbol> {
    // type_alias: type_identifier = type_identifier
    let name = first_type_identifier(node, source)?;
    Some(symbol_from_node(
        name,
        SymbolKind::TypeAlias,
        node,
        source,
        None,
    ))
}

/// Find the first type_identifier child (used as the type name).
fn first_type_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() == "type_identifier" {
                return Some(node_text(&child, source));
            }
        }
    }
    None
}

/// Determine the SymbolKind from the type body in a type_spec.
fn determine_type_kind(node: &tree_sitter::Node) -> SymbolKind {
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            match child.kind() {
                "struct_type" => return SymbolKind::Struct,
                "interface_type" => return SymbolKind::Interface,
                _ => {}
            }
        }
    }
    SymbolKind::TypeAlias
}

fn extract_const_or_var(
    node: &tree_sitter::Node,
    source: &str,
    symbols: &mut Vec<ExtractedSymbol>,
    kind: SymbolKind,
) {
    // const/var declarations can have:
    // - direct const_spec/var_spec children (single decl)
    // - const_spec_list/var_spec_list wrapping multiple specs (grouped)
    collect_specs(node, source, symbols, kind);
}

fn collect_specs(
    node: &tree_sitter::Node,
    source: &str,
    symbols: &mut Vec<ExtractedSymbol>,
    kind: SymbolKind,
) {
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            let child_kind = child.kind();
            if child_kind == "const_spec" || child_kind == "var_spec" {
                // var_spec uses "name" field for single, or first identifier child
                let name = child
                    .child_by_field_name("name")
                    .map(|n| node_text(&n, source))
                    .or_else(|| {
                        // Fallback: find first identifier child
                        (0..child.child_count())
                            .filter_map(|j| child.child(j as u32))
                            .find(|c| c.kind() == "identifier")
                            .map(|c| node_text(&c, source))
                    });
                if let Some(name) = name {
                    symbols.push(symbol_from_node(name, kind, &child, source, None));
                }
            } else {
                // Recurse into list wrappers (const_spec_list, var_spec_list)
                collect_specs(&child, source, symbols, kind);
            }
        }
    }
}

// ---------------------------------------------------------------------------
// 2-pass: link methods to receiver types
// ---------------------------------------------------------------------------

fn link_methods_to_receivers(source: &str, symbols: &mut [ExtractedSymbol]) {
    // Build a map of type name → index for all struct/interface/type_alias symbols
    let type_indices: Vec<(String, usize)> = symbols
        .iter()
        .enumerate()
        .filter(|(_, sym)| {
            matches!(
                sym.kind,
                SymbolKind::Struct | SymbolKind::Interface | SymbolKind::TypeAlias
            )
        })
        .map(|(idx, sym)| (sym.name.clone(), idx))
        .collect();

    // Re-parse to get method_declaration nodes with receiver info.
    // We need the AST again because ExtractedSymbol doesn't store receiver type.
    let mut parser = tree_sitter::Parser::new();
    parser
        .set_language(&go_language())
        .expect("failed to set Go language");
    let tree = match parser.parse(source, None) {
        Some(t) => t,
        None => return,
    };

    let mut method_idx = 0;
    collect_method_receivers(
        tree.root_node(),
        source,
        symbols,
        &type_indices,
        &mut method_idx,
    );
}

fn collect_method_receivers(
    node: tree_sitter::Node,
    source: &str,
    symbols: &mut [ExtractedSymbol],
    type_indices: &[(String, usize)],
    method_idx: &mut usize,
) {
    if node.kind() == "method_declaration" {
        if let Some(recv_type) = receiver_type_name(&node, source) {
            // Find the corresponding method in symbols (by order of appearance)
            while *method_idx < symbols.len() {
                if symbols[*method_idx].kind == SymbolKind::Method {
                    // Match by checking the receiver type against known types
                    if let Some((_, type_idx)) =
                        type_indices.iter().find(|(name, _)| *name == recv_type)
                    {
                        symbols[*method_idx].parent_index = Some(*type_idx);
                    }
                    *method_idx += 1;
                    break;
                }
                *method_idx += 1;
            }
        }
        return;
    }

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

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

fn extract_imports(node: &tree_sitter::Node, source: &str) -> Vec<ExtractedImport> {
    let mut imports = Vec::new();
    collect_import_specs(node, source, &mut imports);
    imports
}

fn collect_import_specs(
    node: &tree_sitter::Node,
    source: &str,
    imports: &mut Vec<ExtractedImport>,
) {
    for i in 0..node.child_count() {
        if let Some(child) = node.child(i as u32) {
            if child.kind() == "import_spec" {
                if let Some(imp) = extract_import_spec(&child, source) {
                    imports.push(imp);
                }
            } else {
                // Recurse into import_spec_list and other wrappers
                collect_import_specs(&child, source, imports);
            }
        }
    }
}

fn extract_import_spec(node: &tree_sitter::Node, source: &str) -> Option<ExtractedImport> {
    // Find the path: child_by_field_name("path") or first interpreted_string_literal
    let path_node = node.child_by_field_name("path").or_else(|| {
        (0..node.child_count())
            .filter_map(|i| node.child(i as u32))
            .find(|c| c.kind() == "interpreted_string_literal")
    })?;
    // Extract the string content (strip outer quotes)
    let raw_path = node_text(&path_node, source);
    let path = raw_path.trim_matches('"').to_string();

    if path.is_empty() {
        return None;
    }

    // Check for alias/name
    let name_node = node.child_by_field_name("name");
    let alias = name_node.map(|n| node_text(&n, source));

    match alias.as_deref() {
        Some("_") => {
            // Blank import: import _ "side/effect" — skip, no symbols
            Some(ExtractedImport {
                path,
                imported_symbols: vec![],
            })
        }
        Some(".") => {
            // Dot import: import . "fmt" — wildcard-like
            Some(ExtractedImport {
                path: format!("{}::*", path),
                imported_symbols: vec![],
            })
        }
        Some(alias_name) => {
            // Aliased import: import f "fmt" — use alias as symbol name
            Some(ExtractedImport {
                path,
                imported_symbols: vec![alias_name.to_string()],
            })
        }
        None => {
            // Normal import: import "fmt" — use last path segment as package name
            let pkg_name = path.rsplit('/').next().unwrap_or(&path).to_string();
            Some(ExtractedImport {
                path,
                imported_symbols: vec![pkg_name],
            })
        }
    }
}

// ---------------------------------------------------------------------------
// 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" {
        if let Some(func_node) = node.child_by_field_name("function") {
            let raw_callee = node_text(&func_node, source);
            // Strip package qualifier: fmt.Println → Println, obj.Method → Method
            let callee_name = raw_callee
                .rsplit('.')
                .next()
                .unwrap_or(&raw_callee)
                .to_string();

            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_go_builtin(name: &str) -> bool {
    matches!(
        name,
        "bool"
            | "int"
            | "int8"
            | "int16"
            | "int32"
            | "int64"
            | "uint"
            | "uint8"
            | "uint16"
            | "uint32"
            | "uint64"
            | "uintptr"
            | "float32"
            | "float64"
            | "complex64"
            | "complex128"
            | "string"
            | "byte"
            | "rune"
            | "error"
            | "any"
            | "comparable"
    )
}

// ── Resolver ──

pub(crate) struct GoResolver {
    module_path: Option<String>,
}

impl GoResolver {
    fn new(project_root: &Path) -> Self {
        let module_path = read_go_module_path(project_root);
        Self { module_path }
    }
}

impl LanguageResolver for GoResolver {
    fn resolve_import(
        &self,
        import_path: &str,
        source_file: &str,
        project_root: &Path,
    ) -> Option<PathBuf> {
        let effective_path = import_path.strip_suffix("::*").unwrap_or(import_path);

        // Synthetic package import: resolve to same directory
        if effective_path == "." {
            let source_abs = project_root.join(source_file);
            let source_dir = source_abs.parent()?;
            return find_first_go_file(source_dir, source_file);
        }

        // Standard library: first path segment has no dot
        let first_segment = effective_path.split('/').next().unwrap_or(effective_path);
        if !first_segment.contains('.') {
            return None;
        }

        // Local package: starts with module path
        if let Some(ref module_path) = self.module_path {
            if let Some(local_path) = effective_path.strip_prefix(module_path.as_str()) {
                let local_path = local_path.strip_prefix('/').unwrap_or(local_path);
                let pkg_dir = project_root.join(local_path);
                return find_first_go_file_in_dir(&pkg_dir);
            }
        }

        // External dependency: skip
        None
    }
}

/// Read the module path from go.mod.
fn read_go_module_path(project_root: &Path) -> Option<String> {
    let go_mod = project_root.join("go.mod");
    let content = std::fs::read_to_string(&go_mod).ok()?;
    for line in content.lines() {
        let trimmed = line.trim();
        if let Some(rest) = trimmed.strip_prefix("module") {
            let module = rest.trim();
            if !module.is_empty() {
                return Some(module.to_string());
            }
        }
    }
    warn!("go.mod found but no module directive");
    None
}

/// Find the first .go file in a directory (excluding the source file itself).
fn find_first_go_file(dir: &Path, exclude_source: &str) -> Option<PathBuf> {
    let entries = std::fs::read_dir(dir).ok()?;
    for entry in entries {
        let entry = entry.ok()?;
        let path = entry.path();
        if path.extension().is_some_and(|e| e == "go") {
            // Don't return self
            let file_name = path.file_name()?.to_str()?;
            let source_name = Path::new(exclude_source)
                .file_name()
                .and_then(|n| n.to_str())
                .unwrap_or("");
            if file_name != source_name {
                return Some(path);
            }
        }
    }
    None
}

/// Find the first .go file in a directory.
fn find_first_go_file_in_dir(dir: &Path) -> Option<PathBuf> {
    let entries = std::fs::read_dir(dir).ok()?;
    for entry in entries {
        let entry = entry.ok()?;
        let path = entry.path();
        if path.extension().is_some_and(|e| e == "go") {
            return Some(path);
        }
    }
    None
}

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

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

    // --- Symbol extraction ---

    #[test]
    fn test_parse_function() {
        let result = parse("package main\nfunc 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");
    }

    #[test]
    fn test_parse_method() {
        let source = r#"package main
type User struct { Name string }
func (u *User) Greet() string { return u.Name }
"#;
        let result = parse(source);
        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");
        // Should be linked to User struct
        assert!(
            methods[0].parent_index.is_some(),
            "method should be linked to receiver type"
        );
    }

    #[test]
    fn test_parse_method_pointer_vs_value_receiver() {
        let source = r#"package main
type User struct {}
func (u *User) PtrMethod() {}
func (u User) ValMethod() {}
"#;
        let result = parse(source);
        let methods: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Method)
            .collect();
        assert_eq!(methods.len(), 2);
        assert!(
            methods[0].parent_index.is_some(),
            "pointer receiver method should be linked"
        );
        assert!(
            methods[1].parent_index.is_some(),
            "value receiver method should be linked"
        );
    }

    #[test]
    fn test_parse_struct() {
        let source = r#"package main
type User struct {
    Name string
    Age  int
}
"#;
        let result = parse(source);
        assert!(!result.has_errors);
        let structs: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Struct)
            .collect();
        assert_eq!(structs.len(), 1);
        assert_eq!(structs[0].name, "User");
    }

    #[test]
    fn test_parse_interface() {
        let source = r#"package main
type Reader interface {
    Read(p []byte) (n int, err error)
}
"#;
        let result = parse(source);
        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, "Reader");
    }

    #[test]
    fn test_parse_type_alias() {
        let source = r#"package main
type MyInt = int
type Duration int64
"#;
        let result = parse(source);
        let aliases: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::TypeAlias)
            .collect();
        assert_eq!(aliases.len(), 2);
        assert_eq!(aliases[0].name, "MyInt");
        assert_eq!(aliases[1].name, "Duration");
    }

    #[test]
    fn test_parse_const() {
        let source = r#"package main
const MaxSize = 100
const (
    A = 1
    B = 2
)
"#;
        let result = parse(source);
        let consts: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Const)
            .collect();
        assert_eq!(consts.len(), 3);
        assert_eq!(consts[0].name, "MaxSize");
        assert_eq!(consts[1].name, "A");
        assert_eq!(consts[2].name, "B");
    }

    #[test]
    fn test_parse_var() {
        let source = r#"package main
var GlobalFlag = true
var (
    X = 1
    Y = "hello"
)
"#;
        let result = parse(source);
        let vars: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Variable)
            .collect();
        assert_eq!(vars.len(), 3);
        assert_eq!(vars[0].name, "GlobalFlag");
        assert_eq!(vars[1].name, "X");
        assert_eq!(vars[2].name, "Y");
    }

    #[test]
    fn test_parse_exported_vs_unexported() {
        let source = r#"package main
func ExportedFunc() {}
func unexportedFunc() {}
type ExportedType struct {}
type unexportedType struct {}
"#;
        let result = parse(source);
        let names: Vec<&str> = result.symbols.iter().map(|s| s.name.as_str()).collect();
        assert!(names.contains(&"ExportedFunc"));
        assert!(names.contains(&"unexportedFunc"));
        assert!(names.contains(&"ExportedType"));
        assert!(names.contains(&"unexportedType"));
    }

    #[test]
    fn test_parse_nested_struct_method() {
        let source = r#"package main
type Server struct { Port int }
func (s *Server) Start() {}
func (s *Server) Stop() {}
"#;
        let result = parse(source);
        let server_idx = result
            .symbols
            .iter()
            .position(|s| s.name == "Server")
            .unwrap();
        let methods: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind == SymbolKind::Method)
            .collect();
        assert_eq!(methods.len(), 2);
        assert_eq!(methods[0].parent_index, Some(server_idx));
        assert_eq!(methods[1].parent_index, Some(server_idx));
    }

    // --- Import extraction ---

    #[test]
    fn test_import_simple() {
        let result = parse(
            r#"package main
import "fmt"
"#,
        );
        // Filter out the synthetic ".::*" import
        let real_imports: Vec<_> = result.imports.iter().filter(|i| i.path != ".::*").collect();
        assert_eq!(real_imports.len(), 1);
        assert_eq!(real_imports[0].path, "fmt");
        assert_eq!(real_imports[0].imported_symbols, vec!["fmt"]);
    }

    #[test]
    fn test_import_grouped() {
        let result = parse(
            r#"package main
import (
    "fmt"
    "net/http"
)
"#,
        );
        let real_imports: Vec<_> = result.imports.iter().filter(|i| i.path != ".::*").collect();
        assert_eq!(real_imports.len(), 2);
        assert_eq!(real_imports[0].path, "fmt");
        assert_eq!(real_imports[1].path, "net/http");
        assert_eq!(real_imports[1].imported_symbols, vec!["http"]);
    }

    #[test]
    fn test_import_aliased() {
        let result = parse(
            r#"package main
import f "fmt"
"#,
        );
        let real_imports: Vec<_> = result.imports.iter().filter(|i| i.path != ".::*").collect();
        assert_eq!(real_imports.len(), 1);
        assert_eq!(real_imports[0].path, "fmt");
        assert_eq!(real_imports[0].imported_symbols, vec!["f"]);
    }

    #[test]
    fn test_import_dot() {
        let result = parse(
            r#"package main
import . "fmt"
"#,
        );
        // Dot import becomes wildcard: "fmt::*"
        let dot_imports: Vec<_> = result
            .imports
            .iter()
            .filter(|i| i.path == "fmt::*")
            .collect();
        assert_eq!(dot_imports.len(), 1);
        assert!(dot_imports[0].imported_symbols.is_empty());
        // No non-wildcard, non-synthetic imports
        let plain_imports: Vec<_> = result
            .imports
            .iter()
            .filter(|i| !i.path.ends_with("::*"))
            .collect();
        assert_eq!(plain_imports.len(), 0);
    }

    #[test]
    fn test_import_blank() {
        let result = parse(
            r#"package main
import _ "database/sql"
"#,
        );
        let real_imports: Vec<_> = result.imports.iter().filter(|i| i.path != ".::*").collect();
        assert_eq!(real_imports.len(), 1);
        assert_eq!(real_imports[0].path, "database/sql");
        assert!(real_imports[0].imported_symbols.is_empty());
    }

    #[test]
    fn test_synthetic_package_import() {
        let result = parse("package main\nfunc main() {}");
        let synthetic: Vec<_> = result.imports.iter().filter(|i| i.path == ".::*").collect();
        assert_eq!(
            synthetic.len(),
            1,
            "synthetic .::* import should be present"
        );
        assert!(synthetic[0].imported_symbols.is_empty());
    }

    // --- Call extraction ---

    #[test]
    fn test_extract_calls_simple() {
        let source = r#"package main
func helper() {}
func main() {
    helper()
}
"#;
        let result = parse(source);
        let callee_names: Vec<&str> = result
            .calls
            .iter()
            .map(|c| c.callee_name.as_str())
            .collect();
        assert!(callee_names.contains(&"helper"));
    }

    #[test]
    fn test_extract_calls_method() {
        let source = r#"package main
type S struct {}
func (s *S) Do() {}
func main() {
    s := S{}
    s.Do()
}
"#;
        let result = parse(source);
        let callee_names: Vec<&str> = result
            .calls
            .iter()
            .map(|c| c.callee_name.as_str())
            .collect();
        assert!(callee_names.contains(&"Do"));
    }

    #[test]
    fn test_extract_calls_package_qualified() {
        let source = r#"package main
import "fmt"
func main() {
    fmt.Println("hello")
}
"#;
        let result = parse(source);
        let callee_names: Vec<&str> = result
            .calls
            .iter()
            .map(|c| c.callee_name.as_str())
            .collect();
        assert!(
            callee_names.contains(&"Println"),
            "should strip package qualifier"
        );
    }

    // --- Type reference extraction ---

    #[test]
    fn test_extract_type_refs_param() {
        let source = r#"package main
type Config struct {}
func process(c Config) {}
"#;
        let result = parse(source);
        let type_names: Vec<&str> = result
            .type_refs
            .iter()
            .map(|r| r.to_type.as_str())
            .collect();
        assert!(type_names.contains(&"Config"));
    }

    #[test]
    fn test_extract_type_refs_return() {
        let source = r#"package main
type Result struct {}
func create() Result { return Result{} }
"#;
        let result = parse(source);
        let type_names: Vec<&str> = result
            .type_refs
            .iter()
            .map(|r| r.to_type.as_str())
            .collect();
        assert!(type_names.contains(&"Result"));
    }

    #[test]
    fn test_extract_type_refs_field() {
        let source = r#"package main
type Address struct { City string }
type User struct {
    Addr Address
}
"#;
        let result = parse(source);
        let type_names: Vec<&str> = result
            .type_refs
            .iter()
            .map(|r| r.to_type.as_str())
            .collect();
        assert!(type_names.contains(&"Address"));
    }

    #[test]
    fn test_extract_type_refs_builtin_filtered() {
        let source = r#"package main
func foo(s string, n int, b bool) float64 { return 0.0 }
"#;
        let result = parse(source);
        let type_names: Vec<&str> = result
            .type_refs
            .iter()
            .map(|r| r.to_type.as_str())
            .collect();
        assert!(
            !type_names.contains(&"string"),
            "builtins should be filtered"
        );
        assert!(!type_names.contains(&"int"));
        assert!(!type_names.contains(&"bool"));
        assert!(!type_names.contains(&"float64"));
    }

    // --- Resolver ---

    #[test]
    fn test_resolve_stdlib_skip() {
        let tmp = tempfile::tempdir().unwrap();
        std::fs::write(
            tmp.path().join("go.mod"),
            "module example.com/myproject\n\ngo 1.21\n",
        )
        .unwrap();
        let resolver = GoResolver::new(tmp.path());
        assert_eq!(resolver.resolve_import("fmt", "main.go", tmp.path()), None);
        assert_eq!(
            resolver.resolve_import("net/http", "main.go", tmp.path()),
            None
        );
    }

    #[test]
    fn test_resolve_external_skip() {
        let tmp = tempfile::tempdir().unwrap();
        std::fs::write(
            tmp.path().join("go.mod"),
            "module example.com/myproject\n\ngo 1.21\n",
        )
        .unwrap();
        let resolver = GoResolver::new(tmp.path());
        assert_eq!(
            resolver.resolve_import("github.com/other/pkg", "main.go", tmp.path()),
            None
        );
    }

    #[test]
    fn test_resolve_local_package() {
        let tmp = tempfile::tempdir().unwrap();
        std::fs::write(
            tmp.path().join("go.mod"),
            "module example.com/myproject\n\ngo 1.21\n",
        )
        .unwrap();
        let pkg_dir = tmp.path().join("pkg");
        std::fs::create_dir_all(&pkg_dir).unwrap();
        std::fs::write(pkg_dir.join("service.go"), "package pkg\n").unwrap();

        let resolver = GoResolver::new(tmp.path());
        let resolved = resolver.resolve_import("example.com/myproject/pkg", "main.go", tmp.path());
        assert!(resolved.is_some(), "local package should resolve");
        assert!(resolved.unwrap().ends_with("service.go"));
    }

    #[test]
    fn test_resolve_no_gomod() {
        let tmp = tempfile::tempdir().unwrap();
        let resolver = GoResolver::new(tmp.path());
        // Without go.mod, no module path — can't resolve anything with dots
        assert_eq!(
            resolver.resolve_import("example.com/myproject/pkg", "main.go", tmp.path()),
            None
        );
        // Stdlib should still be skipped
        assert_eq!(resolver.resolve_import("fmt", "main.go", tmp.path()), None);
    }

    #[test]
    fn test_resolve_dot_import() {
        let tmp = tempfile::tempdir().unwrap();
        let pkg_dir = tmp.path().join("pkg");
        std::fs::create_dir_all(&pkg_dir).unwrap();
        std::fs::write(pkg_dir.join("types.go"), "package pkg\n").unwrap();
        std::fs::write(pkg_dir.join("service.go"), "package pkg\n").unwrap();

        let resolver = GoResolver::new(tmp.path());
        let resolved = resolver.resolve_import(".::*", "pkg/service.go", tmp.path());
        assert!(
            resolved.is_some(),
            "synthetic .::* should resolve to a sibling"
        );
        let resolved_path = resolved.unwrap();
        assert!(resolved_path.ends_with("types.go"));
    }

    #[test]
    fn test_resolve_gomod_with_comments() {
        let tmp = tempfile::tempdir().unwrap();
        std::fs::write(
            tmp.path().join("go.mod"),
            "// This is a comment\n\nmodule example.com/myproject\n\ngo 1.21\n",
        )
        .unwrap();
        let resolver = GoResolver::new(tmp.path());
        assert!(
            resolver.module_path.is_some(),
            "should parse module path despite comments"
        );
        assert_eq!(
            resolver.module_path.as_deref(),
            Some("example.com/myproject")
        );
    }

    // --- Edge cases ---

    #[test]
    fn test_empty_file() {
        let result = parse("");
        assert!(
            result.symbols.is_empty()
                || result
                    .symbols
                    .iter()
                    .all(|s| s.kind != SymbolKind::Function)
        );
    }

    #[test]
    fn test_package_only() {
        let result = parse("package main\n");
        assert!(!result.has_errors);
        // Only synthetic import, no real symbols
        let real_symbols: Vec<_> = result
            .symbols
            .iter()
            .filter(|s| s.kind != SymbolKind::Module)
            .collect();
        assert!(real_symbols.is_empty());
    }

    #[test]
    fn test_malformed_file() {
        let result = parse("package main\nfunc {{{ invalid syntax");
        assert!(result.has_errors);
    }
}
