//! Rust call-site detection and post-pass call resolver.
//!
//! `emit_calls` — walk a function body recording raw call targets with
//! type-aware receiver dispatch (`self.foo()` → `Type::foo` at 0.7).
//! `resolve_calls` — post-pass that rewrites raw targets into typed
//! Calls/Awaits edges using the name→id map and use-alias map.

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

use crate::util::text;
use super::Ctx;

pub(crate) fn emit_calls(ctx: &mut Ctx<'_>, node: Node<'_>, src: &str, src_id: &str) {
    // First pass over the body: build var_name → type_name map from `let
    // x: T = ...` and from the enclosing fn's parameters.
    let mut types: std::collections::HashMap<String, String> = std::collections::HashMap::new();
    // Seed `self → <impl Type>` so `self.foo()` resolves to `Type.foo`
    // at conf 0.7. Without this, self-method calls bottom out at conf 0.4.
    if let Some(self_ty) = ctx.impl_self_type.as_deref() {
        types.insert("self".to_string(), self_ty.to_string());
    }
    if let Some(params) = node
        .parent()
        .and_then(|f| f.child_by_field_name("parameters"))
    {
        collect_param_types(params, src, &mut types);
    }
    collect_let_types(node, src, &mut types);

    let mut cursor = node.walk();
    let mut stack = vec![node];
    while let Some(n) = stack.pop() {
        let k = n.kind();
        if k == "call_expression"
            && let Some(func) = n.child_by_field_name("function")
        {
            let (target_text, receiver_var): (String, Option<String>) =
                if func.kind() == "field_expression" {
                    let method_name = func
                        .child_by_field_name("field")
                        .map(|x| text(x, src).to_string())
                        .unwrap_or_else(|| text(func, src).to_string());
                    let receiver = func
                        .child_by_field_name("value")
                        .filter(|v| v.kind() == "identifier")
                        .map(|v| text(v, src).to_string());
                    (method_name, receiver)
                } else {
                    (text(func, src).to_string(), None)
                };
            // Resolve receiver via the type map → upgrade to `Type::method`.
            // Follow `type Foo = Bar` aliases so dispatch resolves through
            // chains (`x: MyResult` → `Result` → `core::result::Result`).
            let dst = if let Some(r) = receiver_var.as_deref()
                && let Some(ty) = types.get(r)
            {
                let chained = follow_type_alias(&ctx.type_aliases, ty);
                format!("{chained}::{target_text}")
            } else {
                target_text.clone()
            };
            let is_awaited = is_under_await(n, node);
            ctx.raw_calls.push((src_id.to_string(), dst, is_awaited));
            if target_text == "route" {
                crate::framework::axum::detect_axum_route(ctx, n, src);
            }
        }
        // Macro invocations parse their body as opaque `token_tree`s, so
        // calls like `json!({ k: helper() })` never appear as
        // `call_expression` and would otherwise be invisible. Walk the
        // token tree, picking out the `identifier` + `(`-prefixed
        // `token_tree` shape that means a call site.
        if k == "macro_invocation" {
            collect_macro_calls(ctx, n, src, src_id);
        }
        for child in n.children(&mut cursor) {
            stack.push(child);
        }
    }
}

/// Walk a `macro_invocation` looking for `identifier` nodes immediately
/// followed by a parenthesized `token_tree` — the syntactic shape of a
/// function call. Emits a raw call edge with confidence 0.5 (no type
/// info available inside the macro body).
fn collect_macro_calls(ctx: &mut Ctx<'_>, mac: Node<'_>, src: &str, src_id: &str) {
    let mut cursor = mac.walk();
    let mut stack = vec![mac];
    while let Some(n) = stack.pop() {
        if n.kind() == "token_tree" {
            // Scan immediate children for `identifier` followed by a
            // `token_tree` whose first character is `(`.
            let mut prev: Option<Node<'_>> = None;
            let mut c = n.walk();
            for child in n.children(&mut c) {
                if let Some(p) = prev
                    && p.kind() == "identifier"
                    && child.kind() == "token_tree"
                    && text(child, src).starts_with('(')
                {
                    let name = text(p, src).to_string();
                    // Skip tokens that are obviously not calls — Rust
                    // keywords / single-letter capitals (likely types).
                    if !is_macro_callee_skip(&name) {
                        ctx.raw_calls.push((src_id.to_string(), name, false));
                    }
                }
                prev = Some(child);
            }
        }
        for child in n.children(&mut cursor) {
            stack.push(child);
        }
    }
}

fn is_macro_callee_skip(name: &str) -> bool {
    // Rust keywords + common literals that can't be calls. Anything
    // CamelCase is also skipped — it's a type or variant, not a fn.
    matches!(
        name,
        "if" | "else"
            | "match"
            | "let"
            | "mut"
            | "ref"
            | "as"
            | "in"
            | "for"
            | "while"
            | "loop"
            | "true"
            | "false"
            | "self"
            | "Self"
            | "super"
            | "crate"
            | "return"
            | "break"
            | "continue"
            | "move"
            | "async"
            | "await"
            | "use"
            | "fn"
            | "type"
            | "impl"
            | "trait"
            | "where"
            | "pub"
            | "const"
            | "static"
            | "unsafe"
    )
}

/// True if any ancestor of `call_node` (within the function body) is an
/// `await_expression` — the call site is being awaited.
fn is_under_await(node: Node<'_>, body_root: Node<'_>) -> bool {
    let mut cur = node;
    while cur != body_root {
        let Some(parent) = cur.parent() else { break };
        if parent.kind() == "await_expression" {
            return true;
        }
        cur = parent;
    }
    false
}

/// Follow `type X = Y; type Y = Z;` chains, capped at 8 hops to prevent
/// pathological cycles. Returns the deepest non-alias name.
pub(crate) fn follow_type_alias(
    aliases: &std::collections::HashMap<String, String>,
    start: &str,
) -> String {
    let mut current = start.to_string();
    for _ in 0..8 {
        match aliases.get(&current) {
            Some(next) if next != &current => current = next.clone(),
            _ => break,
        }
    }
    current
}

/// Collect `(var_name, type_name)` from `fn f(param: Type, …)` parameter list.
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" {
            continue;
        }
        if let (Some(pat), Some(ty)) = (
            p.child_by_field_name("pattern"),
            p.child_by_field_name("type"),
        ) && pat.kind() == "identifier"
        {
            let name = text(pat, src).to_string();
            out.insert(name, type_name(ty, src));
        }
    }
}

/// Collect bindings inside `body`:
///   - `let x: T = …` / `let x = T::new(…)` annotated or inferred from RHS
///   - `if let Some(x) = expr` / `while let Some(x) = …` / `match _ { Some(x) => … }`
///     where the `tuple_struct_pattern` head identifier (e.g. `Some`, `Foo::Bar`)
///     is recorded as the bound variable's "type".
///
/// The tuple-struct-pattern binding is approximate: in `Some(x)` we record
/// `x: Some` (rather than the inner `T` of `Option<T>`). Good enough for
/// dispatch when the user actually wrote `Some::method` etc. — for the more
/// common case the binding is at least visible, lifting confidence from 0.5 to
/// 0.7 on `x.method` calls inside the arm.
pub(crate) fn collect_let_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() {
            "let_declaration" => {
                if let Some(pat) = n.child_by_field_name("pattern")
                    && pat.kind() == "identifier"
                {
                    let var = text(pat, src).to_string();
                    if let Some(ty) = n.child_by_field_name("type") {
                        out.insert(var, type_name(ty, src));
                    } else if let Some(val) = n.child_by_field_name("value")
                        && let Some(ty) = infer_from_rhs(val, src)
                    {
                        out.insert(var, ty);
                    }
                }
            }
            // `if let <pat> = <expr>` / `while let <pat> = <expr>`.
            "let_condition" => harvest_pattern_bindings(n, src, out),
            "match_arm" => {
                if let Some(p) = n.child_by_field_name("pattern") {
                    harvest_match_pattern(p, src, out);
                }
            }
            _ => {}
        }
        for child in n.children(&mut cursor) {
            stack.push(child);
        }
    }
}

/// `let_condition` has shape `let <pattern> = <expr>`. When `<pattern>` is a
/// `tuple_struct_pattern` `Some(v)` we record `v: Some`.
fn harvest_pattern_bindings(
    node: Node<'_>,
    src: &str,
    out: &mut std::collections::HashMap<String, String>,
) {
    let mut c = node.walk();
    for child in node.children(&mut c) {
        if child.kind() == "tuple_struct_pattern" {
            harvest_tuple_struct_pattern(child, src, out);
        }
    }
}

fn harvest_match_pattern(
    pat: Node<'_>,
    src: &str,
    out: &mut std::collections::HashMap<String, String>,
) {
    let mut c = pat.walk();
    for child in pat.children(&mut c) {
        if child.kind() == "tuple_struct_pattern" {
            harvest_tuple_struct_pattern(child, src, out);
        }
    }
}

fn harvest_tuple_struct_pattern(
    n: Node<'_>,
    src: &str,
    out: &mut std::collections::HashMap<String, String>,
) {
    // First child is the constructor (head identifier or scoped path).
    let mut c = n.walk();
    let mut head: Option<String> = None;
    let mut bindings: Vec<String> = Vec::new();
    for child in n.children(&mut c) {
        match child.kind() {
            "identifier" | "scoped_identifier" if head.is_none() => {
                head = Some(text(child, src).to_string());
            }
            "identifier" => bindings.push(text(child, src).to_string()),
            _ => {}
        }
    }
    let Some(head) = head else { return };
    for b in bindings {
        out.insert(b, head.clone());
    }
}

pub(crate) fn type_name(ty: Node<'_>, src: &str) -> String {
    // For `Foo`, `Foo<T>`, `&Foo`, `Box<Foo>` we want the leaf path leading
    // identifier — good enough for v0.1 dispatch.
    let raw = text(ty, src);
    raw.trim_start_matches('&')
        .trim_start_matches("mut ")
        .split(['<', ' ', ','])
        .next()
        .unwrap_or(raw)
        .trim()
        .to_string()
}

/// Infer the type of a RHS expression from its textual shape.
///
/// Handles `Foo::new(...)` / `Foo::default()` / `Foo { ... }` / `Foo(...)`.
///
/// **M12 (YAGNI – no fix)**: `Box::leak(x)` is a known false-positive — the
/// inferred type would be `Box`, not the pointee type. Fixing this requires
/// type inference beyond what the AST walker can provide. The edge is emitted
/// at low confidence (0.5) so callers can filter by threshold.
pub(crate) fn infer_from_rhs(val: Node<'_>, src: &str) -> Option<String> {
    let raw = text(val, src);
    // `Foo::new(...)` / `Foo::default()` / `Foo { ... }` / `Foo(...)`
    let head = raw.split([':', '(', '{', ' ']).next().unwrap_or("").trim();
    if head
        .chars()
        .next()
        .map(|c| c.is_ascii_uppercase())
        .unwrap_or(false)
    {
        Some(head.to_string())
    } else {
        None
    }
}

/// Post-pass: rewrite `raw_calls` into `Calls` edges with the highest-
/// confidence `dst` we can derive. Tiers:
///   - 1.0  bare name matches a same-file symbol
///   - 0.9  bare name matches a `use` alias
///   - 0.7  `Type::method` form (type-aware dispatch)
///   - 0.5  unresolved
pub(crate) fn resolve_calls(ctx: &mut Ctx<'_>) {
    let raw = std::mem::take(&mut ctx.raw_calls);
    for (src, raw_target, is_awaited) in raw {
        let head = raw_target
            .split("::")
            .next()
            .unwrap_or(&raw_target)
            .trim()
            .to_string();
        let is_typed_dispatch = raw_target.contains("::") && head != "self";
        // Only treat the call as a file-local match when the raw target is a
        // bare identifier (no `::`); otherwise `Foo::bar()` would match
        // `Foo` (the struct) instead of `Foo::bar` (the method).
        let (dst, confidence) = if !raw_target.contains("::")
            && let Some(id) = ctx.name_to_id.get(&head)
        {
            (id.clone(), 1.0)
        } else if let Some(full) = ctx.use_aliases.get(&head) {
            // Substitute the head with the alias path, preserve the suffix.
            let suffix = if raw_target.len() > head.len() {
                &raw_target[head.len()..]
            } else {
                ""
            };
            (format!("{full}{suffix}"), 0.9)
        } else if is_typed_dispatch {
            (raw_target, 0.7)
        } else {
            (raw_target, 0.5)
        };
        ctx.out.edges.push(Edge {
            kind: EdgeKind::Calls,
            src: src.clone(),
            dst: dst.clone(),
            confidence,
        });
        if is_awaited {
            ctx.out.edges.push(Edge {
                kind: EdgeKind::Awaits,
                src,
                dst,
                confidence,
            });
        }
    }
}

