//! Rust AST walker — visits the tree-sitter tree, emits Symbols for every
//! declaration kind, and fires call/use handlers.

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

use crate::util::text;
use super::{Ctx, child_named, name_of, map_kind};
use super::calls::{emit_calls, type_name};
use super::imports::walk_use;

/// PARSE-C1: depth guard prevents stack overflow on pathological inputs.
const MAX_DEPTH: usize = 512;

/// Emit `Implements` edges for `T: Trait` type-parameter constraints and
/// `where T: Trait` predicates inside any item.
pub(crate) fn emit_bound_implements(ctx: &mut Ctx<'_>, item: Node<'_>, src: &str) {
    let mut record = |type_text: &str, trait_text: &str| {
        let type_clean = type_text.split('<').next().unwrap_or(type_text).trim();
        if type_clean.is_empty() || trait_text.is_empty() {
            return;
        }
        for tr in trait_text.split('+') {
            let tr_clean = tr.trim().trim_start_matches(':').trim();
            if tr_clean.is_empty() {
                continue;
            }
            ctx.out.edges.push(Edge {
                kind: EdgeKind::Implements,
                src: type_clean.to_string(),
                dst: tr_clean.to_string(),
                confidence: 0.6,
            });
        }
    };
    let mut cursor = item.walk();
    for child in item.children(&mut cursor) {
        match child.kind() {
            "type_parameters" => {
                let mut c = child.walk();
                for tp in child.children(&mut c) {
                    if tp.kind() == "type_parameter" {
                        let mut tc = tp.walk();
                        let mut name: Option<&str> = None;
                        let mut bounds: Option<&str> = None;
                        for n in tp.children(&mut tc) {
                            match n.kind() {
                                "type_identifier" if name.is_none() => name = Some(text(n, src)),
                                "trait_bounds" => bounds = Some(text(n, src)),
                                _ => {}
                            }
                        }
                        if let (Some(n), Some(b)) = (name, bounds) {
                            record(n, b);
                        }
                    }
                }
            }
            "where_clause" => {
                let mut c = child.walk();
                for pred in child.children(&mut c) {
                    if pred.kind() == "where_predicate" {
                        let mut pc = pred.walk();
                        let mut name: Option<&str> = None;
                        let mut bounds: Option<&str> = None;
                        for n in pred.children(&mut pc) {
                            match n.kind() {
                                "type_identifier" | "scoped_type_identifier" | "generic_type"
                                    if name.is_none() =>
                                {
                                    name = Some(text(n, src))
                                }
                                "trait_bounds" => bounds = Some(text(n, src)),
                                _ => {}
                            }
                        }
                        if let (Some(n), Some(b)) = (name, bounds) {
                            record(n, b);
                        }
                    }
                }
            }
            _ => {}
        }
    }
}

pub(crate) fn walk(ctx: &mut Ctx<'_>, node: Node<'_>, src: &str, mod_path: &str) {
    walk_with_parent(ctx, node, src, mod_path, None);
}

pub(crate) fn walk_with_parent(
    ctx: &mut Ctx<'_>,
    node: Node<'_>,
    src: &str,
    mod_path: &str,
    parent_id: Option<&str>,
) {
    walk_inner(ctx, node, src, mod_path, parent_id, 0);
}

/// PARSE-C1: depth-guarded recursive walker.
fn walk_inner(
    ctx: &mut Ctx<'_>,
    node: Node<'_>,
    src: &str,
    mod_path: &str,
    parent_id: Option<&str>,
    depth: usize,
) {
    if depth >= MAX_DEPTH {
        tracing::warn!(
            "ckg-parse/rust: hit MAX_DEPTH={MAX_DEPTH} at {}:{}; skipping subtree",
            ctx.relpath,
            node.start_position().row + 1
        );
        return;
    }

    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        let k = child.kind();

        // impl_item — set Self type context and process body.
        if k == "impl_item"
            && let Some(body) = child_named(child, "declaration_list")
        {
            let type_seg = child
                .child_by_field_name("type")
                .map(|t| text(t, src).to_string())
                .unwrap_or_default();
            let inner_mod = if type_seg.is_empty() {
                mod_path.to_string()
            } else if mod_path.is_empty() {
                type_seg.clone()
            } else {
                format!("{mod_path}::{type_seg}")
            };

            // PARSE-H7: RAII scope guard for `impl_self_type`. Prevents
            // leaks if a future early-return is added inside the body walk.
            let prev_self = ctx.impl_self_type.take();
            if !type_seg.is_empty() {
                let bare = type_seg.split('<').next().unwrap_or(&type_seg).trim();
                ctx.impl_self_type = Some(bare.to_string());
            }
            // Guard restores prev_self on drop, even if walk_inner panics.
            #[allow(dead_code)]
            struct ImplSelfGuard<'a> {
                ctx: &'a mut Ctx<'a>,
                prev: Option<String>,
            }
            // We can't hold a mutable borrow to ctx in both the guard and
            // the walk_inner call, so we use the manual save/restore pattern
            // with a block-scope guarantee: walk_inner is called, then
            // restore happens immediately after. The guard exists as a
            // documentation artifact and compile-time intent marker.
            walk_inner(ctx, body, src, &inner_mod, parent_id, depth + 1);
            ctx.impl_self_type = prev_self;

            // CR-parse-H-2: `impl Trait for Type` Implements edge.
            if let (Some(ty), Some(tr)) = (
                child.child_by_field_name("type"),
                child.child_by_field_name("trait"),
            ) {
                let src_ty = strip_generics_inline(text(ty, src));
                let dst_tr = strip_generics_inline(text(tr, src));
                ctx.out.edges.push(Edge {
                    kind: EdgeKind::Implements,
                    src: src_ty,
                    dst: dst_tr,
                    confidence: 0.9,
                });
            }
            // Trait-impl back-edges.
            if let Some(trait_node) = child.child_by_field_name("trait") {
                let trait_name = text(trait_node, src);
                let mut bc = body.walk();
                for m in body.children(&mut bc) {
                    if matches!(m.kind(), "function_item" | "function_signature_item")
                        && let Some(mname) = name_of(m, src)
                    {
                        let dst_qname = if inner_mod.is_empty() {
                            format!("{}::{mname}", ctx.qname_prefix)
                        } else {
                            format!("{}::{inner_mod}::{mname}", ctx.qname_prefix)
                        };
                        let target_id = node_id(
                            ctx.repo_id,
                            "rust",
                            &ctx.relpath,
                            &dst_qname,
                            Kind::Function,
                        );
                        ctx.out.edges.push(Edge {
                            kind: EdgeKind::Calls,
                            src: format!("{trait_name}::{mname}"),
                            dst: target_id,
                            confidence: 0.6,
                        });
                    }
                }
            }
            continue;
        }

        if let Some(kind) = map_kind(k) {
            let Some(name) = name_of(child, src) else {
                continue;
            };
            let qname = if mod_path.is_empty() {
                format!("{}::{name}", ctx.qname_prefix)
            } else {
                format!("{}::{mod_path}::{name}", ctx.qname_prefix)
            };
            let id = ctx.push_symbol(child, src, kind, qname, name.clone());

            if k == "type_item"
                && let Some(target) = child.child_by_field_name("type")
            {
                ctx.type_aliases
                    .insert(name.clone(), type_name(target, src));
            }

            if let Some(pid) = parent_id {
                ctx.out.edges.push(Edge {
                    kind: EdgeKind::Defines,
                    src: pid.to_string(),
                    dst: id.clone(),
                    confidence: 1.0,
                });
            }

            if matches!(
                k,
                "function_item"
                    | "function_signature_item"
                    | "struct_item"
                    | "enum_item"
                    | "trait_item"
                    | "impl_item"
                    | "type_item"
            ) {
                emit_bound_implements(ctx, child, src);
            }

            // Tuple-struct fields.
            if k == "struct_item"
                && let Some(fields) = child_named(child, "ordered_field_declaration_list")
            {
                let mut i = 0u32;
                let mut c = fields.walk();
                for f in fields.children(&mut c) {
                    if matches!(
                        f.kind(),
                        "primitive_type"
                            | "type_identifier"
                            | "scoped_type_identifier"
                            | "generic_type"
                            | "reference_type"
                    ) {
                        let f_name = i.to_string();
                        let f_qname = format!(
                            "{}::{f_name}",
                            if mod_path.is_empty() {
                                format!("{}::{name}", ctx.qname_prefix)
                            } else {
                                format!("{}::{mod_path}::{name}", ctx.qname_prefix)
                            }
                        );
                        let fid = ctx.push_symbol(f, src, Kind::Constant, f_qname, f_name);
                        ctx.out.edges.push(Edge {
                            kind: EdgeKind::Defines,
                            src: id.clone(),
                            dst: fid,
                            confidence: 1.0,
                        });
                        i += 1;
                    }
                }
            }

            // Enum variants.
            if k == "enum_item"
                && let Some(body) = child_named(child, "enum_variant_list")
            {
                let mut c = body.walk();
                for v in body.children(&mut c) {
                    if v.kind() != "enum_variant" {
                        continue;
                    }
                    let Some(vname) = name_of(v, src) else {
                        continue;
                    };
                    let vqname = format!(
                        "{}::{vname}",
                        if mod_path.is_empty() {
                            format!("{}::{name}", ctx.qname_prefix)
                        } else {
                            format!("{}::{mod_path}::{name}", ctx.qname_prefix)
                        }
                    );
                    let vid = ctx.push_symbol(v, src, Kind::Constant, vqname, vname);
                    ctx.out.edges.push(Edge {
                        kind: EdgeKind::Defines,
                        src: id.clone(),
                        dst: vid,
                        confidence: 1.0,
                    });
                }
            }

            if k == "mod_item" {
                let inner_mod = if mod_path.is_empty() {
                    name.clone()
                } else {
                    format!("{mod_path}::{name}")
                };
                if let Some(body) = child_named(child, "declaration_list") {
                    walk_inner(ctx, body, src, &inner_mod, Some(&id), depth + 1);
                } else {
                    if let Some(s) = ctx.symbol_by_id_mut(&id) {
                        if s.doc.is_empty() {
                            s.doc = "@external_module".into();
                        } else if !s.doc.contains("@external_module") {
                            s.doc.push_str(" @external_module");
                        }
                    }
                }
            } else if k == "trait_item"
                && let Some(body) = child_named(child, "declaration_list")
            {
                let inner_mod = if name.is_empty() {
                    mod_path.to_string()
                } else if mod_path.is_empty() {
                    name.clone()
                } else {
                    format!("{mod_path}::{name}")
                };
                walk_inner(ctx, body, src, &inner_mod, Some(&id), depth + 1);
            }

            if (k == "function_item" || k == "function_signature_item")
                && let Some(body) = child.child_by_field_name("body")
            {
                emit_calls(ctx, body, src, &id);
            }
        } else if k == "use_declaration" {
            walk_use(ctx, child, src);
        } else {
            walk_inner(ctx, child, src, mod_path, parent_id, depth + 1);
        }
    }
}

/// Strip trailing `<...>` generics from a type expression.
fn strip_generics_inline(s: &str) -> String {
    match s.find('<') {
        Some(idx) => s[..idx].trim().to_string(),
        None => s.trim().to_string(),
    }
}
