//! ActiveRecord validation and scope macros —
//! `validates`, `scope`, `default_scope`.

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

use crate::lang::ruby::{Ctx, strip_quotes, text};

pub(crate) fn handle_validation(
    ctx: &mut Ctx<'_>,
    n: Node<'_>,
    src: &str,
    scope: &str,
    class_id: &str,
) {
    let Some(args) = n.child_by_field_name("arguments") else {
        return;
    };
    let mut field: Option<String> = None;
    let mut rules: Vec<String> = Vec::new();
    let mut c = args.walk();
    for arg in args.children(&mut c) {
        match arg.kind() {
            "simple_symbol" if field.is_none() => {
                field = Some(strip_quotes(text(arg, src)).to_string());
            }
            "pair" => {
                let key = arg.child_by_field_name("key").map(|k| text(k, src));
                if let Some(k) = key {
                    rules.push(strip_quotes(k.trim_end_matches(':')).to_string());
                }
            }
            "hash" => {
                let mut hc = arg.walk();
                for kv in arg.children(&mut hc) {
                    if kv.kind() == "pair"
                        && let Some(k) = kv.child_by_field_name("key").map(|k| text(k, src))
                    {
                        rules.push(strip_quotes(k.trim_end_matches(':')).to_string());
                    }
                }
            }
            _ => {}
        }
    }
    let Some(field) = field else { return };
    let name = format!("validates_{field}");
    let qname = if scope.is_empty() {
        format!("{}.{name}", ctx.module)
    } else {
        format!("{}::{scope}.{name}", ctx.module)
    };
    let pos = n.start_position();
    let id = node_id(ctx.repo_id, "ruby", &ctx.relpath, &qname, Kind::Method);
    ctx.out.symbols.push(Symbol {
        id: id.clone(),
        qname,
        name,
        kind: Kind::Method,
        file: ctx.relpath.clone(),
        line: pos.row as u32 + 1,
        col: pos.column as u32,
        is_public: true,
        doc: format!("validates {field} ({})", rules.join(", ")),
        hash: blake3::hash(text(n, src).as_bytes()).to_hex()[..16].to_string(),
    });
    ctx.out.edges.push(Edge {
        kind: EdgeKind::Defines,
        src: class_id.to_string(),
        dst: id,
        confidence: 1.0,
    });
}

pub(crate) fn handle_scope(
    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();
    for arg in args.children(&mut c) {
        if arg.kind() == "simple_symbol" {
            let name = strip_quotes(text(arg, src)).to_string();
            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,
            });
            return;
        }
    }
}

/// `default_scope { … }` → emit a `default_scope` Method on the class.
pub(crate) fn handle_default_scope(
    ctx: &mut Ctx<'_>,
    n: Node<'_>,
    src: &str,
    scope: &str,
    class_id: &str,
    is_public: bool,
) {
    let qname = if scope.is_empty() {
        format!("{}.default_scope", ctx.module)
    } else {
        format!("{}::{scope}.default_scope", ctx.module)
    };
    let id = ctx.push_with_visibility(
        n,
        src,
        Kind::Method,
        qname,
        "default_scope".to_string(),
        is_public,
    );
    ctx.out.edges.push(Edge {
        kind: EdgeKind::Defines,
        src: class_id.to_string(),
        dst: id,
        confidence: 1.0,
    });
}
