///|
extern "js" fn webgpu_measure_text(
  text : String,
  family : String,
  size : Double,
  weight : Int,
) -> Array[Double] =
  #| (text, family, size, weight) => {
  #|   const cvs = document.createElement('canvas');
  #|   const ctx = cvs.getContext('2d');
  #|   const fontSize = Number.isFinite(size) && size > 0 ? size : 16;
  #|   const fontFamily = family && family.length > 0 ? family : 'Arial';
  #|   const fontWeight = Math.max(1, Math.min(1000, Number(weight) || 400));
  #|   ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
  #|   const m = ctx.measureText(text ?? '');
  #|   const width = Math.max(0, Number(m.width) || 0);
  #|   const ascent = Math.max(0, Number(m.actualBoundingBoxAscent) || fontSize * 0.8);
  #|   const descent = Math.max(0, Number(m.actualBoundingBoxDescent) || fontSize * 0.2);
  #|   const height = Math.max(1, Math.ceil(ascent + descent));
  #|   return [width, height];
  #| }

///|
extern "js" fn webgpu_begin_frame(
  clear_r : Double,
  clear_g : Double,
  clear_b : Double,
  clear_a : Double,
) -> Unit =
  #| (r, g, b, a) => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   if (rt) {
  #|     rt.beginFrame(r, g, b, a);
  #|   }
  #| }

///|
extern "js" fn webgpu_end_frame() -> Unit =
  #| () => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   if (rt) {
  #|     rt.endFrame();
  #|   }
  #| }

///|
extern "js" fn webgpu_capture_frame_png_bytes() -> Array[Int] =
  #| () => {
  #|   try {
  #|     const rt = globalThis.__selene_webgpu_runtime;
  #|     const canvas = rt?.canvas;
  #|     if (!canvas || !canvas.toDataURL) return [];
  #|     const dataUrl = canvas.toDataURL("image/png");
  #|     const comma = dataUrl.indexOf(",");
  #|     if (comma < 0) return [];
  #|     const base64 = dataUrl.slice(comma + 1);
  #|     const binary = atob(base64);
  #|     const out = new Array(binary.length);
  #|     for (let i = 0; i < binary.length; i += 1) {
  #|       out[i] = binary.charCodeAt(i) & 0xff;
  #|     }
  #|     return out;
  #|   } catch (_err) {
  #|     return [];
  #|   }
  #| }

///|
extern "js" fn webgpu_preload_img(path : String) -> Unit =
  #| (path) => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   if (rt) {
  #|     rt.ensureImage(path);
  #|   }
  #| }

///|
extern "js" fn webgpu_begin_2d_pass(
  x : Double,
  y : Double,
  width : Double,
  height : Double,
  load_op : Int,
  clear_r : Double,
  clear_g : Double,
  clear_b : Double,
  clear_a : Double,
  target_name : String,
) -> Unit =
  #| (x, y, width, height, loadOp, clearR, clearG, clearB, clearA, targetName) => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   if (rt) {
  #|     rt.beginPass2d(x, y, width, height, loadOp, [clearR, clearG, clearB, clearA], targetName);
  #|   }
  #| }

///|
extern "js" fn webgpu_end_2d_pass() -> Unit =
  #| () => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   if (rt) {
  #|     rt.endPass2d();
  #|   }
  #| }

///|
extern "js" fn webgpu_push_clip_rect(
  x : Double,
  y : Double,
  width : Double,
  height : Double,
) -> Unit =
  #| (x, y, width, height) => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   if (rt) {
  #|     rt.pushClip2d(x, y, width, height);
  #|   }
  #| }