Skip to content
use crate::wasm::model::Table;
use crate::wasm::wire;

unsafe extern "C" {
    fn ag_grid_count() -> usize;
    fn ag_grid_label(index: usize, out_ptr: *mut u8, out_cap: usize) -> usize;
    fn ag_grid_schema(index: usize, out_ptr: *mut u8, out_cap: usize) -> usize;
    fn ag_grid_inject(index: usize, ptr: *const u8, len: usize) -> usize;
}

/// A single AG Grid table discovered on the host page.
pub struct Grid {
    /// Human-readable description (id/index, column and row counts).
    pub label: String,
    /// Row-model label (`client-side` / `server-side` / ...) for the UI badge.
    pub row_model: String,
    /// Observed row count, used as the default generation size in the UI.
    pub default_count: usize,
}

/// Scan the host page for AG Grid tables.
///
/// The DOM scanning lives on the JS side (see `ag_grid_count` / `ag_grid_label`
/// in `template.js`); this module just pulls the results across the FFI
/// boundary into owned Rust values.
pub fn find() -> Vec<Grid> {
    let count = unsafe { ag_grid_count() };
    (0..count)
        .map(|index| {
            let (label, row_model, default_count) = split_label(label(index));
            Grid {
                label,
                row_model,
                default_count,
            }
        })
        .collect()
}

/// Read and infer the schema of grid `index` by sampling it through the grid
/// API (DOM/JS side), returning an owned `Table`.
pub fn read_table(index: usize) -> Table {
    let bytes = wire::pull(|ptr, cap| unsafe { ag_grid_schema(index, ptr, cap) });
    let text = String::from_utf8_lossy(&bytes).into_owned();
    Table::from_schema(&text)
}

/// Inject a generated-row payload into grid `index`. Returns whether the JS
/// side accepted it (grid + API found).
pub fn inject(index: usize, payload: &str) -> bool {
    let ok = unsafe { ag_grid_inject(index, payload.as_ptr(), payload.len()) };
    ok != 0
}

/// Read the label for the grid at `index` out of wasm memory.
///
/// The JS side packs `label\trowModel\trowCount` so we can show a badge and a
/// sensible default generation size without extra FFI calls.
fn label(index: usize) -> String {
    let mut buf = vec![0u8; 512];
    let len = unsafe { ag_grid_label(index, buf.as_mut_ptr(), buf.len()) };
    buf.truncate(len.min(buf.len()));
    String::from_utf8_lossy(&buf).into_owned()
}

/// Split the `label\trowModel\trowCount` triple the JS side packs into one
/// string. Missing trailing fields fall back to sane defaults.
fn split_label(raw: String) -> (String, String, usize) {
    let mut parts = raw.splitn(3, '\t');
    let label = parts.next().unwrap_or("").to_string();
    let model = parts.next().unwrap_or("client-side").to_string();
    let count = parts.next().and_then(|c| c.parse().ok()).unwrap_or(100);
    (label, model, count)
}