fn main() {
    println!("cargo::rustc-check-cfg=cfg(cbindgen)");
    println!("cargo::rustc-check-cfg=cfg(has_unwind)");

    // Link the C standard library for intrinsics (memcpy, memset, strlen, etc.)
    // These are generated by the compiler for no_std code and need libc implementations.
    println!("cargo::rustc-link-lib=c");

    // For macOS, suppress the undefined symbol error for _rust_eh_personality
    // by allowing undefined symbols in the dylib. The C side won't use unwinding anyway.
    #[cfg(target_os = "macos")]
    {
        println!("cargo::rustc-link-arg=-undefined");
        println!("cargo::rustc-link-arg=dynamic_lookup");
    }

    // Read CARGO_CFG_PANIC to detect if we are building with unwinding (e.g. during cargo test)
    if let Ok(panic_strat) = std::env::var("CARGO_CFG_PANIC")
        && panic_strat == "unwind"
    {
        println!("cargo::rustc-cfg=has_unwind");
    }

    // ── MBUS_MAX_TCP_CLIENTS ──────────────────────────────────────────────────
    //
    // Controls how many TCP client slots are pre-allocated in the static pool.
    // Default: 1. Valid range: [1, 127].
    // IDs 0x00..=0x7E index the TCP pool (MSB = 0).
    println!("cargo::rerun-if-env-changed=MBUS_MAX_TCP_CLIENTS");

    let max_tcp: usize = match std::env::var("MBUS_MAX_TCP_CLIENTS") {
        Ok(val) => {
            let n: usize = val.parse().unwrap_or_else(|_| {
                panic!("MBUS_MAX_TCP_CLIENTS must be a valid integer, got: \"{val}\"")
            });
            if n == 0 {
                panic!("MBUS_MAX_TCP_CLIENTS must be >= 1, got: 0");
            }
            if n > 127 {
                panic!("MBUS_MAX_TCP_CLIENTS must be <= 127 (MSB=0 range), got: {n}");
            }
            n
        }
        Err(_) => 1, // default
    };

    // ── MBUS_MAX_SERIAL_CLIENTS ───────────────────────────────────────────────
    //
    // Controls how many Serial client slots are pre-allocated in the static pool.
    // Default: 1. Valid range: [1, 126].
    // IDs 0x80..=0xFD index the Serial pool (MSB = 1). 0xFF = INVALID_ID.
    println!("cargo::rerun-if-env-changed=MBUS_MAX_SERIAL_CLIENTS");

    let max_serial: usize = match std::env::var("MBUS_MAX_SERIAL_CLIENTS") {
        Ok(val) => {
            let n: usize = val.parse().unwrap_or_else(|_| {
                panic!("MBUS_MAX_SERIAL_CLIENTS must be a valid integer, got: \"{val}\"")
            });
            if n == 0 {
                panic!("MBUS_MAX_SERIAL_CLIENTS must be >= 1, got: 0");
            }
            if n > 126 {
                panic!(
                    "MBUS_MAX_SERIAL_CLIENTS must be <= 126 (MSB=1 range, 0xFF reserved), got: {n}"
                );
            }
            n
        }
        Err(_) => 1, // default
    };

    let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set");
    let config_path = format!("{out_dir}/pool_config.rs");
    std::fs::write(
        &config_path,
        format!(
            "/// Maximum number of TCP client slots (set via `MBUS_MAX_TCP_CLIENTS` env var, default 1).\n\
             pub(crate) const MAX_TCP_CLIENTS: usize = {max_tcp};\n\
             /// Maximum number of Serial client slots (set via `MBUS_MAX_SERIAL_CLIENTS` env var, default 1).\n\
             pub(crate) const MAX_SERIAL_CLIENTS: usize = {max_serial};\n"
        ),
    )
    .expect("failed to write pool_config.rs");

    // ── cbindgen ──────────────────────────────────────────────────────────────

    // Only run cbindgen when the `c` feature is enabled.
    if std::env::var("CARGO_FEATURE_C").is_err() {
        return;
    }

    let crate_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
    let include_dir = format!("{crate_dir}/include");
    std::fs::create_dir_all(&include_dir).expect("failed to create include/ directory");

    let output_file = format!("{include_dir}/mbus_ffi.h");

    let config_path = format!("{crate_dir}/cbindgen.toml");
    let config = cbindgen::Config::from_file(&config_path)
        .unwrap_or_else(|err| panic!("failed to parse {config_path}: {err}"));

    cbindgen::Builder::new()
        .with_crate(&crate_dir)
        .with_language(cbindgen::Language::C)
        .with_define("feature", "c", "MBUS_FEATURE_C")
        .with_define("feature", "coils", "MBUS_FEATURE_COILS")
        .with_define("feature", "registers", "MBUS_FEATURE_REGISTERS")
        .with_define("feature", "discrete-inputs", "MBUS_FEATURE_DISCRETE_INPUTS")
        .with_define("feature", "fifo", "MBUS_FEATURE_FIFO")
        .with_define("feature", "file-record", "MBUS_FEATURE_FILE_RECORD")
        .with_define("feature", "diagnostics", "MBUS_FEATURE_DIAGNOSTICS")
        .with_config(config)
        .generate()
        .expect("cbindgen failed to generate C header")
        .write_to_file(&output_file);
}
