//! Go call-site detection and body-local type harvesting.

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

use crate::util::text;
use super::{Ctx, unwrap_pointer};

/// Pull a Go type name out of a constructor-like expression:
/// `Foo{…}` → `Foo`, `&Foo{…}` → `Foo`, `new(Foo)` → `Foo`.
pub(crate) fn extract_constructor_type(node: Node<'_>, src: &str) -> Option<String> {
    match node.kind() {
        "composite_literal" => {
            let mut c = node.walk();
            node.children(&mut c)
                .find(|x| matches!(x.kind(), "type_identifier" | "qualified_type"))
                .map(|t| text(t, src).to_string())
        }
        "unary_expression" => {
            // `&Foo{...}` — descend.
            let mut c = node.walk();
            node.children(&mut c)
                .find_map(|x| extract_constructor_type(x, src))
        }
        "call_expression" => {
            // `new(Foo)` — function = `new`, args = `(Foo)`.
            let func = node.child_by_field_name("function")?;
            if text(func, src) != "new" {
                return None;
            }
            let args = node.child_by_field_name("arguments")?;
            let mut c = args.walk();
            args.children(&mut c)
                .find(|x| matches!(x.kind(), "type_identifier" | "qualified_type"))
                .map(|t| text(t, src).to_string())
        }
        _ => None,
    }
}

/// Harvest `var x Foo`, `x := Foo{}`, `x := &Foo{}`, `x := new(Foo)`
/// inside a function body. Pointer indirection stripped.
pub(crate) fn collect_body_var_types(
    body: Node<'_>,
    src: &str,
    out: &mut std::collections::HashMap<String, String>,
) {
    let mut cursor = body.walk();
    let mut stack = vec![body];
    while let Some(n) = stack.pop() {
        match n.kind() {
            "var_spec" => {
                let mut c = n.walk();
                let kids: Vec<_> = n.children(&mut c).collect();
                let name = kids
                    .iter()
                    .find(|x| x.kind() == "identifier")
                    .map(|x| text(*x, src).to_string());
                let ty = kids
                    .iter()
                    .find(|x| matches!(x.kind(), "type_identifier" | "pointer_type"))
                    .and_then(|t| unwrap_pointer(*t, src));
                if let (Some(n), Some(t)) = (name, ty) {
                    out.insert(n, t);
                }
            }
            "short_var_declaration" => {
                let mut c = n.walk();
                let kids: Vec<_> = n.children(&mut c).collect();
                let name = kids
                    .iter()
                    .find(|x| x.kind() == "expression_list")
                    .and_then(|el| {
                        let mut ec = el.walk();
                        el.children(&mut ec)
                            .find(|x| x.kind() == "identifier")
                            .map(|i| text(i, src).to_string())
                    });
                // RHS — parse `Foo{…}` (composite_literal), `&Foo{…}`
                // (unary_expression containing composite_literal),
                // `new(Foo)` (call_expression with `new` + type arg).
                let ty = kids
                    .iter()
                    .find(|x| {
                        x.kind() == "expression_list" && {
                            let mut ec = x.walk();
                            x.children(&mut ec).any(|e| {
                                matches!(
                                    e.kind(),
                                    "composite_literal" | "unary_expression" | "call_expression"
                                )
                            })
                        }
                    })
                    .and_then(|el| {
                        let mut ec = el.walk();
                        el.children(&mut ec)
                            .find_map(|e| extract_constructor_type(e, src))
                    });
                if let (Some(n), Some(t)) = (name, ty) {
                    out.insert(n, t);
                }
            }
            _ => {}
        }
        for ch in n.children(&mut cursor) {
            stack.push(ch);
        }
    }
}

/// Harvest typed parameters into the type map.
/// `func f(x Foo, y *Bar)` → x→Foo, y→Bar.
pub(crate) fn collect_param_types(
    params: Node<'_>,
    src: &str,
    out: &mut std::collections::HashMap<String, String>,
) {
    let mut c = params.walk();
    for p in params.children(&mut c) {
        if p.kind() != "parameter_declaration" {
            continue;
        }
        let mut pc = p.walk();
        let mut name: Option<String> = None;
        let mut ty: Option<String> = None;
        for ch in p.children(&mut pc) {
            match ch.kind() {
                "identifier" if name.is_none() => {
                    name = Some(text(ch, src).to_string());
                }
                "type_identifier" | "pointer_type" | "qualified_type" => {
                    ty = unwrap_pointer(ch, src);
                }
                _ => {}
            }
        }
        if let (Some(n), Some(t)) = (name, ty) {
            out.insert(n, t);
        }
    }
}

/// Walk a function body harvesting body-local var-type bindings, then
/// emit Calls (and Imports for any import-rooted member call we
/// encounter through aliases).
pub(crate) fn emit_calls(
    ctx: &mut Ctx<'_>,
    body: Node<'_>,
    src_id: &str,
    src: &str,
    receiver_type: Option<&str>,
) {
    // Body-local var declarations + short var declarations.
    collect_body_var_types(body, src, &mut ctx.types);

    let mut stack = vec![body];
    while let Some(n) = stack.pop() {
        if n.kind() == "call_expression"
            && let Some(func) = n.child_by_field_name("function")
        {
            let target = text(func, src).to_string();
            let head = target.split('.').next().unwrap_or(&target).to_string();

            // Receiver-type dispatch: caller is the implicit `self` of
            // a method. Without explicit `self` keyword in Go, the only
            // way for a method body to reference its own type is via
            // `r.foo()` where `r` is the receiver name. That's already
            // handled by collect_param_types for the receiver. So no
            // separate `this`-rewrite is needed.
            let _ = receiver_type;

            let typed_dst = ctx
                .types
                .get(&head)
                .map(|ty| target.replacen(head.as_str(), ty, 1));
            let (dst, conf) = if let Some(typed) = typed_dst {
                (typed, 0.7)
            } else if !target.contains('.')
                && let Some(id) = ctx.name_to_id.get(&head)
            {
                (id.clone(), 1.0)
            } else if let Some(full) = ctx.aliases.get(&head) {
                let suffix = if target.len() > head.len() {
                    &target[head.len()..]
                } else {
                    ""
                };
                (format!("{full}{suffix}"), 0.9)
            } else {
                (target, 0.5)
            };
            ctx.out.edges.push(Edge {
                kind: EdgeKind::Calls,
                src: src_id.to_string(),
                dst,
                confidence: conf,
            });
        }
        let mut c = n.walk();
        for child in n.children(&mut c) {
            stack.push(child);
        }
    }
}
