//! Rails 7 field macros (`encrypts`, `normalizes`), framework role
//! classification (`class_framework_role`), mailbox routing, and mailer
//! route emission.

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

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

/// `encrypts :ssn` (Rails 7) / `normalizes :email, with: …` (Rails 7).
/// Emit a Method-kind Symbol with the macro name in `doc`.
pub(crate) fn handle_field_macro(
    ctx: &mut Ctx<'_>,
    n: Node<'_>,
    src: &str,
    macro_name: &str,
    scope: &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 arg.kind() != "simple_symbol" {
            continue;
        }
        let field = strip_quotes(text(arg, src)).to_string();
        let name = format!("{macro_name}_{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!("@{macro_name} {field}"),
            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,
        });
        return;
    }
}

/// Decide a "framework role" tag from a class superclass name. Stamped into
/// the class symbol's `doc` so consumers can filter `@job`, `@mailer`,
/// `@record`, `@controller` classes.
pub(crate) fn class_framework_role(superclass: &str) -> Option<&'static str> {
    let s = superclass.trim_start_matches('<').trim();
    if s.ends_with("ApplicationJob") || s.contains("ActiveJob::Base") {
        Some("@job")
    } else if s.ends_with("ApplicationMailer") || s.contains("ActionMailer::Base") {
        Some("@mailer")
    } else if s.ends_with("ApplicationMailbox") || s.contains("ActionMailbox::Base") {
        Some("@mailbox")
    } else if s.ends_with("ApplicationRecord") || s.contains("ActiveRecord::Base") {
        Some("@record")
    } else if s.ends_with("Controller") || s.contains("ActionController") {
        Some("@controller")
    } else if s.ends_with("ApplicationCable::Channel") {
        Some("@channel")
    } else {
        None
    }
}

/// `routing /pattern/ => :handler_method` (Rails 6 ActionMailbox) → emit
/// a `MBOX <pattern>` Route with `handler=<class>#<method>` doc.
pub(crate) fn handle_mailbox_routing(ctx: &mut Ctx<'_>, n: Node<'_>, src: &str, class_name: &str) {
    let Some(args) = n.child_by_field_name("arguments") else {
        return;
    };
    let mut c = args.walk();
    let mut pattern: Option<String> = None;
    let mut handler: Option<String> = None;
    let process_pair = |kv: Node<'_>, pat: &mut Option<String>, hand: &mut Option<String>| {
        if kv.kind() != "pair" {
            return;
        }
        // `regex => symbol` form: key is the regex pattern, value is the
        // handler method symbol.
        let key = kv.child_by_field_name("key");
        let val = kv.child_by_field_name("value");
        if let Some(k) = key
            && matches!(k.kind(), "regex" | "string")
        {
            *pat = Some(text(k, src).to_string());
        }
        if let Some(v) = val
            && v.kind() == "simple_symbol"
        {
            *hand = Some(strip_quotes(text(v, src)).to_string());
        }
    };
    for arg in args.children(&mut c) {
        match arg.kind() {
            "pair" => process_pair(arg, &mut pattern, &mut handler),
            "hash" => {
                let mut hc = arg.walk();
                for kv in arg.children(&mut hc) {
                    process_pair(kv, &mut pattern, &mut handler);
                }
            }
            _ => {}
        }
    }
    if let (Some(p), Some(h)) = (pattern, handler) {
        let handler_qname = format!("{class_name}#{h}");
        // Re-use push_route via the super-module's routes helper.
        crate::framework::rails::handlers::routes::push_route(
            ctx, n, "MBOX", p.trim(), &handler_qname,
        );
    }
}

/// Within a class extending ApplicationMailer, every public `def x` becomes
/// a Mailer "route" — emit `MAIL <class>#<method>` Route Symbols.
pub(crate) fn emit_mailer_routes(ctx: &mut Ctx<'_>, body: Node<'_>, src: &str, class_name: &str) {
    let mut c = body.walk();
    for stmt in body.children(&mut c) {
        if stmt.kind() != "method" {
            continue;
        }
        let Some(action) = name_of(stmt, src) else {
            continue;
        };
        let path = format!("/{class_name}/{action}");
        let handler = format!("{class_name}#{action}");
        crate::framework::rails::handlers::routes::push_route(
            ctx, stmt, "MAIL", &path, &handler,
        );
    }
}

pub(crate) fn emit_implicit_controller_routes(
    ctx: &mut Ctx<'_>,
    body: Node<'_>,
    src: &str,
    class_name: &str,
) {
    crate::framework::rails::handlers::routes::emit_implicit_controller_routes(
        ctx, body, src, class_name,
    )
}
