fn main() {
    use std::io::Write;

    fn gen_pthread_mutex(out_file: &str) -> std::io::Result<()> {
        println!("cargo:rerun-if-env-changed=CARGO_FEATURE_MULTITASK");
        println!("cargo:rerun-if-env-changed=CARGO_FEATURE_SMP");
        let has_multitask = std::env::var_os("CARGO_FEATURE_MULTITASK").is_some();
        let has_smp = std::env::var_os("CARGO_FEATURE_SMP").is_some();
        let (mutex_size, mutex_init) = if has_multitask {
            if has_smp {
                // Current 64-bit SMP targets keep an extra padding word in
                // `ax_sync::Mutex<()>` for the pthread path.
                //
                // core::mem::transmute::<_, [usize; 6]>(ax_sync::Mutex::new(()))
                (6, "{0, 0, 8, 0, 0, 0}")
            } else {
                // The non-SMP multitask path uses the compact 5-word mutex
                // layout.
                //
                // core::mem::transmute::<_, [usize; 5]>(ax_sync::Mutex::new(()))
                (5, "{0, 8, 0, 0, 0}")
            }
        } else {
            (1, "{0}")
        };

        let mut output = Vec::new();
        writeln!(
            output,
            "// Generated by ax-posix-api/build.rs — DO NOT edit!"
        )?;
        writeln!(
            output,
            r#"
typedef struct {{
    long __l[{mutex_size}];
}} pthread_mutex_t;

#define PTHREAD_MUTEX_INITIALIZER {{ .__l = {mutex_init} }}
"#
        )?;
        std::fs::write(out_file, output)?;
        Ok(())
    }

    fn gen_c_to_rust_bindings(in_file: &str, out_file: &str) {
        println!("cargo:rerun-if-changed={in_file}");

        let allow_types = [
            "stat",
            "size_t",
            "ssize_t",
            "off_t",
            "mode_t",
            "sock.*",
            "fd_set",
            "timeval",
            "pthread_t",
            "pthread_attr_t",
            "pthread_mutex_t",
            "pthread_mutexattr_t",
            "epoll_event",
            "iovec",
            "clockid_t",
            "rlimit",
            "aibuf",
        ];

        let allow_vars = [
            "CLOCK_.*",
            "O_.*",
            "AF_.*",
            "SOCK_.*",
            "IPPROTO_.*",
            "FD_.*",
            "F_.*",
            "_SC_.*",
            "EPOLL_CTL_.*",
            "EPOLL.*",
            "RLIMIT_.*",
            "EAI_.*",
            "MAXADDRS",
        ];

        #[derive(Debug)]
        struct MyCallbacks;

        impl bindgen::callbacks::ParseCallbacks for MyCallbacks {
            fn include_file(&self, fname: &str) {
                if !fname.contains("ax_pthread_mutex.h") {
                    println!("cargo:rerun-if-changed={fname}");
                }
            }
        }

        let target = std::env::var("TARGET").unwrap();
        let mut builder = bindgen::Builder::default()
            .header(in_file)
            .clang_arg("-I./../../ulib/axlibc/include")
            .parse_callbacks(Box::new(MyCallbacks))
            .derive_default(true)
            .size_t_is_usize(false)
            .use_core();

        // Remove the "-softfloat" suffix for some targets.
        if let Some(llvm_target) = target.strip_suffix("-softfloat") {
            builder = builder.clang_arg(format!("--target={llvm_target}"));
        }

        for ty in allow_types {
            builder = builder.allowlist_type(ty);
        }

        for var in allow_vars {
            builder = builder.allowlist_var(var);
        }

        builder
            .generate()
            .expect("Unable to generate C→Rust bindings")
            .write_to_file(out_file)
            .expect("Couldn't write bindings!");
    }

    let axlibc_include = std::path::Path::new("../../ulib/axlibc/include");
    if axlibc_include.exists() {
        gen_pthread_mutex(axlibc_include.join("ax_pthread_mutex.h").to_str().unwrap()).unwrap();
        gen_c_to_rust_bindings("ctypes.h", "src/ctypes_gen.rs");
    } else {
        // During `cargo publish` verification, the external headers are not
        // available. Use the pre-generated ctypes_gen.rs shipped in the crate.
        eprintln!(
            "cargo:warning=axlibc include directory not found, skipping ctypes_gen.rs regeneration"
        );
    }
}
