//! Ruby walk helpers — mixin/attr/define_method/variadic-tag handlers
//! extracted from the main walker dispatch loop to keep file size under 200L.

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

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

// ---------------------------------------------------------------------------
// Mixin edges
// ---------------------------------------------------------------------------

/// `include Foo, Bar` / `extend Mod` / `prepend Mod` → Extends edges from
/// the enclosing class id to each module name.
pub(super) fn handle_mixin(ctx: &mut Ctx<'_>, n: Node<'_>, src: &str, class_id: &str) {
    let Some(args) = n.child_by_field_name("arguments") else {
        return;
    };
    let mut c = args.walk();
    for arg in args.children(&mut c) {
        if matches!(arg.kind(), "constant" | "scope_resolution" | "identifier") {
            ctx.out.edges.push(Edge {
                kind: EdgeKind::Extends,
                src: class_id.to_string(),
                dst: text(arg, src).to_string(),
                confidence: 0.9,
            });
        }
    }
}

// ---------------------------------------------------------------------------
// define_method
// ---------------------------------------------------------------------------

/// `define_method :foo do; end` / `define_method "foo" do; end` →
/// emit a `Method` Symbol with that literal name.
pub(super) fn handle_define_method(
    ctx: &mut Ctx<'_>,
    n: Node<'_>,
    src: &str,
    scope: &str,
    class_id: &str,
    is_public: bool,
) {
    let Some(args) = n.child_by_field_name("arguments") else {
        return;
    };
    let mut c = args.walk();
    let mut name: Option<String> = None;
    for arg in args.children(&mut c) {
        match arg.kind() {
            "simple_symbol" => {
                name = Some(strip_quotes(text(arg, src)).to_string());
                break;
            }
            "string" => {
                let mut sc = arg.walk();
                for s in arg.children(&mut sc) {
                    if s.kind() == "string_content" {
                        name = Some(text(s, src).to_string());
                        break;
                    }
                }
                if name.is_none() {
                    name = Some(strip_quotes(text(arg, src)).to_string());
                }
                break;
            }
            _ => {}
        }
    }
    let Some(name) = name else { return };
    let qname = if scope.is_empty() {
        format!("{}.{name}", ctx.module)
    } else {
        format!("{}::{scope}.{name}", ctx.module)
    };
    let id = ctx.push_with_visibility(n, src, Kind::Method, qname, name, is_public);
    ctx.out.edges.push(Edge {
        kind: EdgeKind::Defines,
        src: class_id.to_string(),
        dst: id,
        confidence: 1.0,
    });
}

// ---------------------------------------------------------------------------
// attr_accessor / attr_reader / attr_writer
// ---------------------------------------------------------------------------

/// `attr_accessor :x, :y` → emit Method symbols `x`, `x=`, `y`, `y=`.
/// `attr_reader` / `attr_writer` emit only the getter / setter.
pub(super) fn handle_attr(
    ctx: &mut Ctx<'_>,
    n: Node<'_>,
    src: &str,
    macro_name: &str,
    scope: &str,
    class_id: Option<&str>,
    is_public: bool,
) {
    let Some(args) = n.child_by_field_name("arguments") else {
        return;
    };
    let mut c = args.walk();
    for arg in args.children(&mut c) {
        if arg.kind() != "simple_symbol" {
            continue;
        }
        let base = strip_quotes(text(arg, src)).to_string();
        let names: Vec<String> = match macro_name {
            "attr_reader" => vec![base],
            "attr_writer" => vec![format!("{base}=")],
            _ => vec![base.clone(), format!("{base}=")], // attr_accessor
        };
        for name in names {
            let qname = if scope.is_empty() {
                format!("{}.{name}", ctx.module)
            } else {
                format!("{}::{scope}.{name}", ctx.module)
            };
            let id = ctx.push_with_visibility(n, src, Kind::Method, qname, name, is_public);
            if let Some(pid) = class_id {
                ctx.out.edges.push(Edge {
                    kind: EdgeKind::Defines,
                    src: pid.to_string(),
                    dst: id,
                    confidence: 1.0,
                });
            }
        }
    }
}

// ---------------------------------------------------------------------------
// Variadic tag
// ---------------------------------------------------------------------------

/// Stamp `@variadic` on a method's doc when its parameter list contains
/// `*args` (`splat_parameter`), `**kw` (`hash_splat_parameter`), or `&blk`
/// (`block_parameter`).
pub(crate) fn tag_variadic(ctx: &mut Ctx<'_>, method: Node<'_>, id: &str) {
    let Some(params) = method.child_by_field_name("parameters") else {
        return;
    };
    let mut tags: Vec<&str> = Vec::new();
    let mut c = params.walk();
    for p in params.children(&mut c) {
        match p.kind() {
            "splat_parameter" => tags.push("splat"),
            "hash_splat_parameter" => tags.push("double_splat"),
            "block_parameter" => tags.push("block_param"),
            _ => {}
        }
    }
    if tags.is_empty() {
        return;
    }
    let Some(sym) = ctx.symbol_by_id_mut(id) else {
        return;
    };
    let marker = format!("@variadic({})", tags.join(","));
    if sym.doc.is_empty() {
        sym.doc = marker;
    } else if !sym.doc.contains("@variadic") {
        sym.doc.push(' ');
        sym.doc.push_str(&marker);
    }
}
