//! Go package extraction via tree-sitter.
//!
//! This module uses tree-sitter-go to parse Go source files and extract
//! type signatures for functions, types, and constants.

use std::collections::HashMap;
use std::fs;
use std::path::Path;

use miette::{Diagnostic, Result};
use thiserror::Error;
use tree_sitter::{Parser, Tree};

#[derive(Error, Diagnostic, Debug)]
pub enum ExtractError {
    #[error("Failed to read file: {0}")]
    ReadFile(String),

    #[error("Failed to parse Go source: {0}")]
    Parse(String),

    #[error("Failed to initialise parser")]
    ParserInit,
}

/// Extracted information from a Go package
#[derive(Debug, Default, Clone)]
pub struct GoPackage {
    /// Package name
    pub name: String,
    /// Whether this package was generated by Soppo (has //soppo:generated marker)
    pub soppo_generated: bool,
    /// Exported functions
    pub functions: HashMap<String, FuncDef>,
    /// Exported types
    pub types: HashMap<String, TypeDef>,
    /// Exported constants
    pub constants: HashMap<String, ConstDef>,
    /// Exported variables
    pub variables: HashMap<String, VarDef>,
    /// Methods by receiver type (e.g., "Reader" -> [methods])
    pub methods: HashMap<String, Vec<MethodDef>>,
    /// Soppo type definitions recovered from marker comments
    pub soppo_types: HashMap<String, SoppoTypeDef>,
}

/// Function definition
#[derive(Debug, Clone)]
pub struct FuncDef {
    pub name: String,
    pub generics: Vec<String>,
    pub params: Vec<Param>,
    pub return_type: String,
}

/// Function parameter
#[derive(Debug, Clone)]
pub struct Param {
    pub name: String,
    pub ty: String,
}

/// Type definition
#[derive(Debug, Clone)]
pub struct TypeDef {
    pub name: String,
    pub generics: Vec<String>,
    pub kind: String, // "struct", "interface", "alias"
    pub fields: Vec<Field>,
    /// For type aliases, the underlying type (e.g., "int64" for "type Duration int64")
    pub underlying: Option<String>,
}

/// Struct field
#[derive(Debug, Clone)]
pub struct Field {
    pub name: String,
    pub ty: String,
    /// Whether this field is nullable (from //soppo:nilable marker)
    pub nullable: bool,
}

/// Constant definition
#[derive(Debug, Clone)]
pub struct ConstDef {
    pub name: String,
    pub ty: String,
}

/// Variable definition
#[derive(Debug, Clone)]
pub struct VarDef {
    pub name: String,
    pub ty: String,
}

/// Method definition (function with receiver)
#[derive(Debug, Clone)]
pub struct MethodDef {
    pub name: String,
    pub receiver_type: String, // e.g., "Reader", "*Reader"
    pub params: Vec<Param>,
    pub return_type: String,
}

/// Soppo type definition recovered from marker comments
#[derive(Debug, Clone)]
pub struct SoppoTypeDef {
    pub kind: String, // "enum"
    pub name: String,
    pub generics: Vec<String>,
    pub variants: Vec<Variant>,
}

/// Enum variant
#[derive(Debug, Clone)]
pub struct Variant {
    pub name: String,
    pub fields: Vec<Field>,
}

/// Extract Go package information from a file or directory
pub fn extract(path: &Path) -> Result<GoPackage> {
    let mut pkg = GoPackage::default();

    if path.is_dir() {
        extract_dir(path, &mut pkg)?;
    } else {
        extract_file(path, &mut pkg)?;
    }

    Ok(pkg)
}

fn extract_dir(dir: &Path, pkg: &mut GoPackage) -> Result<()> {
    let entries = fs::read_dir(dir).map_err(|e| ExtractError::ReadFile(e.to_string()))?;

    for entry in entries {
        let entry = entry.map_err(|e| ExtractError::ReadFile(e.to_string()))?;
        let path = entry.path();

        if path.is_file()
            && let Some(name) = path.file_name().and_then(|n| n.to_str())
            && name.ends_with(".go")
            && !name.ends_with("_test.go")
        {
            extract_file(&path, pkg)?;
        }
    }

    Ok(())
}

fn extract_file(path: &Path, pkg: &mut GoPackage) -> Result<()> {
    let source = fs::read_to_string(path).map_err(|e| ExtractError::ReadFile(e.to_string()))?;

    let mut parser = Parser::new();
    parser
        .set_language(&tree_sitter_go::LANGUAGE.into())
        .map_err(|_| ExtractError::ParserInit)?;

    let tree = parser
        .parse(&source, None)
        .ok_or(ExtractError::Parse("Failed to parse".to_string()))?;

    // Extract soppo markers from comments first
    extract_soppo_markers(&source, pkg);

    // Extract declarations
    extract_declarations(&tree, &source, pkg);

    Ok(())
}

fn extract_soppo_markers(source: &str, pkg: &mut GoPackage) {
    // Check for //soppo:generated marker (indicates this file was generated by Soppo)
    if source.contains("//soppo:generated") {
        pkg.soppo_generated = true;
    }

    // Look for /*soppo:enum ... */ block comments
    let mut i = 0;
    let bytes = source.as_bytes();

    while i < bytes.len().saturating_sub(2) {
        if bytes[i] == b'/' && bytes[i + 1] == b'*' {
            // Found start of block comment
            if let Some(end) = source[i..].find("*/") {
                let comment = &source[i + 2..i + end];
                if let Some(rest) = comment.strip_prefix("soppo:enum")
                    && let Some(soppo_type) = parse_soppo_enum(rest.trim())
                {
                    pkg.soppo_types.insert(soppo_type.name.clone(), soppo_type);
                }
                i += end + 2;
            } else {
                break;
            }
        } else {
            i += 1;
        }
    }
}

fn parse_soppo_enum(text: &str) -> Option<SoppoTypeDef> {
    // Format: Name[T, E] {\n    Variant1 Type\n    Variant2\n    Variant3 {\n        field Type\n    }\n}
    let lines: Vec<&str> = text.lines().collect();
    if lines.is_empty() {
        return None;
    }

    let header = lines[0].trim().trim_end_matches('{').trim();

    let mut st = SoppoTypeDef {
        kind: "enum".to_string(),
        name: String::new(),
        generics: Vec::new(),
        variants: Vec::new(),
    };

    // Parse name and generics
    if let Some(bracket_idx) = header.find('[') {
        st.name = header[..bracket_idx].to_string();
        let generics_str = header[bracket_idx + 1..].trim_end_matches(']');
        for g in generics_str.split(',') {
            let parts: Vec<&str> = g.split_whitespace().collect();
            if !parts.is_empty() {
                st.generics.push(parts[0].to_string());
            }
        }
    } else {
        st.name = header.to_string();
    }

    // Parse variants (handling multi-line struct variants)
    let mut i = 1;
    while i < lines.len() {
        let line = lines[i].trim();
        i += 1;

        if line.is_empty() || line == "}" {
            continue;
        }

        // Check if this is a struct variant (ends with {)
        if line.ends_with('{') {
            let variant_name = line.trim_end_matches('{').trim();
            let mut v = Variant {
                name: variant_name.to_string(),
                fields: Vec::new(),
            };

            // Parse fields until we hit closing }
            while i < lines.len() {
                let field_line = lines[i].trim();
                i += 1;

                if field_line == "}" {
                    break;
                }
                if field_line.is_empty() {
                    continue;
                }

                let parts: Vec<&str> = field_line.split_whitespace().collect();
                if parts.len() >= 2 {
                    v.fields.push(Field {
                        name: parts[0].to_string(),
                        ty: parts[1].to_string(),
                        nullable: false,
                    });
                }
            }

            st.variants.push(v);
        } else {
            // Unit or single-value variant
            let parts: Vec<&str> = line.split_whitespace().collect();
            if parts.is_empty() {
                continue;
            }
            let mut v = Variant {
                name: parts[0].to_string(),
                fields: Vec::new(),
            };
            if parts.len() > 1 {
                v.fields.push(Field {
                    name: "Value".to_string(),
                    ty: parts[1].to_string(),
                    nullable: false,
                });
            }
            st.variants.push(v);
        }
    }

    Some(st)
}

fn extract_declarations(tree: &Tree, source: &str, pkg: &mut GoPackage) {
    let root = tree.root_node();

    // First pass: extract package name, functions, types, and constants
    // (needed for variable type inference)
    let mut cursor = root.walk();
    for child in root.children(&mut cursor) {
        match child.kind() {
            "package_clause" => {
                // Extract package name - tree-sitter-go uses "package_identifier" child
                let mut cursor2 = child.walk();
                for pkg_child in child.children(&mut cursor2) {
                    if pkg_child.kind() == "package_identifier" && pkg.name.is_empty() {
                        pkg.name = node_text(pkg_child, source).to_string();
                    }
                }
            }
            "function_declaration" => {
                if let Some(func) = extract_function(child, source)
                    && is_exported(&func.name)
                {
                    pkg.functions.insert(func.name.clone(), func);
                }
            }
            "method_declaration" => {
                if let Some(method) = extract_method(child, source)
                    && is_exported(&method.name)
                {
                    // Store methods by receiver type (base type without pointer)
                    let base_type = method
                        .receiver_type
                        .strip_prefix('*')
                        .unwrap_or(&method.receiver_type)
                        .to_string();
                    pkg.methods.entry(base_type).or_default().push(method);
                }
            }
            "type_declaration" => {
                extract_type_specs(child, source, pkg);
            }
            "const_declaration" => {
                extract_const_specs(child, source, pkg);
            }
            _ => {}
        }
    }

    // Second pass: extract variables (may need function return types for inference)
    let mut cursor = root.walk();
    for child in root.children(&mut cursor) {
        if child.kind() == "var_declaration" {
            extract_var_specs(child, source, pkg);
        }
    }
}

fn extract_function(node: tree_sitter::Node, source: &str) -> Option<FuncDef> {
    let name_node = node.child_by_field_name("name")?;
    let name = node_text(name_node, source).to_string();

    let mut func = FuncDef {
        name,
        generics: Vec::new(),
        params: Vec::new(),
        return_type: String::new(),
    };

    // Extract type parameters (generics)
    if let Some(type_params) = node.child_by_field_name("type_parameters") {
        extract_type_params(type_params, source, &mut func.generics);
    }

    // Extract parameters
    if let Some(params) = node.child_by_field_name("parameters") {
        extract_params(params, source, &mut func.params);
    }

    // Extract return type
    if let Some(result) = node.child_by_field_name("result") {
        func.return_type = extract_type_string(result, source);
    }

    Some(func)
}

fn extract_method(node: tree_sitter::Node, source: &str) -> Option<MethodDef> {
    let name_node = node.child_by_field_name("name")?;
    let name = node_text(name_node, source).to_string();

    // Extract receiver type
    let receiver = node.child_by_field_name("receiver")?;
    let receiver_type = extract_receiver_type(receiver, source)?;

    let mut method = MethodDef {
        name,
        receiver_type,
        params: Vec::new(),
        return_type: String::new(),
    };

    // Extract parameters
    if let Some(params) = node.child_by_field_name("parameters") {
        extract_params(params, source, &mut method.params);
    }

    // Extract return type
    if let Some(result) = node.child_by_field_name("result") {
        method.return_type = extract_type_string(result, source);
    }

    Some(method)
}

fn extract_receiver_type(node: tree_sitter::Node, source: &str) -> Option<String> {
    // Receiver is a parameter_list with one parameter
    // e.g., (r *Reader) or (r Reader)
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        if child.kind() == "parameter_declaration" {
            // Get the type from the parameter (skip the identifier which is the receiver name)
            let mut inner_cursor = child.walk();
            for inner_child in child.children(&mut inner_cursor) {
                // Match actual type nodes, not identifiers (which could be receiver name)
                let kind = inner_child.kind();
                if kind != "identifier"
                    && (kind == "type_identifier"
                        || kind == "pointer_type"
                        || kind == "slice_type"
                        || kind == "array_type"
                        || kind == "map_type"
                        || kind == "qualified_type"
                        || kind == "generic_type")
                {
                    return Some(extract_type_string(inner_child, source));
                }
            }
        }
    }
    None
}

fn extract_type_params(node: tree_sitter::Node, source: &str, generics: &mut Vec<String>) {
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        if child.kind() == "type_parameter_declaration" {
            // Get the name(s) from this declaration
            let mut inner_cursor = child.walk();
            for inner_child in child.children(&mut inner_cursor) {
                if inner_child.kind() == "identifier" {
                    generics.push(node_text(inner_child, source).to_string());
                }
            }
        }
    }
}

fn extract_params(node: tree_sitter::Node, source: &str, params: &mut Vec<Param>) {
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        match child.kind() {
            "parameter_declaration" => {
                let mut names = Vec::new();
                let mut ty = String::new();

                let mut inner_cursor = child.walk();
                for inner_child in child.children(&mut inner_cursor) {
                    match inner_child.kind() {
                        "identifier" => {
                            names.push(node_text(inner_child, source).to_string());
                        }
                        _ if is_type_node(inner_child.kind()) => {
                            ty = extract_type_string(inner_child, source);
                        }
                        _ => {}
                    }
                }

                // If we have names, add params with those names
                // If no names, add a single unnamed param
                if names.is_empty() {
                    params.push(Param {
                        name: String::new(),
                        ty,
                    });
                } else {
                    for name in names {
                        params.push(Param {
                            name,
                            ty: ty.clone(),
                        });
                    }
                }
            }
            "variadic_parameter_declaration" => {
                let mut name = String::new();
                let mut ty = String::new();

                let mut inner_cursor = child.walk();
                for inner_child in child.children(&mut inner_cursor) {
                    match inner_child.kind() {
                        "identifier" => {
                            name = node_text(inner_child, source).to_string();
                        }
                        _ if is_type_node(inner_child.kind()) => {
                            // The type is already handled - get the raw text for variadic
                            ty = format!("...{}", extract_type_string(inner_child, source));
                        }
                        _ => {}
                    }
                }

                params.push(Param { name, ty });
            }
            _ => {}
        }
    }
}

fn extract_type_specs(node: tree_sitter::Node, source: &str, pkg: &mut GoPackage) {
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        if child.kind() == "type_spec"
            && let Some(type_def) = extract_type_spec(child, source)
            && is_exported(&type_def.name)
        {
            pkg.types.insert(type_def.name.clone(), type_def);
        }
    }
}

fn extract_type_spec(node: tree_sitter::Node, source: &str) -> Option<TypeDef> {
    let name_node = node.child_by_field_name("name")?;
    let name = node_text(name_node, source).to_string();

    let mut type_def = TypeDef {
        name,
        generics: Vec::new(),
        kind: "alias".to_string(),
        fields: Vec::new(),
        underlying: None,
    };

    // Extract type parameters
    if let Some(type_params) = node.child_by_field_name("type_parameters") {
        extract_type_params(type_params, source, &mut type_def.generics);
    }

    // Extract the actual type
    if let Some(type_node) = node.child_by_field_name("type") {
        match type_node.kind() {
            "struct_type" => {
                type_def.kind = "struct".to_string();
                extract_struct_fields(type_node, source, &mut type_def.fields);
            }
            "interface_type" => {
                type_def.kind = "interface".to_string();
            }
            _ => {
                type_def.kind = "alias".to_string();
                // For type aliases, store the underlying type
                type_def.underlying = Some(extract_type_string(type_node, source));
            }
        }
    }

    Some(type_def)
}

fn extract_struct_fields(node: tree_sitter::Node, source: &str, fields: &mut Vec<Field>) {
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        if child.kind() == "field_declaration_list" {
            let mut inner_cursor = child.walk();
            for field_decl in child.children(&mut inner_cursor) {
                if field_decl.kind() == "field_declaration" {
                    let mut names = Vec::new();
                    let mut ty = String::new();
                    let mut nullable = false;

                    let mut field_cursor = field_decl.walk();
                    for field_child in field_decl.children(&mut field_cursor) {
                        match field_child.kind() {
                            "field_identifier" => {
                                names.push(node_text(field_child, source).to_string());
                            }
                            "comment" => {
                                // Check for //soppo:nilable marker
                                let comment_text = node_text(field_child, source);
                                if comment_text.contains("soppo:nilable") {
                                    nullable = true;
                                }
                            }
                            _ if is_type_node(field_child.kind()) => {
                                ty = extract_type_string(field_child, source);
                            }
                            _ => {}
                        }
                    }

                    // Handle embedded fields (no names)
                    if names.is_empty() && !ty.is_empty() {
                        fields.push(Field {
                            name: ty.clone(),
                            ty,
                            nullable,
                        });
                    } else {
                        for name in names {
                            fields.push(Field {
                                name,
                                ty: ty.clone(),
                                nullable,
                            });
                        }
                    }
                }
            }
        }
    }
}

fn extract_const_specs(node: tree_sitter::Node, source: &str, pkg: &mut GoPackage) {
    // In Go, const blocks can have implicit typing:
    // const (
    //     Nanosecond Duration = 1       // explicit type
    //     Microsecond = 1000 * Nanosecond  // inherits Duration type
    // )
    // Track the inherited type across const_specs in the block
    let mut inherited_type = String::new();

    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        if child.kind() == "const_spec" {
            inherited_type = extract_const_spec_with_type(child, source, pkg, &inherited_type);
        }
    }
}

/// Extract a single const spec and return its type (for inheritance)
fn extract_const_spec_with_type(
    node: tree_sitter::Node,
    source: &str,
    pkg: &mut GoPackage,
    inherited_type: &str,
) -> String {
    let mut names = Vec::new();
    let mut explicit_ty = String::new();

    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        match child.kind() {
            "identifier" => {
                names.push(node_text(child, source).to_string());
            }
            _ if is_type_node(child.kind()) => {
                explicit_ty = extract_type_string(child, source);
            }
            "expression_list" => {
                // Try to infer type from value if not explicitly typed and no inherited type
                if explicit_ty.is_empty() && inherited_type.is_empty() {
                    explicit_ty = infer_const_type(child);
                }
            }
            _ => {}
        }
    }

    // Determine the type to use: explicit > inherited > inferred/untyped
    let ty = if !explicit_ty.is_empty() {
        explicit_ty.clone()
    } else if !inherited_type.is_empty() {
        inherited_type.to_string()
    } else {
        "untyped".to_string()
    };

    for name in names {
        if is_exported(&name) {
            pkg.constants.insert(
                name.clone(),
                ConstDef {
                    name,
                    ty: ty.clone(),
                },
            );
        }
    }

    // Return the type for inheritance: if this spec had an explicit type, use it
    // Otherwise, keep using the inherited type
    if !explicit_ty.is_empty() {
        explicit_ty
    } else {
        inherited_type.to_string()
    }
}

fn infer_const_type(node: tree_sitter::Node) -> String {
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        match child.kind() {
            "int_literal" => return "int".to_string(),
            "float_literal" => return "float64".to_string(),
            "interpreted_string_literal" | "raw_string_literal" => return "string".to_string(),
            "true" | "false" => return "bool".to_string(),
            _ => {}
        }
    }
    "untyped".to_string()
}

fn extract_var_specs(node: tree_sitter::Node, source: &str, pkg: &mut GoPackage) {
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        match child.kind() {
            "var_spec" => {
                extract_var_spec(child, source, pkg);
            }
            "var_spec_list" => {
                // For var blocks: var ( ... )
                let mut inner_cursor = child.walk();
                for inner_child in child.children(&mut inner_cursor) {
                    if inner_child.kind() == "var_spec" {
                        extract_var_spec(inner_child, source, pkg);
                    }
                }
            }
            _ => {}
        }
    }
}

fn extract_var_spec(node: tree_sitter::Node, source: &str, pkg: &mut GoPackage) {
    let mut names = Vec::new();
    let mut ty = String::new();

    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        match child.kind() {
            "identifier" => {
                names.push(node_text(child, source).to_string());
            }
            "expression_list" => {
                // Try to infer type from expression (e.g., function call)
                if ty.is_empty() {
                    ty = infer_var_type_from_expr(child, source, pkg);
                }
            }
            _ if is_type_node(child.kind()) => {
                ty = extract_type_string(child, source);
            }
            _ => {}
        }
    }

    // Only add exported variables with known types
    if !ty.is_empty() {
        for name in names {
            if is_exported(&name) {
                pkg.variables.insert(
                    name.clone(),
                    VarDef {
                        name,
                        ty: ty.clone(),
                    },
                );
            }
        }
    }
}

/// Try to infer variable type from initialization expression
fn infer_var_type_from_expr(node: tree_sitter::Node, source: &str, pkg: &GoPackage) -> String {
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        if child.kind() == "call_expression" {
            // Get the function being called
            if let Some(func_node) = child.child_by_field_name("function") {
                let func_name = node_text(func_node, source);
                // Look up the function's return type in the package
                if let Some(func_def) = pkg.functions.get(func_name)
                    && !func_def.return_type.is_empty()
                {
                    return func_def.return_type.clone();
                }
            }
        }
    }
    String::new()
}

fn extract_type_string(node: tree_sitter::Node, source: &str) -> String {
    match node.kind() {
        "type_identifier" | "identifier" => node_text(node, source).to_string(),
        "pointer_type" => {
            if let Some(elem) = node.child_by_field_name("element") {
                format!("*{}", extract_type_string(elem, source))
            } else {
                node_text(node, source).to_string()
            }
        }
        "slice_type" => {
            if let Some(elem) = node.child_by_field_name("element") {
                format!("[]{}", extract_type_string(elem, source))
            } else {
                node_text(node, source).to_string()
            }
        }
        "array_type" => {
            let length = node
                .child_by_field_name("length")
                .map(|n| node_text(n, source))
                .unwrap_or("...");
            let elem = node
                .child_by_field_name("element")
                .map(|n| extract_type_string(n, source))
                .unwrap_or_default();
            format!("[{}]{}", length, elem)
        }
        "map_type" => {
            let key = node
                .child_by_field_name("key")
                .map(|n| extract_type_string(n, source))
                .unwrap_or_default();
            let value = node
                .child_by_field_name("value")
                .map(|n| extract_type_string(n, source))
                .unwrap_or_default();
            format!("map[{}]{}", key, value)
        }
        "channel_type" => {
            let value = node
                .child_by_field_name("value")
                .map(|n| extract_type_string(n, source))
                .unwrap_or_default();
            format!("chan {}", value)
        }
        "qualified_type" => {
            let package = node
                .child_by_field_name("package")
                .map(|n| node_text(n, source))
                .unwrap_or_default();
            let name = node
                .child_by_field_name("name")
                .map(|n| node_text(n, source))
                .unwrap_or_default();
            format!("{}.{}", package, name)
        }
        "generic_type" => {
            let type_name = node
                .child_by_field_name("type")
                .map(|n| extract_type_string(n, source))
                .unwrap_or_default();
            let args = node
                .child_by_field_name("type_arguments")
                .map(|n| extract_type_args(n, source))
                .unwrap_or_default();
            format!("{}[{}]", type_name, args.join(", "))
        }
        "function_type" => extract_function_type(node, source),
        "interface_type" => "interface{}".to_string(),
        "struct_type" => "struct{}".to_string(),
        "parameter_list" => {
            // Multiple return values - extract only the type from each parameter_declaration
            let mut types = Vec::new();
            let mut cursor = node.walk();
            for child in node.children(&mut cursor) {
                if child.kind() == "parameter_declaration" {
                    // Use the "type" field to get only the type, not the name
                    if let Some(ty) = child.child_by_field_name("type") {
                        types.push(extract_type_string(ty, source));
                    }
                }
            }
            if types.len() == 1 {
                types[0].clone()
            } else {
                format!("({})", types.join(", "))
            }
        }
        "variadic_parameter_declaration" => {
            if let Some(ty) = node.child_by_field_name("type") {
                format!("...{}", extract_type_string(ty, source))
            } else {
                "...".to_string()
            }
        }
        _ => node_text(node, source).to_string(),
    }
}

fn extract_type_args(node: tree_sitter::Node, source: &str) -> Vec<String> {
    let mut args = Vec::new();
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        if is_type_node(child.kind()) {
            args.push(extract_type_string(child, source));
        }
    }
    args
}

/// Extract a function type like `func(A, B) C` into a string representation
fn extract_function_type(node: tree_sitter::Node, source: &str) -> String {
    let mut params = Vec::new();
    let mut result = String::new();

    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        match child.kind() {
            "parameter_list" => {
                // This is the parameters of the function type
                let mut param_cursor = child.walk();
                for param_child in child.children(&mut param_cursor) {
                    if param_child.kind() == "parameter_declaration" {
                        // Get the type from the parameter (may have multiple names with same type)
                        let mut names_count = 0;
                        let mut ty = String::new();

                        let mut inner_cursor = param_child.walk();
                        for inner_child in param_child.children(&mut inner_cursor) {
                            if inner_child.kind() == "identifier" {
                                names_count += 1;
                            } else if is_type_node(inner_child.kind()) {
                                ty = extract_type_string(inner_child, source);
                            }
                        }

                        // If we have names, add that many parameters with this type
                        // If no names, just add the type once
                        let count = if names_count > 0 { names_count } else { 1 };
                        for _ in 0..count {
                            if !ty.is_empty() {
                                params.push(ty.clone());
                            }
                        }
                    } else if param_child.kind() == "variadic_parameter_declaration" {
                        let mut ty = String::new();
                        let mut inner_cursor = param_child.walk();
                        for inner_child in param_child.children(&mut inner_cursor) {
                            if is_type_node(inner_child.kind()) {
                                ty = format!("...{}", extract_type_string(inner_child, source));
                            }
                        }
                        if !ty.is_empty() {
                            params.push(ty);
                        }
                    }
                }
            }
            _ if is_type_node(child.kind()) => {
                // This is the result type (return type)
                result = extract_type_string(child, source);
            }
            _ => {}
        }
    }

    // Build the function type string: func(A, B) C
    if result.is_empty() {
        format!("func({})", params.join(", "))
    } else {
        format!("func({}) {}", params.join(", "), result)
    }
}

fn is_type_node(kind: &str) -> bool {
    matches!(
        kind,
        "type_identifier"
            | "identifier"
            | "pointer_type"
            | "slice_type"
            | "array_type"
            | "map_type"
            | "channel_type"
            | "qualified_type"
            | "generic_type"
            | "function_type"
            | "interface_type"
            | "struct_type"
            | "parameter_list"
            | "variadic_parameter_declaration"
    )
}

fn node_text<'a>(node: tree_sitter::Node, source: &'a str) -> &'a str {
    &source[node.byte_range()]
}

fn is_exported(name: &str) -> bool {
    name.chars().next().is_some_and(|c| c.is_ascii_uppercase())
}

#[cfg(test)]
mod tests {
    use std::fs::{self, File};
    use std::io::Write;

    use super::*;

    #[test]
    fn test_extract_go_file() {
        let dir = std::env::temp_dir().join("soppo-treesitter-test");
        let _ = fs::remove_dir_all(&dir);
        fs::create_dir_all(&dir).unwrap();

        let file_path = dir.join("test.go");
        let mut file = File::create(&file_path).unwrap();
        writeln!(
            file,
            r#"
package test

type Point struct {{
    X int
    Y int
}}

func Hello(name string) string {{
    return "Hello, " + name
}}

const MaxSize = 100
"#
        )
        .unwrap();

        let pkg = extract(&file_path).unwrap();
        assert_eq!(pkg.name, "test");
        assert!(pkg.functions.contains_key("Hello"));
        assert!(pkg.types.contains_key("Point"));
        assert!(pkg.constants.contains_key("MaxSize"));

        fs::remove_dir_all(&dir).unwrap();
    }

    #[test]
    fn test_extract_with_soppo_marker() {
        let dir = std::env::temp_dir().join("soppo-treesitter-marker-test");
        let _ = fs::remove_dir_all(&dir);
        fs::create_dir_all(&dir).unwrap();

        let file_path = dir.join("result.go");
        let mut file = File::create(&file_path).unwrap();
        writeln!(
            file,
            r#"
package test

/*soppo:enum
Result[T, E] {{
    Ok T
    Err E
}}
*/
type Result[T any, E any] interface {{
    isResult()
}}
"#
        )
        .unwrap();

        let pkg = extract(&file_path).unwrap();
        assert!(pkg.soppo_types.contains_key("Result"));

        let soppo_type = &pkg.soppo_types["Result"];
        assert_eq!(soppo_type.kind, "enum");
        assert_eq!(soppo_type.name, "Result");
        assert_eq!(soppo_type.generics, vec!["T", "E"]);
        assert_eq!(soppo_type.variants.len(), 2);

        fs::remove_dir_all(&dir).unwrap();
    }

    #[test]
    fn test_extract_generics() {
        let dir = std::env::temp_dir().join("soppo-treesitter-generics-test");
        let _ = fs::remove_dir_all(&dir);
        fs::create_dir_all(&dir).unwrap();

        let file_path = dir.join("generics.go");
        let mut file = File::create(&file_path).unwrap();
        writeln!(
            file,
            r#"
package test

func Map[T any, U any](items []T, f func(T) U) []U {{
    return nil
}}

type Container[T any] struct {{
    Value T
}}
"#
        )
        .unwrap();

        let pkg = extract(&file_path).unwrap();

        let map_fn = &pkg.functions["Map"];
        assert_eq!(map_fn.generics, vec!["T", "U"]);
        assert_eq!(map_fn.params.len(), 2);

        let container = &pkg.types["Container"];
        assert_eq!(container.generics, vec!["T"]);
        assert_eq!(container.kind, "struct");

        fs::remove_dir_all(&dir).unwrap();
    }

    #[test]
    fn test_extract_type_alias() {
        let dir = std::env::temp_dir().join("soppo-treesitter-alias-test");
        let _ = fs::remove_dir_all(&dir);
        fs::create_dir_all(&dir).unwrap();

        let file_path = dir.join("alias.go");
        let mut file = File::create(&file_path).unwrap();
        writeln!(
            file,
            r#"
package test

type MyInt int
type StringSlice []string
type Handler func(string) error
"#
        )
        .unwrap();

        let pkg = extract(&file_path).unwrap();

        let my_int = &pkg.types["MyInt"];
        assert_eq!(my_int.kind, "alias");

        let string_slice = &pkg.types["StringSlice"];
        assert_eq!(string_slice.kind, "alias");

        let handler = &pkg.types["Handler"];
        assert_eq!(handler.kind, "alias");

        fs::remove_dir_all(&dir).unwrap();
    }

    #[test]
    fn test_extract_soppo_struct_variant() {
        let dir = std::env::temp_dir().join("soppo-treesitter-struct-variant-test");
        let _ = fs::remove_dir_all(&dir);
        fs::create_dir_all(&dir).unwrap();

        let file_path = dir.join("shape.go");
        let mut file = File::create(&file_path).unwrap();
        writeln!(
            file,
            r#"
package test

/*soppo:enum
Shape {{
    Circle {{
        radius float64
    }}
    Rectangle {{
        width float64
        height float64
    }}
    Point
}}
*/
type Shape interface {{
    isShape()
}}
"#
        )
        .unwrap();

        let pkg = extract(&file_path).unwrap();
        assert!(pkg.soppo_types.contains_key("Shape"));

        let soppo_type = &pkg.soppo_types["Shape"];
        assert_eq!(soppo_type.kind, "enum");
        assert_eq!(soppo_type.name, "Shape");
        assert_eq!(soppo_type.variants.len(), 3);

        // Circle variant with one field
        let circle = &soppo_type.variants[0];
        assert_eq!(circle.name, "Circle");
        assert_eq!(circle.fields.len(), 1);
        assert_eq!(circle.fields[0].name, "radius");
        assert_eq!(circle.fields[0].ty, "float64");

        // Rectangle variant with two fields
        let rect = &soppo_type.variants[1];
        assert_eq!(rect.name, "Rectangle");
        assert_eq!(rect.fields.len(), 2);
        assert_eq!(rect.fields[0].name, "width");
        assert_eq!(rect.fields[1].name, "height");

        // Point unit variant
        let point = &soppo_type.variants[2];
        assert_eq!(point.name, "Point");
        assert_eq!(point.fields.len(), 0);

        fs::remove_dir_all(&dir).unwrap();
    }

    #[test]
    fn test_extract_var_block() {
        let dir = std::env::temp_dir().join("soppo-var-block-test");
        let _ = fs::remove_dir_all(&dir);
        fs::create_dir_all(&dir).unwrap();

        let file_path = dir.join("test.go");
        let mut file = File::create(&file_path).unwrap();
        writeln!(
            file,
            r#"
package test

type File struct {{}}

func NewFile() *File {{ return nil }}

var (
    Stdin  = NewFile()
    Stdout = NewFile()
)

var Single = NewFile()
"#
        )
        .unwrap();

        let pkg = extract(&file_path).unwrap();

        assert!(
            pkg.functions.contains_key("NewFile"),
            "NewFile should exist"
        );
        assert_eq!(pkg.functions["NewFile"].return_type, "*File");
        assert!(pkg.variables.contains_key("Stdin"), "Stdin should exist");
        assert!(pkg.variables.contains_key("Stdout"), "Stdout should exist");
        assert!(pkg.variables.contains_key("Single"), "Single should exist");
        assert_eq!(pkg.variables["Stdin"].ty, "*File");

        fs::remove_dir_all(&dir).unwrap();
    }

    #[test]
    fn test_extract_os_stdin() {
        use std::process::Command;

        // Get GOROOT
        let output = Command::new("go").args(["env", "GOROOT"]).output().unwrap();
        let goroot = String::from_utf8_lossy(&output.stdout).trim().to_string();

        let os_path = std::path::PathBuf::from(&goroot).join("src/os");
        let pkg = extract(&os_path).unwrap();

        // os package should have NewFile function and Stdin variable
        assert!(
            pkg.functions.contains_key("NewFile"),
            "NewFile should exist"
        );
        assert!(pkg.variables.contains_key("Stdin"), "Stdin should exist");
        assert_eq!(pkg.variables["Stdin"].ty, "*File");
    }

    #[test]
    fn test_extract_bufio_methods() {
        use std::process::Command;

        // Get GOROOT
        let output = Command::new("go").args(["env", "GOROOT"]).output().unwrap();
        let goroot = String::from_utf8_lossy(&output.stdout).trim().to_string();

        let bufio_path = std::path::PathBuf::from(&goroot).join("src/bufio");
        let pkg = extract(&bufio_path).unwrap();

        println!("\n=== Types ===");
        for name in pkg.types.keys() {
            println!("  {}", name);
        }

        println!("\n=== Methods ===");
        for (receiver, methods) in &pkg.methods {
            println!("  {} has {} methods:", receiver, methods.len());
            for m in methods.iter().take(5) {
                println!("    - {} -> {}", m.name, m.return_type);
            }
        }

        // bufio should have Reader type with ReadString method
        assert!(pkg.types.contains_key("Reader"), "Reader type should exist");
        assert!(
            pkg.methods.contains_key("Reader"),
            "Reader should have methods"
        );

        let reader_methods = &pkg.methods["Reader"];
        let read_string = reader_methods.iter().find(|m| m.name == "ReadString");
        assert!(read_string.is_some(), "ReadString method should exist");

        let read_string = read_string.unwrap();
        println!(
            "\nReadString: params={:?}, return={}",
            read_string.params, read_string.return_type
        );
    }

    #[test]
    fn test_extract_named_return_values() {
        // Regression test: named return values like (n int, err error) should be
        // extracted as just the types (int, error), not (n, int, err, error)
        let dir = std::env::temp_dir().join("soppo-named-return-test");
        let _ = fs::remove_dir_all(&dir);
        fs::create_dir_all(&dir).unwrap();

        let file_path = dir.join("test.go");
        let mut file = File::create(&file_path).unwrap();
        writeln!(
            file,
            r#"
package test

func SingleNamedReturn(s string) (n int) {{
    return len(s)
}}

func MultiNamedReturn(s string) (n int, err error) {{
    return len(s), nil
}}

func ThreeNamedReturns(s string) (a int, b string, c error) {{
    return 0, s, nil
}}

func MixedReturn(s string) (int, error) {{
    return len(s), nil
}}
"#
        )
        .unwrap();

        let pkg = extract(&file_path).unwrap();

        // Single named return should be just "int", not "n int" or similar
        let single = &pkg.functions["SingleNamedReturn"];
        assert_eq!(
            single.return_type, "int",
            "Single named return should extract type only"
        );

        // Multiple named returns should be "(int, error)", not "(n, int, err, error)"
        let multi = &pkg.functions["MultiNamedReturn"];
        assert_eq!(
            multi.return_type, "(int, error)",
            "Multi named return should extract types only"
        );

        // Three named returns
        let three = &pkg.functions["ThreeNamedReturns"];
        assert_eq!(
            three.return_type, "(int, string, error)",
            "Three named returns should extract types only"
        );

        // Unnamed returns should still work
        let mixed = &pkg.functions["MixedReturn"];
        assert_eq!(
            mixed.return_type, "(int, error)",
            "Unnamed returns should work"
        );

        fs::remove_dir_all(&dir).unwrap();
    }
}
