Skip to content
//! wasm entry points called from `template.js`, plus the host imports they need
//! for saving and reloading config files.

use core::sync::atomic::{AtomicU64, Ordering};

unsafe extern "C" {
    /// Trigger a browser download of `name` containing `data`.
    fn download_text(name_ptr: *const u8, name_len: usize, data_ptr: *const u8, data_len: usize);
    /// Pull the most recently loaded config file's text into wasm memory.
    fn config_text(out_ptr: *mut u8, out_cap: usize) -> usize;
}

/// Process-global seed counter; wasm has no clock, so we advance this each call
/// to vary generated data between clicks.
static SEED: AtomicU64 = AtomicU64::new(0xDEAD_BEEF_CAFE_F00D);

fn next_seed() -> u64 {
    SEED.fetch_add(0x9E37_79B9_7F4A_7C15, Ordering::Relaxed)
}

#[cfg(target_arch = "wasm32")]
#[unsafe(no_mangle)]
pub extern "C" fn run() {
    use crate::wasm::{utils, view};
    utils::log("Rust WASM: Auto-start function executed.");
    view::render();
}

/// Re-scan the page and re-render the UI. Exposed so the rescan button in the
/// view can trigger a refresh.
#[cfg(target_arch = "wasm32")]
#[unsafe(no_mangle)]
pub extern "C" fn rescan() {
    crate::wasm::view::render();
}

/// Sample grid `index`, generate `count` similar synthetic rows (or as many as
/// were observed when `count == 0`), and inject them back into the grid.
#[cfg(target_arch = "wasm32")]
#[unsafe(no_mangle)]
pub extern "C" fn generate(index: usize, count: usize) {
    use crate::wasm::{grid, rng::Rng, utils, view};
    let table = grid::read_table(index);
    let n = if count == 0 {
        table.row_count.max(1)
    } else {
        count
    };
    let mut rng = Rng::new(next_seed());
    let payload = table.generate(n, &mut rng);
    let ok = grid::inject(index, &payload);
    utils::log(format!(
        "hecatomb: generated {} {} rows for grid {} ({})",
        n,
        table.row_model.label(),
        index,
        if ok { "injected" } else { "no grid api" }
    ));
    view::render();
}

/// Sample grid `index` and download its schema as a per-table config file,
/// recording `count` as the default generation size.
#[cfg(target_arch = "wasm32")]
#[unsafe(no_mangle)]
pub extern "C" fn save_config(index: usize, count: usize) {
    use crate::wasm::{config, grid};
    let mut table = grid::read_table(index);
    table.gen_count = if count == 0 {
        table.row_count.max(1)
    } else {
        count
    };
    let text = config::to_text(&table);
    let stem = if table.id.is_empty() {
        format!("grid-{}", index)
    } else {
        table.id.clone()
    };
    let name = format!("{}.heca.txt", stem);
    unsafe {
        download_text(name.as_ptr(), name.len(), text.as_ptr(), text.len());
    }
}

/// Apply a previously loaded config file (pulled from the JS side) to grid
/// `index`: parse it, generate the configured number of rows, and inject.
#[cfg(target_arch = "wasm32")]
#[unsafe(no_mangle)]
pub extern "C" fn apply_config(index: usize) {
    use crate::wasm::{config, grid, rng::Rng, utils, view, wire};
    let bytes = wire::pull(|ptr, cap| unsafe { config_text(ptr, cap) });
    let text = String::from_utf8_lossy(&bytes).into_owned();
    if text.trim().is_empty() {
        return;
    }
    let table = config::from_text(&text);
    let mut rng = Rng::new(next_seed());
    let payload = table.generate(table.gen_count.max(1), &mut rng);
    let ok = grid::inject(index, &payload);
    utils::log(format!(
        "hecatomb: applied config ({} cols, {} rows) to grid {} ({})",
        table.columns.len(),
        table.gen_count,
        index,
        if ok { "injected" } else { "no grid api" }
    ));
    view::render();
}