//! Go declaration handlers — function, method, type, const/var.

use ckg_core::{Edge, EdgeKind, Kind};
use tree_sitter::Node;

use crate::util::text;
use super::{Ctx, unwrap_pointer, collect_doc, is_test_name};
use super::calls::{emit_calls, collect_param_types};

pub(crate) fn handle_function(ctx: &mut Ctx<'_>, n: Node<'_>, src: &str) {
    // function_declaration children: `func`, identifier (name),
    // parameter_list (params), optional return type, block.
    let mut c = n.walk();
    let kids: Vec<_> = n.children(&mut c).collect();
    let Some(name_node) = kids.iter().find(|x| x.kind() == "identifier") else {
        return;
    };
    let name = text(*name_node, src).to_string();
    let qname = if ctx.package.is_empty() {
        name.clone()
    } else {
        format!("{}.{name}", ctx.package)
    };
    let mut doc = collect_doc(n, src);
    if is_test_name(&name) && !doc.contains("@test") {
        doc = if doc.is_empty() {
            "@test".into()
        } else {
            format!("@test\n{doc}")
        };
    }
    let id = ctx.push(n, src, Kind::Function, qname, name, doc);
    // Param types into the type map.
    if let Some(params) = kids.iter().find(|x| x.kind() == "parameter_list") {
        collect_param_types(*params, src, &mut ctx.types);
    }
    if let Some(body) = kids.iter().find(|x| x.kind() == "block") {
        emit_calls(ctx, *body, &id, src, None);
    }
}

pub(crate) fn handle_method(ctx: &mut Ctx<'_>, n: Node<'_>, src: &str) {
    // method_declaration children: `func`, parameter_list (receiver),
    // field_identifier (method name), parameter_list (params),
    // optional return type, block.
    let mut c = n.walk();
    let kids: Vec<_> = n.children(&mut c).collect();
    // First parameter_list = receiver. Inside it, parameter_declaration's
    // type child is either type_identifier or pointer_type.
    let receiver_type: Option<String> = kids
        .iter()
        .find(|x| x.kind() == "parameter_list")
        .and_then(|pl| {
            let mut pc = pl.walk();
            pl.children(&mut pc)
                .find(|p| p.kind() == "parameter_declaration")
                .and_then(|pd| {
                    let mut dc = pd.walk();
                    pd.children(&mut dc)
                        .find(|x| matches!(x.kind(), "type_identifier" | "pointer_type"))
                        .and_then(|t| unwrap_pointer(t, src))
                })
        });
    let Some(name_node) = kids.iter().find(|x| x.kind() == "field_identifier") else {
        return;
    };
    let name = text(*name_node, src).to_string();
    let qname = match (&ctx.package, &receiver_type) {
        (p, Some(rt)) if !p.is_empty() => format!("{p}.{rt}.{name}"),
        (p, None) if !p.is_empty() => format!("{p}.{name}"),
        (_, Some(rt)) => format!("{rt}.{name}"),
        (_, None) => name.clone(),
    };
    let doc = collect_doc(n, src);
    let id = ctx.push(n, src, Kind::Method, qname, name.clone(), doc);

    // Defines edge from receiver type → this method (if we know the receiver).
    if let Some(rt) = &receiver_type
        && let Some(parent_id) = ctx.name_to_id.get(rt).cloned()
    {
        ctx.out.edges.push(Edge {
            kind: EdgeKind::Defines,
            src: parent_id,
            dst: id.clone(),
            confidence: 1.0,
        });
    }

    // Param types into the body type map.
    let param_lists: Vec<_> = kids
        .iter()
        .filter(|x| x.kind() == "parameter_list")
        .collect();
    if let Some(params) = param_lists.get(1) {
        collect_param_types(**params, src, &mut ctx.types);
    }
    if let Some(body) = kids.iter().find(|x| x.kind() == "block") {
        emit_calls(ctx, *body, &id, src, receiver_type.as_deref());
    }
}

pub(crate) fn handle_type_decl(ctx: &mut Ctx<'_>, n: Node<'_>, src: &str) {
    let mut c = n.walk();
    for child in n.children(&mut c) {
        match child.kind() {
            "type_spec" => handle_type_spec(ctx, child, src),
            "type_alias" => handle_type_alias(ctx, child, src),
            _ => {}
        }
    }
}

fn handle_type_spec(ctx: &mut Ctx<'_>, n: Node<'_>, src: &str) {
    // `type Foo struct {…}` / `type Foo interface {…}` / `type Foo Bar`
    let mut c = n.walk();
    let kids: Vec<_> = n.children(&mut c).collect();
    let Some(name_node) = kids.iter().find(|x| x.kind() == "type_identifier") else {
        return;
    };
    let name = text(*name_node, src).to_string();
    let qname = if ctx.package.is_empty() {
        name.clone()
    } else {
        format!("{}.{name}", ctx.package)
    };
    let doc = collect_doc(n, src);
    // Body kind determines Symbol kind.
    let body = kids.iter().find(|x| {
        matches!(
            x.kind(),
            "struct_type" | "interface_type" | "type_identifier" | "pointer_type" | "function_type"
        ) && **x != *name_node
    });
    let kind = match body.map(|b| b.kind()) {
        Some("struct_type") => Kind::Struct,
        Some("interface_type") => Kind::Interface,
        _ => Kind::TypeAlias,
    };
    let id = ctx.push(*name_node, src, kind, qname, name, doc);

    if let Some(body) = body {
        match body.kind() {
            "struct_type" => emit_struct_fields(ctx, *body, &id, src),
            "interface_type" => emit_interface_methods(ctx, *body, &id, src),
            _ => {}
        }
    }
}

fn handle_type_alias(ctx: &mut Ctx<'_>, n: Node<'_>, src: &str) {
    let mut c = n.walk();
    let kids: Vec<_> = n.children(&mut c).collect();
    let Some(name_node) = kids.iter().find(|x| x.kind() == "type_identifier") else {
        return;
    };
    let name = text(*name_node, src).to_string();
    let qname = if ctx.package.is_empty() {
        name.clone()
    } else {
        format!("{}.{name}", ctx.package)
    };
    let doc = collect_doc(n, src);
    ctx.push(*name_node, src, Kind::TypeAlias, qname, name, doc);
}

fn emit_struct_fields(ctx: &mut Ctx<'_>, struct_node: Node<'_>, struct_id: &str, src: &str) {
    let mut c = struct_node.walk();
    for body in struct_node.children(&mut c) {
        if body.kind() != "field_declaration_list" {
            continue;
        }
        let mut bc = body.walk();
        for field in body.children(&mut bc) {
            if field.kind() != "field_declaration" {
                continue;
            }
            // Embedded struct: field_declaration with a type_identifier
            // and NO field_identifier first.
            let mut fc = field.walk();
            let kids: Vec<_> = field.children(&mut fc).collect();
            let has_field_name = kids.iter().any(|x| x.kind() == "field_identifier");
            if !has_field_name
                && let Some(t) = kids
                    .iter()
                    .find(|x| matches!(x.kind(), "type_identifier" | "qualified_type"))
            {
                ctx.out.edges.push(Edge {
                    kind: EdgeKind::Extends,
                    src: struct_id.to_string(),
                    dst: text(*t, src).to_string(),
                    confidence: 0.9,
                });
            }
        }
    }
}

fn emit_interface_methods(ctx: &mut Ctx<'_>, iface_node: Node<'_>, iface_id: &str, src: &str) {
    let mut c = iface_node.walk();
    for elem in iface_node.children(&mut c) {
        if elem.kind() != "method_elem" {
            continue;
        }
        let mut ec = elem.walk();
        if let Some(name) = elem
            .children(&mut ec)
            .find(|x| x.kind() == "field_identifier")
        {
            let mname = text(name, src).to_string();
            let qname = format!("{}.{}", ctx.package, mname);
            let id = ctx.push(name, src, Kind::Method, qname, mname, String::new());
            ctx.out.edges.push(Edge {
                kind: EdgeKind::Defines,
                src: iface_id.to_string(),
                dst: id,
                confidence: 1.0,
            });
        }
    }
}

pub(crate) fn handle_const_or_var(ctx: &mut Ctx<'_>, n: Node<'_>, src: &str, kind: Kind) {
    // const_spec / var_spec children: identifier (name), optional type, optional value.
    let mut stack = vec![n];
    while let Some(node) = stack.pop() {
        if matches!(node.kind(), "const_spec" | "var_spec") {
            let mut c = node.walk();
            let kids: Vec<_> = node.children(&mut c).collect();
            let Some(name_node) = kids.iter().find(|x| x.kind() == "identifier") else {
                continue;
            };
            let name = text(*name_node, src).to_string();
            let qname = if ctx.package.is_empty() {
                name.clone()
            } else {
                format!("{}.{name}", ctx.package)
            };
            let doc = collect_doc(node, src);
            ctx.push(*name_node, src, kind, qname, name.clone(), doc);

            // Type harvest: explicit `var x Foo` or `var x *Foo`.
            if let Some(ty) = kids
                .iter()
                .find(|x| matches!(x.kind(), "type_identifier" | "pointer_type"))
                .and_then(|t| unwrap_pointer(*t, src))
            {
                ctx.types.insert(name, ty);
            }
            continue;
        }
        let mut c = node.walk();
        for ch in node.children(&mut c) {
            stack.push(ch);
        }
    }
}
