use crate::boundary_ir::{BoundaryLayout, BoundaryModule};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LayoutProbes {
    pub rust: String,
    pub zig: String,
}

pub fn emit_layout_probes(module: &BoundaryModule) -> Result<LayoutProbes, String> {
    if module.module.is_empty() {
        return Err("module id is empty".to_string());
    }
    if module.layouts.is_empty() {
        return Err("no layouts to probe".to_string());
    }

    Ok(LayoutProbes {
        rust: emit_rust_probes(module)?,
        zig: emit_zig_probes(module)?,
    })
}

fn emit_rust_probes(module: &BoundaryModule) -> Result<String, String> {
    let mut out = String::new();
    out.push_str("#![allow(dead_code)]\n");
    out.push_str("use std::mem::{align_of, offset_of, size_of};\n\n");
    emit_rust_abi_types(&mut out);

    for layout in &module.layouts {
        emit_rust_layout(&mut out, layout)?;
    }

    Ok(out)
}

fn emit_rust_abi_types(out: &mut String) {
    out.push_str("#[repr(C)]\nstruct InSliceU8 {\n    ptr: *const u8,\n    len: u64,\n}\n\n");
    out.push_str("#[repr(C)]\nstruct InBufU8 {\n    ptr: *mut u8,\n    len: u64,\n    cap: u64,\n    allocator_id: u64,\n}\n\n");
    out.push_str("#[repr(C)]\nstruct InBorrowToken {\n    arena_id: u64,\n    generation: u64,\n    start: u64,\n    len: u64,\n    flags: u64,\n}\n\n");
    out.push_str("#[repr(C)]\nstruct InArenaHandle {\n    id: u64,\n    generation: u64,\n}\n\n");
}

fn emit_rust_layout(out: &mut String, layout: &BoundaryLayout) -> Result<(), String> {
    if layout.name.is_empty() {
        return Err("layout name is empty".to_string());
    }

    let repr = super::repr_attribute(layout.repr.as_ref());
    out.push_str(&format!("#[repr({repr})]\nstruct {} {{\n", layout.name));
    for field in &layout.fields {
        let rust_type = super::rust_type_name(&field.typ);
        out.push_str(&format!("    {}: {},\n", field.name, rust_type));
    }
    out.push_str("}\n\n");

    out.push_str("const _: () = {\n");
    out.push_str(&format!(
        "    assert!(size_of::<{}>() == {});\n",
        layout.name, layout.size
    ));
    out.push_str(&format!(
        "    assert!(align_of::<{}>() == {});\n",
        layout.name, layout.align
    ));
    for field in &layout.fields {
        out.push_str(&format!(
            "    assert!(offset_of!({}, {}) == {});\n",
            layout.name, field.name, field.offset
        ));
    }
    out.push_str("};\n\n");
    Ok(())
}

fn emit_zig_probes(module: &BoundaryModule) -> Result<String, String> {
    let mut out = String::new();
    emit_zig_abi_types(&mut out);

    for layout in &module.layouts {
        emit_zig_layout(&mut out, layout)?;
    }

    Ok(out)
}

fn emit_zig_abi_types(out: &mut String) {
    out.push_str("const InSliceU8 = extern struct {\n    ptr: [*]const u8,\n    len: u64,\n};\n\n");
    out.push_str("const InBufU8 = extern struct {\n    ptr: [*]u8,\n    len: u64,\n    cap: u64,\n    allocator_id: u64,\n};\n\n");
    out.push_str("const InBorrowToken = extern struct {\n    arena_id: u64,\n    generation: u64,\n    start: u64,\n    len: u64,\n    flags: u64,\n};\n\n");
    out.push_str(
        "const InArenaHandle = extern struct {\n    id: u64,\n    generation: u64,\n};\n\n",
    );
}

fn emit_zig_layout(out: &mut String, layout: &BoundaryLayout) -> Result<(), String> {
    if layout.name.is_empty() {
        return Err("layout name is empty".to_string());
    }

    out.push_str(&format!("const {} = extern struct {{\n", layout.name));
    for field in &layout.fields {
        let zig_type = super::zig_type_name(&field.typ);
        out.push_str(&format!("    {}: {},\n", field.name, zig_type));
    }
    out.push_str("};\n\n");

    out.push_str("comptime {\n");
    out.push_str(&format!(
        "    if (@sizeOf({}) != {}) @compileError(\"{} size mismatch\");\n",
        layout.name, layout.size, layout.name
    ));
    out.push_str(&format!(
        "    if (@alignOf({}) != {}) @compileError(\"{} align mismatch\");\n",
        layout.name, layout.align, layout.name
    ));
    for field in &layout.fields {
        out.push_str(&format!(
            "    if (@offsetOf({}, .{}) != {}) @compileError(\"{} field {} offset mismatch\");\n",
            layout.name, field.name, field.offset, layout.name, field.name
        ));
    }
    out.push_str("}\n\n");
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::boundary_emit::sample_module;

    #[test]
    fn probes_cover_rust_and_zig() {
        let module = sample_module();
        let probes = emit_layout_probes(&module).expect("probes");
        assert!(probes.rust.contains("assert!(size_of::<Person>() == 24)"));
        assert!(probes.zig.contains("@sizeOf(Person) != 24"));
    }

    #[test]
    fn rejects_empty_layout_set() {
        let mut module = sample_module();
        module.layouts.clear();
        let err = emit_layout_probes(&module).expect_err("expected error");
        assert!(err.contains("no layouts"));
    }
}
