//! Codegen intercept registry — one dispatch table for every
//! mono-symbol whose body the codegen supplies as LLVM IR
//! directly (replacing the .ail placeholder body that the
//! intercept's home module ships for round-trip stability).
//!
//! Each entry carries:
//!  - `name`                — the mono symbol the dispatch keys on
//!  - `expected_params`     — LLVM-IR param types as text
//!  - `expected_ret`        — LLVM-IR return type as text
//!  - `wants_alwaysinline`  — whether the dispatch site should
//!    attach the `alwaysinline` attribute
//!    to the emitted fn (was a separate
//!    hardcoded name-list in the predicate
//!    `intercept_emit_wants_alwaysinline`
//!    before raw-buf.1)
//!  - `emit`                — the per-arm IR-emission body, run
//!    against `&mut Emitter` after the
//!    sig check
//!
//! Adding a new intercept = adding one row to `INTERCEPTS`. The
//! dispatch site (`try_emit_primitive_instance_body` in
//! `crates/ailang-codegen/src/lib.rs`) and the alwaysinline
//! predicate both consult this single table.

use crate::{CodegenError, Emitter, Result};

pub(crate) struct Intercept {
    pub name: &'static str,
    pub expected_params: &'static [&'static str],
    pub expected_ret: &'static str,
    pub wants_alwaysinline: bool,
    pub emit: fn(&mut Emitter<'_>) -> Result<()>,
}

pub(crate) static INTERCEPTS: &[Intercept] = &[
    Intercept {
        name: "eq__Str",
        expected_params: &["ptr", "ptr"],
        expected_ret: "i1",
        wants_alwaysinline: true,
        emit: emit_eq_str,
    },
    Intercept {
        name: "compare__Int",
        expected_params: &["i64", "i64"],
        expected_ret: "ptr",
        wants_alwaysinline: true,
        emit: emit_compare_int,
    },
    Intercept {
        name: "compare__Bool",
        expected_params: &["i1", "i1"],
        expected_ret: "ptr",
        wants_alwaysinline: true,
        emit: emit_compare_bool,
    },
    Intercept {
        name: "compare__Str",
        expected_params: &["ptr", "ptr"],
        expected_ret: "ptr",
        wants_alwaysinline: true,
        emit: emit_compare_str,
    },
    Intercept {
        name: "eq__Int",
        expected_params: &["i64", "i64"],
        expected_ret: "i1",
        wants_alwaysinline: true,
        emit: emit_eq_int,
    },
    Intercept {
        name: "eq__Bool",
        expected_params: &["i1", "i1"],
        expected_ret: "i1",
        wants_alwaysinline: true,
        emit: emit_eq_bool,
    },
    Intercept {
        name: "eq__Unit",
        // Unit lowers to `i8` per the codegen type-mapping (see lib.rs
        // //! header). The legacy arm checked only `ret_ty != "i1"` and
        // ignored params; this entry restores that semantics under the
        // centralised `check_sig` by naming the actual param shape the
        // call site delivers. The emit fn discards the locals.
        expected_params: &["i8", "i8"],
        expected_ret: "i1",
        wants_alwaysinline: true,
        emit: emit_eq_unit,
    },
    Intercept {
        name: "float_eq",
        expected_params: &["double", "double"],
        expected_ret: "i1",
        wants_alwaysinline: true,
        emit: emit_float_eq,
    },
    Intercept {
        name: "float_ne",
        expected_params: &["double", "double"],
        expected_ret: "i1",
        wants_alwaysinline: true,
        emit: emit_float_ne,
    },
    Intercept {
        name: "float_lt",
        expected_params: &["double", "double"],
        expected_ret: "i1",
        wants_alwaysinline: true,
        emit: emit_float_lt,
    },
    Intercept {
        name: "float_le",
        expected_params: &["double", "double"],
        expected_ret: "i1",
        wants_alwaysinline: true,
        emit: emit_float_le,
    },
    Intercept {
        name: "float_gt",
        expected_params: &["double", "double"],
        expected_ret: "i1",
        wants_alwaysinline: true,
        emit: emit_float_gt,
    },
    Intercept {
        name: "float_ge",
        expected_params: &["double", "double"],
        expected_ret: "i1",
        wants_alwaysinline: true,
        emit: emit_float_ge,
    },
    Intercept {
        name: "lt__Int",
        expected_params: &["i64", "i64"],
        expected_ret: "i1",
        wants_alwaysinline: true,
        emit: emit_lt_int,
    },
    Intercept {
        name: "le__Int",
        expected_params: &["i64", "i64"],
        expected_ret: "i1",
        wants_alwaysinline: true,
        emit: emit_le_int,
    },
    Intercept {
        name: "gt__Int",
        expected_params: &["i64", "i64"],
        expected_ret: "i1",
        wants_alwaysinline: true,
        emit: emit_gt_int,
    },
    Intercept {
        name: "ge__Int",
        expected_params: &["i64", "i64"],
        expected_ret: "i1",
        wants_alwaysinline: true,
        emit: emit_ge_int,
    },
    Intercept {
        name: "ne__Int",
        expected_params: &["i64", "i64"],
        expected_ret: "i1",
        wants_alwaysinline: true,
        emit: emit_ne_int,
    },
    // raw-buf.4: the 12 scope-qualified RawBuf ops. Symbols are
    // `RawBuf_{new,get,set,size}__{Int,Float,Bool}` (raw-buf.3
    // scope-qualified mono mangling). Slab layout:
    // `[ size:i64 @0 ][ elem_0 @8 ][ elem_1 @8+w ]…`; element widths
    // Int/Float = 8, Bool = 1. `own`/`borrow (con RawBuf T)` both
    // lower to `ptr`. The header is always i64, so `new`/`size` are
    // element-type-independent; `get`/`set` carry the element type.
    Intercept {
        name: "RawBuf_new__Int",
        expected_params: &["i64"],
        expected_ret: "ptr",
        wants_alwaysinline: false,
        emit: emit_rawbuf_new_int,
    },
    Intercept {
        name: "RawBuf_get__Int",
        expected_params: &["ptr", "i64"],
        expected_ret: "i64",
        wants_alwaysinline: false,
        emit: emit_rawbuf_get_int,
    },
    Intercept {
        name: "RawBuf_set__Int",
        expected_params: &["ptr", "i64", "i64"],
        expected_ret: "ptr",
        wants_alwaysinline: false,
        emit: emit_rawbuf_set_int,
    },
    Intercept {
        name: "RawBuf_size__Int",
        expected_params: &["ptr"],
        expected_ret: "i64",
        wants_alwaysinline: false,
        emit: emit_rawbuf_size_int,
    },
    Intercept {
        name: "RawBuf_new__Float",
        expected_params: &["i64"],
        expected_ret: "ptr",
        wants_alwaysinline: false,
        emit: emit_rawbuf_new_float,
    },
    Intercept {
        name: "RawBuf_get__Float",
        expected_params: &["ptr", "i64"],
        expected_ret: "double",
        wants_alwaysinline: false,
        emit: emit_rawbuf_get_float,
    },
    Intercept {
        name: "RawBuf_set__Float",
        expected_params: &["ptr", "i64", "double"],
        expected_ret: "ptr",
        wants_alwaysinline: false,
        emit: emit_rawbuf_set_float,
    },
    Intercept {
        name: "RawBuf_size__Float",
        expected_params: &["ptr"],
        expected_ret: "i64",
        wants_alwaysinline: false,
        emit: emit_rawbuf_size_float,
    },
    Intercept {
        name: "RawBuf_new__Bool",
        expected_params: &["i64"],
        expected_ret: "ptr",
        wants_alwaysinline: false,
        emit: emit_rawbuf_new_bool,
    },
    Intercept {
        name: "RawBuf_get__Bool",
        expected_params: &["ptr", "i64"],
        expected_ret: "i1",
        wants_alwaysinline: false,
        emit: emit_rawbuf_get_bool,
    },
    Intercept {
        name: "RawBuf_set__Bool",
        expected_params: &["ptr", "i64", "i1"],
        expected_ret: "ptr",
        wants_alwaysinline: false,
        emit: emit_rawbuf_set_bool,
    },
    Intercept {
        name: "RawBuf_size__Bool",
        expected_params: &["ptr"],
        expected_ret: "i64",
        wants_alwaysinline: false,
        emit: emit_rawbuf_size_bool,
    },
];

pub(crate) fn lookup(name: &str) -> Option<&'static Intercept> {
    INTERCEPTS.iter().find(|i| i.name == name)
}

pub(crate) fn check_sig(
    intercept: &Intercept,
    param_tys: &[String],
    ret_ty: &str,
) -> Result<()> {
    let params_match = param_tys.len() == intercept.expected_params.len()
        && param_tys
            .iter()
            .zip(intercept.expected_params.iter())
            .all(|(have, want)| have == want);
    if !params_match || ret_ty != intercept.expected_ret {
        return Err(CodegenError::Internal(format!(
            "{} body intercept: unexpected signature \
             ({:?}) -> {} (want {:?} -> {})",
            intercept.name,
            param_tys,
            ret_ty,
            intercept.expected_params,
            intercept.expected_ret,
        )));
    }
    Ok(())
}

// ---------------------------------------------------------------
// Per-intercept emit fns. Bodies lifted verbatim from the legacy
// `try_emit_primitive_instance_body` match arms in `lib.rs` (raw-buf.1
// migration). Per-arm sig-check prologues are removed because the
// dispatch shim runs `check_sig` first; trailing `Ok(true)` is
// replaced by `Ok(())` because the shim wraps the bool.
// ---------------------------------------------------------------

/// Emit the `getelementptr inbounds i8, ptr <src>, i64 8` that walks
/// an IR-Str pointer (which lands on the `len` field of the packed
/// slab) forward to its bytes pointer, returning the fresh SSA holding
/// the result. Shared by `emit_eq_str` / `emit_compare_str`. Because
/// `fresh_ssa` allocates sequentially, calling this twice in a row
/// yields the same `%vK`, `%vK+1` pair — and the same two GEP lines in
/// the same order — as the inlined form it replaces, so the emitted IR
/// is byte-identical.
fn emit_str_bytes_gep(emitter: &mut Emitter<'_>, src: &str) -> String {
    let bytes = emitter.fresh_ssa();
    emitter.body.push_str(&format!(
        "  {bytes} = getelementptr inbounds i8, ptr {src}, i64 8\n"
    ));
    bytes
}

pub(crate) fn emit_eq_str(emitter: &mut Emitter<'_>) -> Result<()> {
    // The two params are the two most recently pushed locals;
    // `emit_fn` populated `self.locals` from `f.params` before
    // dispatching here. Pull their SSA names without assuming
    // a specific surface-level binder name.
    let params = emitter.last_param_ssas(2);
    let a_ssa = params[0].clone();
    let b_ssa = params[1].clone();
    // IR-Str pointers now land on the
    // `len`-field of the packed-struct slab; @ail_str_eq's
    // strcmp-based body needs the bytes pointer 8 bytes
    // further on.
    let a_bytes = emit_str_bytes_gep(emitter, &a_ssa);
    let b_bytes = emit_str_bytes_gep(emitter, &b_ssa);
    let dst = emitter.fresh_ssa();
    emitter.body.push_str(&format!(
        "  {dst} = call zeroext i1 @ail_str_eq(ptr {a_bytes}, ptr {b_bytes})\n"
    ));
    emitter.body.push_str(&format!("  ret i1 {dst}\n"));
    emitter.body.push_str("}\n\n");
    emitter.block_terminated = true;
    Ok(())
}

pub(crate) fn emit_compare_int(emitter: &mut Emitter<'_>) -> Result<()> {
    let params = emitter.last_param_ssas(2);
    let a_ssa = params[0].clone();
    let b_ssa = params[1].clone();
    emitter.emit_compare_ladder(
        &format!("icmp slt i64 {a_ssa}, {b_ssa}"),
        &format!("icmp eq i64 {a_ssa}, {b_ssa}"),
    )?;
    Ok(())
}

pub(crate) fn emit_compare_bool(emitter: &mut Emitter<'_>) -> Result<()> {
    let params = emitter.last_param_ssas(2);
    let a_ssa = params[0].clone();
    let b_ssa = params[1].clone();
    emitter.emit_compare_ladder(
        &format!("icmp ult i1 {a_ssa}, {b_ssa}"),
        &format!("icmp eq i1 {a_ssa}, {b_ssa}"),
    )?;
    Ok(())
}

pub(crate) fn emit_compare_str(emitter: &mut Emitter<'_>) -> Result<()> {
    let params = emitter.last_param_ssas(2);
    let a_ssa = params[0].clone();
    let b_ssa = params[1].clone();
    // IR-Str pointers now land on the
    // `len`-field of the packed-struct slab; @ail_str_compare's
    // strcmp-based body needs the bytes pointer 8 bytes
    // further on.
    let a_bytes = emit_str_bytes_gep(emitter, &a_ssa);
    let b_bytes = emit_str_bytes_gep(emitter, &b_ssa);
    let cmp_res = emitter.fresh_ssa();
    emitter.body.push_str(&format!(
        "  {cmp_res} = call i32 @ail_str_compare(ptr {a_bytes}, ptr {b_bytes})\n"
    ));
    emitter.emit_compare_ladder(
        &format!("icmp slt i32 {cmp_res}, 0"),
        &format!("icmp eq i32 {cmp_res}, 0"),
    )?;
    Ok(())
}

pub(crate) fn emit_eq_int(emitter: &mut Emitter<'_>) -> Result<()> {
    let params = emitter.last_param_ssas(2);
    let a_ssa = params[0].clone();
    let b_ssa = params[1].clone();
    let r = emitter.fresh_ssa();
    emitter.body.push_str(&format!(
        "  {r} = icmp eq i64 {a_ssa}, {b_ssa}\n"
    ));
    emitter.body.push_str(&format!("  ret i1 {r}\n"));
    emitter.body.push_str("}\n\n");
    emitter.block_terminated = true;
    Ok(())
}

pub(crate) fn emit_eq_bool(emitter: &mut Emitter<'_>) -> Result<()> {
    let params = emitter.last_param_ssas(2);
    let a_ssa = params[0].clone();
    let b_ssa = params[1].clone();
    let r = emitter.fresh_ssa();
    emitter.body.push_str(&format!(
        "  {r} = icmp eq i1 {a_ssa}, {b_ssa}\n"
    ));
    emitter.body.push_str(&format!("  ret i1 {r}\n"));
    emitter.body.push_str("}\n\n");
    emitter.block_terminated = true;
    Ok(())
}

pub(crate) fn emit_eq_unit(emitter: &mut Emitter<'_>) -> Result<()> {
    // Unit is single-inhabitant: all values compare equal.
    // The fn signature still carries the operand slots; we
    // discard them and return true unconditionally.
    emitter.body.push_str("  ret i1 1\n");
    emitter.body.push_str("}\n\n");
    emitter.block_terminated = true;
    Ok(())
}

pub(crate) fn emit_float_eq(emitter: &mut Emitter<'_>) -> Result<()> {
    let params = emitter.last_param_ssas(2);
    let a_ssa = params[0].clone();
    let b_ssa = params[1].clone();
    let r = emitter.fresh_ssa();
    emitter.body.push_str(&format!(
        "  {r} = fcmp oeq double {a_ssa}, {b_ssa}\n"
    ));
    emitter.body.push_str(&format!("  ret i1 {r}\n"));
    emitter.body.push_str("}\n\n");
    emitter.block_terminated = true;
    Ok(())
}

pub(crate) fn emit_float_ne(emitter: &mut Emitter<'_>) -> Result<()> {
    // `fcmp une` ("unordered or not-equal"), not `one`,
    // mirroring float-semantics.md: NaN != NaN must be true.
    let params = emitter.last_param_ssas(2);
    let a_ssa = params[0].clone();
    let b_ssa = params[1].clone();
    let r = emitter.fresh_ssa();
    emitter.body.push_str(&format!(
        "  {r} = fcmp une double {a_ssa}, {b_ssa}\n"
    ));
    emitter.body.push_str(&format!("  ret i1 {r}\n"));
    emitter.body.push_str("}\n\n");
    emitter.block_terminated = true;
    Ok(())
}

pub(crate) fn emit_float_lt(emitter: &mut Emitter<'_>) -> Result<()> {
    let params = emitter.last_param_ssas(2);
    let a_ssa = params[0].clone();
    let b_ssa = params[1].clone();
    let r = emitter.fresh_ssa();
    emitter.body.push_str(&format!(
        "  {r} = fcmp olt double {a_ssa}, {b_ssa}\n"
    ));
    emitter.body.push_str(&format!("  ret i1 {r}\n"));
    emitter.body.push_str("}\n\n");
    emitter.block_terminated = true;
    Ok(())
}

pub(crate) fn emit_float_le(emitter: &mut Emitter<'_>) -> Result<()> {
    let params = emitter.last_param_ssas(2);
    let a_ssa = params[0].clone();
    let b_ssa = params[1].clone();
    let r = emitter.fresh_ssa();
    emitter.body.push_str(&format!(
        "  {r} = fcmp ole double {a_ssa}, {b_ssa}\n"
    ));
    emitter.body.push_str(&format!("  ret i1 {r}\n"));
    emitter.body.push_str("}\n\n");
    emitter.block_terminated = true;
    Ok(())
}

pub(crate) fn emit_float_gt(emitter: &mut Emitter<'_>) -> Result<()> {
    let params = emitter.last_param_ssas(2);
    let a_ssa = params[0].clone();
    let b_ssa = params[1].clone();
    let r = emitter.fresh_ssa();
    emitter.body.push_str(&format!(
        "  {r} = fcmp ogt double {a_ssa}, {b_ssa}\n"
    ));
    emitter.body.push_str(&format!("  ret i1 {r}\n"));
    emitter.body.push_str("}\n\n");
    emitter.block_terminated = true;
    Ok(())
}

pub(crate) fn emit_float_ge(emitter: &mut Emitter<'_>) -> Result<()> {
    let params = emitter.last_param_ssas(2);
    let a_ssa = params[0].clone();
    let b_ssa = params[1].clone();
    let r = emitter.fresh_ssa();
    emitter.body.push_str(&format!(
        "  {r} = fcmp oge double {a_ssa}, {b_ssa}\n"
    ));
    emitter.body.push_str(&format!("  ret i1 {r}\n"));
    emitter.body.push_str("}\n\n");
    emitter.block_terminated = true;
    Ok(())
}

// The five icmp-delegate emit fns wrap the existing
// `Emitter::emit_direct_int_icmp_intercept` helper, which returns
// `Result<bool>` (always `Ok(true)` on success); we discard the
// bool because the dispatch shim wraps it from `check_sig`.

pub(crate) fn emit_lt_int(emitter: &mut Emitter<'_>) -> Result<()> {
    emitter
        .emit_direct_int_icmp_intercept("lt__Int", "icmp slt i64", &["i64".into(), "i64".into()], "i1")
        .map(|_| ())
}

pub(crate) fn emit_le_int(emitter: &mut Emitter<'_>) -> Result<()> {
    emitter
        .emit_direct_int_icmp_intercept("le__Int", "icmp sle i64", &["i64".into(), "i64".into()], "i1")
        .map(|_| ())
}

pub(crate) fn emit_gt_int(emitter: &mut Emitter<'_>) -> Result<()> {
    emitter
        .emit_direct_int_icmp_intercept("gt__Int", "icmp sgt i64", &["i64".into(), "i64".into()], "i1")
        .map(|_| ())
}

pub(crate) fn emit_ge_int(emitter: &mut Emitter<'_>) -> Result<()> {
    emitter
        .emit_direct_int_icmp_intercept("ge__Int", "icmp sge i64", &["i64".into(), "i64".into()], "i1")
        .map(|_| ())
}

pub(crate) fn emit_ne_int(emitter: &mut Emitter<'_>) -> Result<()> {
    emitter
        .emit_direct_int_icmp_intercept("ne__Int", "icmp ne i64", &["i64".into(), "i64".into()], "i1")
        .map(|_| ())
}

// ---------------------------------------------------------------
// raw-buf.4: RawBuf op emits over an `@ailang_rc_alloc` slab.
// Slab layout `[ size:i64 @0 ][ elem_0 @8 ][ elem_1 @8+w ]…`. The
// rc-header is auto-prepended by `@ailang_rc_alloc` (it returns the
// payload ptr); the i64 size header lives at payload offset 0, the
// elements follow at offset 8. Element widths: Int/Float = 8, Bool
// = 1. `new`/`size` are byte-identical across element types (the
// header is always i64); `get`/`set` carry the element load/store
// type and offset width. Intercepts run only under `--alloc=rc`, so
// `@ailang_rc_alloc` is hardcoded (not `alloc.fn_name()`).
// ---------------------------------------------------------------

// Parameterised cores. The three element variants (Int, Float, Bool)
// differ in exactly two axes: the per-element byte width used in the
// index `mul` / capacity `mul` (Int=8, Float=8, Bool=1) and the LLVM
// load/store type used by `get`/`set` (Int=i64, Float=double, Bool=i1).
// `new`/`size` carry only the width axis (their slab header is always
// i64). Each core emits the IR for one operation; the public per-type
// wrappers below pass the right `(width, elem_ty)` pair, so the emitted
// IR for every variant is byte-identical to the pre-dedup form.

fn emit_rawbuf_new(emitter: &mut Emitter<'_>, elem_width: u64) -> Result<()> {
    let cap = emitter.last_param_ssas(1)[0].clone(); // i64 capacity
    let elems_bytes = emitter.fresh_ssa();
    let total = emitter.fresh_ssa();
    let slab = emitter.fresh_ssa();
    emitter
        .body
        .push_str(&format!("  {elems_bytes} = mul i64 {cap}, {elem_width}\n"));
    emitter.body.push_str(&format!("  {total} = add i64 {elems_bytes}, 8\n"));
    emitter
        .body
        .push_str(&format!("  {slab} = call ptr @ailang_rc_alloc(i64 {total})\n"));
    emitter.body.push_str(&format!("  store i64 {cap}, ptr {slab}\n"));
    emitter.body.push_str(&format!("  ret ptr {slab}\n"));
    emitter.body.push_str("}\n\n");
    emitter.block_terminated = true;
    Ok(())
}

fn emit_rawbuf_get(
    emitter: &mut Emitter<'_>,
    elem_width: u64,
    elem_ty: &str,
) -> Result<()> {
    let params = emitter.last_param_ssas(2);
    let b = params[0].clone();
    let i = params[1].clone();
    let off = emitter.fresh_ssa();
    let byteoff = emitter.fresh_ssa();
    let ptr = emitter.fresh_ssa();
    let v = emitter.fresh_ssa();
    emitter.body.push_str(&format!("  {off} = mul i64 {i}, {elem_width}\n"));
    emitter.body.push_str(&format!("  {byteoff} = add i64 {off}, 8\n"));
    emitter.body.push_str(&format!(
        "  {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n"
    ));
    emitter.body.push_str(&format!("  {v} = load {elem_ty}, ptr {ptr}\n"));
    emitter.body.push_str(&format!("  ret {elem_ty} {v}\n"));
    emitter.body.push_str("}\n\n");
    emitter.block_terminated = true;
    Ok(())
}

fn emit_rawbuf_set(
    emitter: &mut Emitter<'_>,
    elem_width: u64,
    elem_ty: &str,
) -> Result<()> {
    let params = emitter.last_param_ssas(3);
    let b = params[0].clone();
    let i = params[1].clone();
    let v = params[2].clone();
    let off = emitter.fresh_ssa();
    let byteoff = emitter.fresh_ssa();
    let ptr = emitter.fresh_ssa();
    emitter.body.push_str(&format!("  {off} = mul i64 {i}, {elem_width}\n"));
    emitter.body.push_str(&format!("  {byteoff} = add i64 {off}, 8\n"));
    emitter.body.push_str(&format!(
        "  {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n"
    ));
    emitter.body.push_str(&format!("  store {elem_ty} {v}, ptr {ptr}\n"));
    emitter.body.push_str(&format!("  ret ptr {b}\n"));
    emitter.body.push_str("}\n\n");
    emitter.block_terminated = true;
    Ok(())
}

fn emit_rawbuf_size(emitter: &mut Emitter<'_>) -> Result<()> {
    let b = emitter.last_param_ssas(1)[0].clone();
    let sz = emitter.fresh_ssa();
    emitter.body.push_str(&format!("  {sz} = load i64, ptr {b}\n"));
    emitter.body.push_str(&format!("  ret i64 {sz}\n"));
    emitter.body.push_str("}\n\n");
    emitter.block_terminated = true;
    Ok(())
}

// --- Int variants (element type i64, width 8) ---

pub(crate) fn emit_rawbuf_new_int(emitter: &mut Emitter<'_>) -> Result<()> {
    emit_rawbuf_new(emitter, 8)
}

pub(crate) fn emit_rawbuf_get_int(emitter: &mut Emitter<'_>) -> Result<()> {
    emit_rawbuf_get(emitter, 8, "i64")
}

pub(crate) fn emit_rawbuf_set_int(emitter: &mut Emitter<'_>) -> Result<()> {
    emit_rawbuf_set(emitter, 8, "i64")
}

pub(crate) fn emit_rawbuf_size_int(emitter: &mut Emitter<'_>) -> Result<()> {
    emit_rawbuf_size(emitter)
}

// --- Float variants (element type double, width 8) ---

pub(crate) fn emit_rawbuf_new_float(emitter: &mut Emitter<'_>) -> Result<()> {
    emit_rawbuf_new(emitter, 8)
}

pub(crate) fn emit_rawbuf_get_float(emitter: &mut Emitter<'_>) -> Result<()> {
    emit_rawbuf_get(emitter, 8, "double")
}

pub(crate) fn emit_rawbuf_set_float(emitter: &mut Emitter<'_>) -> Result<()> {
    emit_rawbuf_set(emitter, 8, "double")
}

pub(crate) fn emit_rawbuf_size_float(emitter: &mut Emitter<'_>) -> Result<()> {
    emit_rawbuf_size(emitter)
}

// --- Bool variants (element type i1, width 1) ---

pub(crate) fn emit_rawbuf_new_bool(emitter: &mut Emitter<'_>) -> Result<()> {
    emit_rawbuf_new(emitter, 1)
}

pub(crate) fn emit_rawbuf_get_bool(emitter: &mut Emitter<'_>) -> Result<()> {
    emit_rawbuf_get(emitter, 1, "i1")
}

pub(crate) fn emit_rawbuf_set_bool(emitter: &mut Emitter<'_>) -> Result<()> {
    emit_rawbuf_set(emitter, 1, "i1")
}

pub(crate) fn emit_rawbuf_size_bool(emitter: &mut Emitter<'_>) -> Result<()> {
    emit_rawbuf_size(emitter)
}

#[cfg(test)]
mod tests {
    use super::{lookup, INTERCEPTS};
    use ailang_check::mono::{mono_symbol, mono_symbol_n};
    use ailang_core::ast::{Def, FnDef, Module, Term, Type};
    use std::collections::BTreeSet;

    /// INTERCEPTS entries that intercept the monomorphised `__Int`
    /// specialisation of a polymorphic free fn carrying a REAL body
    /// (`ne = not (eq x y)`; `lt/le/gt/ge = match compare ...`). These
    /// are an optimisation class, not a compiler-supplied body — they
    /// legitimately have no `(intrinsic)` marker. Any change here is a
    /// deliberate registry-policy decision, not drift.
    const OPTIMISATION_ONLY: &[&str] =
        &["lt__Int", "le__Int", "gt__Int", "ge__Int", "ne__Int"];

    /// Collect the mangled name of every `(intrinsic)` marker reachable
    /// in the kernel-tier source modules (prelude + raw_buf — the
    /// only modules where an intrinsic body is legal today).
    fn workspace_intrinsic_markers() -> BTreeSet<String> {
        let mut markers = BTreeSet::new();
        for module in [
            ailang_surface::parse_prelude(),
            ailang_surface::parse_raw_buf(),
        ] {
            for def in &module.defs {
                match def {
                    // raw-buf.3: a polymorphic top-level (intrinsic) op
                    // type-scoped to a same-module TypeDef T (its
                    // signature mentions `(con T …)`) expands to one
                    // marker per element type in T's `param-in` set —
                    // the same `T_f__<elem>` strings the mono pass mints
                    // (mono::scoped_base + mono_symbol_n). One marker → N
                    // entries.
                    Def::Fn(f)
                        if matches!(f.body, Term::Intrinsic)
                            && matches!(f.ty, Type::Forall { .. }) =>
                    {
                        match scope_typedef_and_elems(f, &module) {
                            Some((tdef, elems)) => {
                                for elem in elems {
                                    markers.insert(mono_symbol_n(
                                        &format!("{tdef}_{}", f.name),
                                        std::slice::from_ref(&elem),
                                    ));
                                }
                            }
                            // a Forall intrinsic not scoped to a
                            // param-in TypeDef keeps the bare name (no
                            // such case ships today; future-proofing).
                            None => {
                                markers.insert(f.name.clone());
                            }
                        }
                    }
                    // Top-level intrinsic fn: name is already the symbol
                    // (float_eq, ...).
                    Def::Fn(f) if matches!(f.body, Term::Intrinsic) => {
                        markers.insert(f.name.clone());
                    }
                    // Instance method whose lambda body is intrinsic:
                    // the codegen symbol is mono_symbol(method, type).
                    Def::Instance(inst) => {
                        for m in &inst.methods {
                            if let Term::Lam { body, .. } = &m.body {
                                if matches!(**body, Term::Intrinsic) {
                                    markers.insert(mono_symbol(&m.name, &inst.type_));
                                }
                            }
                        }
                    }
                    _ => {}
                }
            }
        }
        markers
    }

    /// Bijection over the intrinsic-backed class:
    /// (A) every workspace intrinsic marker resolves to an INTERCEPTS entry;
    /// (B) every INTERCEPTS entry not on the optimisation-only allowlist
    ///     has a workspace intrinsic marker.
    #[test]
    fn intercepts_bijection_with_intrinsic_markers() {
        let markers = workspace_intrinsic_markers();
        let registry: BTreeSet<String> =
            INTERCEPTS.iter().map(|i| i.name.to_string()).collect();

        // (A) no intrinsic marker without a codegen intercept
        let orphan_markers: Vec<&String> =
            markers.iter().filter(|m| lookup(m).is_none()).collect();
        assert!(
            orphan_markers.is_empty(),
            "intrinsic markers with no INTERCEPTS entry: {orphan_markers:?}"
        );

        // (B) no non-allowlisted intercept without an intrinsic marker
        let orphan_entries: Vec<&String> = registry
            .iter()
            .filter(|n| !OPTIMISATION_ONLY.contains(&n.as_str()))
            .filter(|n| !markers.contains(*n))
            .collect();
        assert!(
            orphan_entries.is_empty(),
            "INTERCEPTS entries with no intrinsic marker (and not optimisation-only): {orphan_entries:?}"
        );

        // Guard: the allowlist names must actually be in the registry —
        // a stale allowlist entry (name removed from INTERCEPTS) is drift.
        let stale_allow: Vec<&&str> = OPTIMISATION_ONLY
            .iter()
            .filter(|n| lookup(n).is_none())
            .collect();
        assert!(
            stale_allow.is_empty(),
            "optimisation-only allowlist names not in INTERCEPTS: {stale_allow:?}"
        );
    }

    /// raw-buf.3: for a type-scoped polymorphic intrinsic fn `f`, return
    /// `(TypeDef-name, element-types)` where the TypeDef is the unique
    /// same-module `Def::Type` referenced via `(con T …)` in `f`'s
    /// signature and the element types are its `param-in` set expressed as
    /// `Type::Con` values (so `mono_symbol_n` produces the same suffix the
    /// mono pass mints). `None` if `f` references zero or several
    /// same-module TypeDefs.
    fn scope_typedef_and_elems(f: &FnDef, module: &Module) -> Option<(String, Vec<Type>)> {
        let typedef_names: BTreeSet<&str> = module
            .defs
            .iter()
            .filter_map(|d| match d {
                Def::Type(td) => Some(td.name.as_str()),
                _ => None,
            })
            .collect();
        // collect Con-heads referenced anywhere in f's type
        let mut referenced: BTreeSet<String> = BTreeSet::new();
        collect_con_heads(&f.ty, &mut referenced);
        let scoped: Vec<&String> = referenced
            .iter()
            .filter(|n| typedef_names.contains(n.as_str()))
            .collect();
        let tdef = match scoped.as_slice() {
            [one] => (*one).clone(),
            _ => return None,
        };
        let td = module.defs.iter().find_map(|d| match d {
            Def::Type(td) if td.name == tdef => Some(td),
            _ => None,
        })?;
        // param-in is keyed by the type var; take its allowed surface
        // names and build `Type::Con` element types. The element strings
        // (`Int`, `Float`) flow through `mono_symbol_n`'s
        // `primitive_surface_name` gate to the same `__Int` / `__Float`
        // suffixes the mono pass mints.
        let allowed = td.param_in.values().next()?; // single-var TypeDefs
        let elems: Vec<Type> = allowed
            .iter()
            .map(|s| Type::Con { name: s.clone(), args: vec![] })
            .collect();
        Some((tdef, elems))
    }

    /// raw-buf.3: push every `Type::Con` head name reachable in `ty`
    /// into `out`. `borrow` / `own` are `ParamMode` metadata on
    /// `Type::Fn`, not wrapper `Type` variants, so the only recursive
    /// shapes are `Forall.body`, `Fn.params`/`ret`, and `Con.args`.
    fn collect_con_heads(ty: &Type, out: &mut BTreeSet<String>) {
        match ty {
            Type::Con { name, args } => {
                out.insert(name.clone());
                for a in args {
                    collect_con_heads(a, out);
                }
            }
            Type::Fn { params, ret, .. } => {
                for p in params {
                    collect_con_heads(p, out);
                }
                collect_con_heads(ret, out);
            }
            Type::Forall { body, .. } => collect_con_heads(body, out),
            Type::Var { .. } => {}
        }
    }
}
