// Stage the kernel image + init-oci binary into OUT_DIR so
// `include_bytes!(concat!(env!("OUT_DIR"), "/...")` in src/lib.rs
// finds them. Three sources, in priority order:
//
//   1. Env-var override:
//        SUPERMACHINE_KERNEL_PATH   — file path to a kernel image
//        SUPERMACHINE_INIT_OCI_PATH — file path to an init-oci binary
//      For users with a custom kernel build. The override points at
//      the *uncompressed* artifact.
//
//   2. xz-compressed assets next to this Cargo.toml: `kernel.xz` and
//      `init-oci.xz`. This is what ships in the published .crate.
//      We pre-compress because the raw 30 MiB kernel doesn't fit
//      under crates.io's 10 MiB upload cap; xz -9 gets the kernel
//      down to ~7 MiB, comfortably under.
//
//      Maintainers re-baking the kernel run `xz -k -9` on the new
//      `kernel` / `init-oci` outputs and commit the .xz files. Raw
//      kernel/init-oci files are .gitignore'd to avoid accidental
//      shadowing.
//
// We shell out to `xz -d` for decompression. xz is universally
// available on macOS 11+ and every mainstream Linux distro; this
// avoids pulling an LZMA build-dep into every consumer.

use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

fn main() {
    println!("cargo:rerun-if-env-changed=SUPERMACHINE_KERNEL_PATH");
    println!("cargo:rerun-if-env-changed=SUPERMACHINE_INIT_OCI_PATH");
    println!("cargo:rerun-if-env-changed=SUPERMACHINE_AGENT_PATH");
    println!("cargo:rerun-if-env-changed=SUPERMACHINE_SMPARK_PATH");

    let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").expect("OUT_DIR set by cargo"));
    stage(
        "kernel",
        "SUPERMACHINE_KERNEL_PATH",
        &out_dir.join("kernel"),
    );
    stage(
        "init-oci",
        "SUPERMACHINE_INIT_OCI_PATH",
        &out_dir.join("init-oci"),
    );
    stage(
        "supermachine-agent",
        "SUPERMACHINE_AGENT_PATH",
        &out_dir.join("supermachine-agent"),
    );
    stage(
        "smpark.ko",
        "SUPERMACHINE_SMPARK_PATH",
        &out_dir.join("smpark.ko"),
    );
}

/// Resolve one asset (`kernel` or `init-oci`) to `dest` in OUT_DIR.
/// First checks the env-var override (uncompressed file). Falls
/// back to `<name>.xz` next to Cargo.toml, decompressed via `xz -d`.
fn stage(name: &str, env_var: &str, dest: &Path) {
    println!("cargo:rerun-if-changed={name}.xz");

    // (1) Env override — uncompressed file path.
    if let Some(p) = std::env::var_os(env_var) {
        let p = PathBuf::from(p);
        if !p.is_file() {
            fail(&format!(
                "{env_var}={} but the file does not exist",
                p.display()
            ));
        }
        copy_or_fail(&p, dest);
        return;
    }

    // (2) Bundled .xz file next to Cargo.toml.
    let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let xz_path = manifest_dir.join(format!("{name}.xz"));
    if !xz_path.is_file() {
        fail(&format!(
            "{name}.xz not found at {}.\n\
             This crate ships compressed kernel + init-oci assets in the\n\
             published .crate. If you're building from a fresh git\n\
             checkout, run the kernel-build pipeline (see\n\
             kernel-build/scripts/) and `xz -k -9` the outputs into\n\
             this directory; or set {env_var} to a local file.",
            xz_path.display()
        ));
    }
    decompress_xz(&xz_path, dest);
}

fn copy_or_fail(src: &Path, dest: &Path) {
    if let Some(parent) = dest.parent() {
        let _ = fs::create_dir_all(parent);
    }
    if let Err(e) = fs::copy(src, dest) {
        fail(&format!(
            "failed to copy {} -> {}: {e}",
            src.display(),
            dest.display()
        ));
    }
}

/// `xz -d -c <src> > <dest>` — shells out so we don't have to pull
/// an LZMA build-dependency. xz is on macOS 11+ by default and every
/// mainstream Linux distro.
fn decompress_xz(src: &Path, dest: &Path) {
    if let Some(parent) = dest.parent() {
        let _ = fs::create_dir_all(parent);
    }
    let dest_file = match fs::File::create(dest) {
        Ok(f) => f,
        Err(e) => fail(&format!("failed to create {}: {e}", dest.display())),
    };
    let status = Command::new("xz")
        .arg("-d")
        .arg("-c")
        .arg(src)
        .stdout(dest_file)
        .status();
    match status {
        Ok(s) if s.success() => {}
        Ok(s) => {
            let _ = fs::remove_file(dest);
            fail(&format!(
                "`xz -d -c {}` exited with status {s}\n\
                 (xz is required to decompress bundled kernel assets;\n\
                 install via your package manager: `brew install xz`,\n\
                 `apt install xz-utils`, `dnf install xz`)",
                src.display()
            ));
        }
        Err(e) => fail(&format!(
            "failed to spawn xz: {e}\n\
             xz is required to decompress bundled kernel assets;\n\
             install via your package manager: `brew install xz`,\n\
             `apt install xz-utils`, `dnf install xz`"
        )),
    }
}

fn fail(msg: &str) -> ! {
    eprintln!("supermachine-kernel build.rs: {msg}");
    std::process::exit(1);
}
