//! Build-time stencil extraction for facet-hash's opt-in native backend.

use std::{env, fs, path::PathBuf};

#[cfg(feature = "jit")]
use copypatch::extract::{Stencil, compile_object, extract_stencil, nightly_available};
#[cfg(feature = "jit")]
use std::path::Path;

const EMPTY_STENCILS: &str = "\
pub const SCALAR: &[u8] = &[];\n\
pub const SCALAR_CONT: &[usize] = &[];\n\
pub const SCALAR_RUN: &[u8] = &[];\n\
pub const SCALAR_RUN_CONT: &[usize] = &[];\n\
pub const DONE: &[u8] = &[];\n";

#[cfg(feature = "jit")]
const SYMBOLS: &[&str] = &[
    "facet_hash_stencil_scalar",
    "facet_hash_stencil_scalar_run",
    "facet_hash_stencil_done",
];

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

    let out = PathBuf::from(env::var("OUT_DIR").unwrap());
    let generated = out.join("stencils.rs");

    #[cfg(feature = "jit")]
    {
        let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
        let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
        if target_os == "macos" && target_arch == "aarch64" {
            emit_arm64_macos(&out, &generated);
            return;
        }
    }

    fs::write(&generated, EMPTY_STENCILS).unwrap();
}

#[cfg(feature = "jit")]
fn emit_arm64_macos(out: &Path, generated: &Path) {
    println!("cargo:rerun-if-changed=stencils/stencils.rs");
    println!("cargo:rerun-if-changed=build.rs");

    let target = env::var("TARGET").expect("TARGET set by cargo");
    let obj = out.join("stencils.o");
    let src = Path::new("stencils/stencils.rs");

    let tailcall =
        nightly_available() && compile_object("rustc", &["+nightly"], src, &obj, &target, true);
    if tailcall {
        println!("cargo:rustc-cfg=facet_hash_jit_tailcall");
    } else {
        let rustc = env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
        assert!(
            compile_object(&rustc, &[], src, &obj, &target, false),
            "rustc failed to compile facet-hash stencils"
        );
    }

    let bytes = fs::read(&obj).unwrap();
    let get = |symbol: &str| extract_stencil(&bytes, SYMBOLS, symbol, "facet_hash_cont");
    let scalar = get("facet_hash_stencil_scalar");
    let scalar_run = get("facet_hash_stencil_scalar_run");
    let done = get("facet_hash_stencil_done");

    let mode = if tailcall {
        "tail-call (nightly become)"
    } else {
        "call (stable)"
    };
    let mut out = String::new();
    out.push_str(&format!(
        "// Generated by build.rs from stencils/stencils.rs (rustc --emit=obj, {mode}).\n"
    ));
    emit(
        &mut out,
        "SCALAR",
        "Hash one scalar field, then continue.",
        &scalar,
    );
    emit_cont(&mut out, "SCALAR_CONT", "SCALAR", &scalar);
    emit(
        &mut out,
        "SCALAR_RUN",
        "Hash a grouped scalar field run, then continue.",
        &scalar_run,
    );
    emit_cont(&mut out, "SCALAR_RUN_CONT", "SCALAR_RUN", &scalar_run);
    emit(&mut out, "DONE", "Terminal hash stencil.", &done);

    fs::write(generated, out).unwrap();
}

#[cfg(feature = "jit")]
fn emit(out: &mut String, name: &str, doc: &str, stencil: &Stencil) {
    out.push_str(&format!(
        "/// {doc}\npub const {name}: &[u8] = &{:?};\n",
        stencil.bytes
    ));
}

#[cfg(feature = "jit")]
fn emit_cont(out: &mut String, name: &str, of: &str, stencil: &Stencil) {
    out.push_str(&format!(
        "/// Byte offsets within `{of}` of the continuation `BRANCH26` relocations to patch.\n\
         pub const {name}: &[usize] = &{:?};\n",
        stencil.cont_relocs
    ));
}
