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

pub fn emit_c_header(module: &BoundaryModule) -> Result<String, String> {
    if module.module.is_empty() {
        return Err("module id is empty".to_string());
    }

    let guard = super::guard_token(&module.module);
    let mut out = String::new();
    out.push_str(&format!("#ifndef {guard}\n"));
    out.push_str(&format!("#define {guard}\n\n"));
    out.push_str("#include <stdint.h>\n");
    out.push_str("#include \"in_abi.h\"\n\n");
    out.push_str("#if defined(__GNUC__) || defined(__clang__)\n");
    out.push_str("#define IN_BOUNDARY_PACKED __attribute__((packed))\n");
    out.push_str("#else\n");
    out.push_str("#define IN_BOUNDARY_PACKED\n");
    out.push_str("#endif\n\n");

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

    out.push_str("\n#endif\n");
    Ok(out)
}

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

    if layout.kind != "struct" && layout.kind != "enum" {
        return Err(format!(
            "layout `{}` unsupported kind `{}`",
            layout.name, layout.kind
        ));
    }

    let packed = matches!(layout.repr, Some(BoundaryRepr::Packed));
    if packed {
        out.push_str("typedef IN_BOUNDARY_PACKED struct ");
    } else {
        out.push_str("typedef struct ");
    }
    out.push_str(&layout.name);
    out.push_str(" {\n");

    if layout.fields.is_empty() {
        out.push_str(&format!("    uint8_t _reserved[{}];\n", layout.size));
    } else {
        for field in &layout.fields {
            let c_type = super::c_type_name(&field.typ);
            out.push_str(&format!("    {} {};\n", c_type, field.name));
        }
    }

    out.push_str(&format!("}} {};\n\n", layout.name));
    Ok(())
}

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

    #[test]
    fn header_guard_uses_module_token() {
        let module = sample_module();
        let header = emit_c_header(&module).expect("header");
        assert!(header.contains("#ifndef IN_BOUNDARY_SAMPLE_PERSON_H"));
        assert!(header.contains("#define IN_BOUNDARY_SAMPLE_PERSON_H"));
    }
}
