#[cfg(feature = "wamr")]
fn build_wamr() {
    use bindgen::callbacks::ParseCallbacks;
    const WAMR_ZIP: &str =
        "https://github.com/bytecodealliance/wasm-micro-runtime/archive/refs/tags/WAMR-2.2.0.zip";
    const ZIP_NAME: &str = "wasm-micro-runtime-WAMR-2.2.0";

    use cmake::Config;
    use std::{
        env,
        path::PathBuf,
        sync::{LazyLock, Mutex},
    };

    let crate_root = env::var("OUT_DIR").unwrap();

    // Read target os from cargo env
    // Transform from cargo value to valid wasm-micro-runtime os
    let target_os = match env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() {
        "linux" => "linux",
        "windows" => "windows",
        "macos" => "darwin",
        "freebsd" => "freebsd",
        "android" => "android",
        "ios" => "ios",
        other => panic!("Unsupported CARGO_CFG_TARGET_OS: {other}"),
    };

    // Read target arch from cargo env
    // Transform from cargo value to valid wasm-micro-runtime WAMR_BUILD_TARGET
    let target_arch = match env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_str() {
        "x86" => "X86_32",
        "x86_64" => "X86_64",
        "arm" => "ARM",
        "aarch64" => "AARCH64",
        "mips" => "MIPS",
        "powerpc" => "POWERPC",
        "powerpc64" => "POWERPC64",
        other => panic!("Unsupported CARGO_CFG_TARGET_ARCH: {other}"),
    };

    // Cleanup tmp data from prior builds
    let wamr_dir = PathBuf::from(&crate_root).join("third_party/wamr");
    if !wamr_dir.exists() {
        let zip_dir = PathBuf::from(&crate_root).join("third_party");
        let _ = std::fs::remove_dir_all(&wamr_dir);
        let _ = std::fs::remove_dir_all(&zip_dir);

        // Fetch & extract wasm-micro-runtime source
        let zip_data = ureq::get(WAMR_ZIP)
            .call()
            .expect("failed to download wamr")
            .body_mut()
            .with_config()
            .limit(50 * 1024 * 1024) // 50MB
            .read_to_vec()
            .expect("failed to download wamr");
        std::fs::create_dir_all(&zip_dir)
            .expect("Failed to create temporary zip extraction directory");
        zip::read::ZipArchive::new(std::io::Cursor::new(zip_data))
            .expect("failed to open wamr zip file")
            .extract(&zip_dir)
            .expect("failed to extract wamr zip file");
        let _ = std::fs::remove_dir_all(&wamr_dir);
        std::fs::rename(zip_dir.join(ZIP_NAME), &wamr_dir)
            .unwrap_or_else(|e| panic!("failed to rename wamr dir: {zip_dir:?} due to: {e:?}"));
    } else {
        println!("cargo::rerun-if-changed={}", wamr_dir.display());
    }

    let wamr_platform_dir = wamr_dir.join("product-mini/platforms").join(target_os);
    let mut dst = Config::new(wamr_platform_dir.as_path());

    dst.always_configure(true)
        .generator("Ninja")
        .no_build_target(true)
        .define(
            "CMAKE_BUILD_TYPE",
            if cfg!(debug_assertions) {
                "RelWithDebInfo"
            } else {
                "Release"
            },
        )
        .define("CMAKE_POLICY_VERSION_MINIMUM", "3.5")
        .define("WAMR_BUILD_AOT", "0")
        //.define("WAMR_BUILD_TAIL_CALL", "1")
        //.define("WAMR_BUILD_DUMP_CALL_STACK", "1")
        // .define("WAMR_BUILD_CUSTOM_NAME_SECTION", "1")
        // .define("WAMR_BUILD_LOAD_CUSTOM_SECTION", "1")
        .define("WAMR_BUILD_BULK_MEMORY", "1")
        .define("WAMR_BUILD_REF_TYPES", "1")
        .define("WAMR_BUILD_SIMD", "1")
        .define("WAMR_BUILD_FAST_INTERP", "1")
        .define("WAMR_BUILD_LIB_PTHREAD", "1")
        .define("WAMR_BUILD_LIB_WASI_THREADS", "0")
        .define("WAMR_BUILD_LIBC_WASI", "0")
        .define("WAMR_BUILD_LIBC_BUILTIN", "0")
        .define("WAMR_BUILD_SHARED_MEMORY", "1")
        .define("WAMR_BUILD_MULTI_MODULE", "1")
        .define("WAMR_DISABLE_HW_BOUND_CHECK", "1")
        .define("WAMR_BUILD_TARGET", target_arch);

    if target_os == "windows" {
        dst.define("CMAKE_CXX_COMPILER", "cl.exe");
        dst.define("CMAKE_C_COMPILER", "cl.exe");
        dst.define("CMAKE_LINKER_TYPE", "MSVC");
        dst.define("WAMR_BUILD_PLATFORM", "windows");
        dst.define("WAMR_BUILD_LIBC_UVWASI", "0");
    }

    //if target_os == "ios" {
    //    // XXX: Hacky
    //    //
    //    // Compiling wamr targeting `aarch64-apple-ios` results in
    //    //
    //    // ```
    //    //  clang: error: unsupported option '-mfloat-abi=' for target 'aarch64-apple-ios'
    //    // ```
    //    // So, here, we simply remove that setting.
    //    //
    //    // See: https://github.com/bytecodealliance/wasm-micro-runtime/pull/3889
    //    let mut lines = vec![];
    //    let cmake_file_path = wamr_platform_dir.join("CMakeLists.txt");
    //    for line in std::fs::read_to_string(&cmake_file_path).unwrap().lines() {
    //        if !line.contains("-mfloat-abi=hard") {
    //            lines.push(line.to_string())
    //        }
    //    }
    //    std::fs::write(cmake_file_path, lines.join("\n")).unwrap();
    //}

    let dst = dst.build();

    // Check output of `cargo build --verbose`, should see something like:
    // -L native=/path/runng/target/debug/build/runng-sys-abc1234/out
    // That contains output from cmake

    // Rename the symbols created from wamr.
    static WAMR_RENAMED: LazyLock<Mutex<Vec<(String, String)>>> =
        LazyLock::new(|| Mutex::new(Vec::new()));

    #[derive(Debug)]
    struct WamrRenamer {}
    impl ParseCallbacks for WamrRenamer {
        /// This function will run for every extern variable and function. The returned value determines
        /// the link name in the bindings.
        fn generated_link_name_override(
            &self,
            item_info: bindgen::callbacks::ItemInfo<'_>,
        ) -> Option<String> {
            if item_info.name.starts_with("wasm") {
                let new_name = format!("wamr_{}", item_info.name);
                WAMR_RENAMED
                    .lock()
                    .expect("cannot lock WAMR_RENAMED")
                    .push((item_info.name.to_string(), new_name.clone()));

                Some(new_name)
            } else {
                None
            }
        }
    }

    let bindings = bindgen::Builder::default()
        .header(
            wamr_dir
                .join("core/iwasm/include/wasm_c_api.h")
                .to_str()
                .unwrap(),
        )
        .derive_default(true)
        .derive_debug(true)
        .parse_callbacks(Box::new(WamrRenamer {}))
        .generate()
        .expect("Unable to generate bindings");
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    let bindings_path = out_path.join("wamr_bindings.rs");
    bindings
        .write_to_file(&bindings_path)
        .expect("Couldn't write bindings");

    let objcopy_names = ["llvm-objcopy", "objcopy", "gobjcopy"];

    let mut objcopy = None;
    for n in objcopy_names {
        if which::which(n).is_ok() {
            objcopy = Some(n);
            break;
        }
    }

    if objcopy.is_none() {
        panic!(
            "No program akin to `objcopy` found\nI searched for these programs in your path: {}",
            objcopy_names.join(", ")
        );
    }

    let objcopy = objcopy.unwrap();

    let syms: Vec<String> = WAMR_RENAMED.lock().expect("cannot lock WAMR_RENAMED")
            .iter()
            .map(|(old, new)|
                // A bit hacky: we need a way to figure out if we're going to target a Mach-O
                // library or an ELF one to take care of the "_" in front of symbols.
            {
                if cfg!(any(target_os = "macos", target_os = "ios")) {
                    format!("--redefine-sym=_{old}={new}")
                } else {
                    format!("--redefine-sym={old}={new}")
                }
            })
            .collect();
    let output = std::process::Command::new(objcopy)
        .args(syms)
        .arg(dst.join("build").join("libvmlib.a").display().to_string())
        .arg(dst.join("build").join("libwamr.a").display().to_string())
        .output()
        .unwrap();

    if !output.status.success() {
        panic!(
            "{objcopy} failed with error code {}: {}",
            output.status,
            String::from_utf8(output.stderr).unwrap()
        );
    }

    println!(
        "cargo:rustc-link-search=native={}",
        dst.join("build").display()
    );
    println!("cargo:rustc-link-lib=static=wamr");
}

#[cfg(feature = "v8")]
fn build_v8() {
    use bindgen::callbacks::ParseCallbacks;
    use std::{
        env,
        path::PathBuf,
        sync::{LazyLock, Mutex},
    };

    let url = match (
        env::var("CARGO_CFG_TARGET_OS").unwrap().as_str(),
        env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_str(),
        env::var("CARGO_CFG_TARGET_ENV")
            .unwrap_or_default()
            .as_str(),
    ) {
        ("macos", "aarch64", _) => {
            "https://github.com/wasmerio/wee8-custom-builds/releases/download/11.8/wee8-darwin-aarch64.tar.xz"
        }
        ("macos", "x86_64", _) => {
            "https://github.com/wasmerio/wee8-custom-builds/releases/download/11.8/wee8-darwin-amd64.tar.xz"
        }
        ("linux", "x86_64", "gnu") => {
            "https://github.com/wasmerio/wee8-custom-builds/releases/download/11.8/wee8-linux-amd64.tar.xz"
        }
        ("linux", "x86_64", "musl") => {
            "https://github.com/wasmerio/wee8-custom-builds/releases/download/11.8/wee8-linux-musl-amd64.tar.xz"
        }
        ("android", "aarch64", _) => {
            "https://github.com/wasmerio/wee8-custom-builds/releases/download/11.8/wee8-android-arm64.tar.xz"
        }
        // Not supported in 6.0.0-alpha1
        //("windows", "x86_64", _) => "https://github.com/wasmerio/wee8-custom-builds/releases/download/11.7-custom1/wee8-windows-amd64.tar.xz",
        (os, arch, _) => panic!("target os + arch combination not supported: {os}, {arch}"),
    };

    let out_dir = env::var("OUT_DIR").unwrap();
    let crate_root = env::var("CARGO_MANIFEST_DIR").unwrap();
    let v8_header_path = PathBuf::from(&crate_root).join("third-party").join("wee8");

    let tar_data = ureq::get(url)
        .call()
        .expect("failed to download v8")
        .body_mut()
        .with_config()
        .limit(50 * 1024 * 1024) // 50MB
        .read_to_vec()
        .expect("failed to download v8 lib");

    let tar = xz::read::XzDecoder::new(tar_data.as_slice());
    let mut archive = tar::Archive::new(tar);

    for entry in archive.entries().unwrap() {
        eprintln!("entry: {:?}", entry.unwrap().path());
    }

    let tar = xz::read::XzDecoder::new(tar_data.as_slice());
    let mut archive = tar::Archive::new(tar);

    archive.unpack(out_dir.clone()).unwrap();
    println!("cargo:rustc-link-search=native={out_dir}");

    if cfg!(any(target_os = "linux",)) {
        println!("cargo:rustc-link-lib=stdc++");
    } else if cfg!(target_os = "windows") {
        println!("cargo:rustc-link-lib=winmm");
        println!("cargo:rustc-link-lib=dbghelp");
        println!("cargo:rustc-link-lib=shlwapi");
    } else {
        println!("cargo:rustc-link-lib=c++");
    }

    // Rename the symbols created from wee8.
    static WEE8_RENAMED: LazyLock<Mutex<Vec<(String, String)>>> =
        LazyLock::new(|| Mutex::new(Vec::new()));

    #[derive(Debug)]
    struct Wee8Renamer {}
    impl ParseCallbacks for Wee8Renamer {
        /// This function will run for every extern variable and function. The returned value determines
        /// the link name in the bindings.
        fn generated_link_name_override(
            &self,
            item_info: bindgen::callbacks::ItemInfo<'_>,
        ) -> Option<String> {
            if item_info.name.starts_with("wasm") {
                let new_name = format!("wee8_{}", item_info.name);
                WEE8_RENAMED
                    .lock()
                    .expect("cannot lock WEE8_RENAMED")
                    .push((item_info.name.to_string(), new_name.clone()));
                Some(new_name)
            } else {
                None
            }
        }
    }

    let header_path = v8_header_path.join("wasm.h");
    let mut args = vec![];
    if cfg!(target_os = "macos") {
        args.push("-I/usr/local/include");
        args.push("-I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1");
        args.push("-I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include");
        args.push("-I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include");
        args.push("-I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks");
    }
    let bindings = bindgen::Builder::default()
        .header(header_path.display().to_string())
        .clang_args(args)
        .derive_default(true)
        .derive_debug(true)
        .parse_callbacks(Box::new(Wee8Renamer {}))
        .generate()
        .expect("Unable to generate bindings for `v8`!");

    let out_path = PathBuf::from(out_dir);

    bindings
        .write_to_file(out_path.join("v8_bindings.rs"))
        .expect("Couldn't write bindings");

    let objcopy_names = ["llvm-objcopy", "objcopy", "gobjcopy"];

    let mut objcopy = None;
    for n in objcopy_names {
        if which::which(n).is_ok() {
            objcopy = Some(n);
            break;
        }
    }

    if objcopy.is_none() {
        panic!(
            "No program akin to `objcopy` found\nI searched for these programs in your path: {}",
            objcopy_names.join(", ")
        );
    }

    let objcopy = objcopy.unwrap();

    let syms: Vec<String> = WEE8_RENAMED.lock()
                        .expect("cannot lock WEE8_RENAMED")
            .iter()
            .map(|(old, new)|
                // A bit hacky: we need a way to figure out if we're going to target a Mach-O
                // library or an ELF one to take care of the "_" in front of symbols.
            {
                if cfg!(any(target_os = "macos", target_os = "ios")) {
                    format!("--redefine-sym=_{old}={new}")
                } else {
                    format!("--redefine-sym={old}={new}")
                }
            })
            .collect();
    let output = dbg!(
        std::process::Command::new(objcopy)
            .args(syms)
            .arg(out_path.join("obj").join("libwee8.a").display().to_string())
            .arg(out_path.join("libwee8prefixed.a").display().to_string())
    )
    .output()
    .unwrap();

    if !output.status.success() {
        panic!(
            "{objcopy} failed with error code {}: {}",
            output.status,
            String::from_utf8(output.stderr).unwrap()
        );
    }

    println!("cargo:rustc-link-lib=static=wee8prefixed");
}

#[cfg(feature = "wasmi")]
fn build_wasmi() {
    use bindgen::callbacks::ParseCallbacks;
    use std::{env, path::PathBuf};

    #[derive(Debug)]
    struct WasmiRenamer {}

    impl ParseCallbacks for WasmiRenamer {
        /// This function will run for every extern variable and function. The returned value determines
        /// the link name in the bindings.
        fn generated_link_name_override(
            &self,
            item_info: bindgen::callbacks::ItemInfo<'_>,
        ) -> Option<String> {
            if item_info.name.starts_with("wasm") {
                let new_name = if cfg!(any(target_os = "macos", target_os = "ios")) {
                    format!("_wasmi_{}", item_info.name)
                } else {
                    format!("wasmi_{}", item_info.name)
                };

                Some(new_name)
            } else {
                None
            }
        }
    }

    let bindings = bindgen::Builder::default()
        .header(
            PathBuf::from(std::env::var("DEP_WASMI_C_API_INCLUDE").unwrap())
                .join("wasm.h")
                .to_string_lossy(),
        )
        .derive_default(true)
        .derive_debug(true)
        .parse_callbacks(Box::new(WasmiRenamer {}))
        .generate()
        .expect("Unable to generate bindings for `wasmi`!");
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    let bindings_path = out_path.join("wasmi_bindings.rs");
    bindings
        .write_to_file(&bindings_path)
        .expect("Couldn't write bindings");

    let original =
        std::fs::read_to_string(&bindings_path).expect("Failed to read generated wasmi bindings");
    let mut patched = String::with_capacity(original.len());
    for line in original.lines() {
        let trimmed = line.trim_start();
        let indent_len = line.len() - trimmed.len();
        let indent = &line[..indent_len];
        if trimmed.starts_with("extern \"")
            && trimmed.ends_with('{')
            && !trimmed.starts_with("unsafe ")
        {
            patched.push_str(indent);
            patched.push_str("unsafe ");
            patched.push_str(trimmed);
        } else {
            patched.push_str(line);
        }
        patched.push('\n');
    }
    std::fs::write(&bindings_path, patched)
        .expect("Failed to post-process wasmi bindings for Rust 2024");
}
#[allow(unused)]
fn main() {
    #[cfg(feature = "wamr")]
    build_wamr();

    #[cfg(feature = "v8")]
    build_v8();

    #[cfg(feature = "wasmi")]
    build_wasmi();
}
