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

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

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

const EMPTY_STENCILS: &str = "\
pub const ROOT_OBJECT_START: &[u8] = &[];\n\
pub const ROOT_OBJECT_START_CONT: &[usize] = &[];\n\
pub const ROOT_ARRAY_START: &[u8] = &[];\n\
pub const ROOT_ARRAY_START_CONT: &[usize] = &[];\n";

#[cfg(feature = "jit")]
const SYMBOLS: &[&str] = &[
    "facet_json_stencil_root_object_start",
    "facet_json_stencil_root_array_start",
];

fn main() {
    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")
            || (target_os == "linux" && target_arch == "x86_64")
        {
            emit_native_stencils(&out, &generated);
            return;
        }
    }

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

#[cfg(feature = "jit")]
fn emit_native_stencils(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 rustc = env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
    assert!(
        compile_object(&rustc, &[], src, &obj, &target, false),
        "rustc failed to compile facet-json stencils"
    );

    let bytes = fs::read(&obj).unwrap();
    let get = |symbol: &str| extract_stencil(&bytes, SYMBOLS, symbol, "facet_json_cont");
    let root_object_start = get("facet_json_stencil_root_object_start");
    let root_array_start = get("facet_json_stencil_root_array_start");

    let mode = "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,
        "ROOT_OBJECT_START",
        "Consume the root object-start byte into the native JSON cursor.",
        &root_object_start,
    );
    emit_cont(
        &mut out,
        "ROOT_OBJECT_START_CONT",
        "ROOT_OBJECT_START",
        &root_object_start,
    );
    emit(
        &mut out,
        "ROOT_ARRAY_START",
        "Consume the root array-start byte into the native JSON cursor.",
        &root_array_start,
    );
    emit_cont(
        &mut out,
        "ROOT_ARRAY_START_CONT",
        "ROOT_ARRAY_START",
        &root_array_start,
    );
    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 relocations to patch.\n\
         pub const {name}: &[usize] = &{:?};\n",
        stencil.cont_relocs
    ));
}
