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)
}