/* advstatslab.jsx — AdvStatsTutor: the advanced (distribution) statistics workbench.
   Four modules: normal curve, binomial + normal approximation, t vs z, F distribution.
   Built on window.BenchKit (loaded first), so this file is just the FIGURES + their field
   computations. The math block below is COPIED VERBATIM from server/benches/stats-math.js
   (+ the advstats helpers) and each computeFields() mirrors server/benches/advstats.js
   simulate() EXACTLY, so client and server agree on every graded field. */
(function () {
  const { useState, useMemo, useEffect } = React;
  const K = window.BenchKit, C = K.C, SVG_BG = K.SVG_BG, fmt = K.fmt, clamp = K.clamp;
  const TaskStrip = K.TaskStrip, StatMeter = K.StatMeter;

  /* ════ numerics — byte-for-byte identical to server/benches/stats-math.js + advstats.js ════ */
  function normalCdf(z) {
    const t = 1 / (1 + 0.2316419 * Math.abs(z));
    const d = 0.3989422804014327 * Math.exp(-z * z / 2);
    const p = d * t * (0.31938153 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));
    return z >= 0 ? 1 - p : p;
  }
  function gammaln(x) {
    const g = [76.18009172947146, -86.50532032941677, 24.01409824083091, -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5];
    let y = x; let tmp = x + 5.5; tmp -= (x + 0.5) * Math.log(tmp);
    let ser = 1.000000000190015;
    for (let j = 0; j < 6; j++) ser += g[j] / ++y;
    return -tmp + Math.log(2.5066282746310005 * ser / x);
  }
  function betai(a, b, x) {
    if (x <= 0) return 0;
    if (x >= 1) return 1;
    const lnBeta = gammaln(a) + gammaln(b) - gammaln(a + b);
    const front = Math.exp(Math.log(x) * a + Math.log(1 - x) * b - lnBeta) / a;
    const TINY = 1e-30; let c = 1; let d = 1 - (a + b) * x / (a + 1);
    if (Math.abs(d) < TINY) d = TINY; d = 1 / d; let h = d;
    for (let m = 1; m <= 200; m++) {
      const m2 = 2 * m;
      let aa = m * (b - m) * x / ((a + m2 - 1) * (a + m2));
      d = 1 + aa * d; if (Math.abs(d) < TINY) d = TINY;
      c = 1 + aa / c; if (Math.abs(c) < TINY) c = TINY;
      d = 1 / d; h *= d * c;
      aa = -(a + m) * (a + b + m) * x / ((a + m2) * (a + m2 + 1));
      d = 1 + aa * d; if (Math.abs(d) < TINY) d = TINY;
      c = 1 + aa / c; if (Math.abs(c) < TINY) c = TINY;
      d = 1 / d; const del = d * c; h *= del;
      if (Math.abs(del - 1) < 3e-7) break;
    }
    const result = front * h;
    return result < 0.5 ? result : 1 - betai(b, a, 1 - x);
  }
  function fPValue(f, d1, d2) { if (!(f > 0)) return 1; return betai(d2 / 2, d1 / 2, d2 / (d2 + d1 * f)); }
  const r1 = (x) => Math.round(x * 10) / 10;
  const r2 = (x) => Math.round(x * 100) / 100;
  const r3 = (x) => Math.round(x * 1000) / 1000;
  function binomPmf(k, n, p) {
    if (k < 0 || k > n) return 0;
    if (p <= 0) return k === 0 ? 1 : 0;
    if (p >= 1) return k === n ? 1 : 0;
    const logc = gammaln(n + 1) - gammaln(k + 1) - gammaln(n - k + 1);
    return Math.exp(logc + k * Math.log(p) + (n - k) * Math.log(1 - p));
  }
  function binomCdf(k, n, p) { let s = 0; const kk = Math.min(Math.floor(k), n); for (let i = 0; i <= kk; i++) s += binomPmf(i, n, p); return Math.min(1, s); }
  function tUpperTail(c, nu) { if (c === 0) return 0.5; if (c < 0) return 1 - tUpperTail(-c, nu); return 0.5 * betai(nu / 2, 0.5, nu / (nu + c * c)); }
  function critUpper(target, fn, lo, hi) { for (let i = 0; i < 80; i++) { const m = (lo + hi) / 2; if (fn(m) > target) lo = m; else hi = m; } return (lo + hi) / 2; }
  const tStar = (nu) => critUpper(0.025, (c) => tUpperTail(c, nu), 0, 60);
  const fStar = (d1, d2) => critUpper(0.05, (c) => fPValue(c, d1, d2), 0, 200);
  const Z975 = 1.959963985;

  /* ════ field computations — EXACTLY the server's per-module simulate() ════ */
  function fieldsNormal(s) {
    const mu = s.mu, sigma = Math.max(0.1, s.sigma);
    const lo = Math.min(s.a, s.b), hi = Math.max(s.a, s.b);
    const zA = (lo - mu) / sigma, zB = (hi - mu) / sigma, area = normalCdf(zB) - normalCdf(zA);
    return { area: r3(area), zA: r2(zA), zB: r2(zB), rightTail: r3(1 - normalCdf(zB)), leftTail: r3(normalCdf(zA)), intervalWidth: r2(hi - lo), mu: r1(mu), sigma: r1(sigma) };
  }
  function fieldsBinom(s) {
    const n = Math.max(1, Math.round(s.n)), pr = Math.min(0.999, Math.max(0.001, s.p)), k = Math.max(0, Math.min(n, Math.round(s.k)));
    const mean = n * pr, sd = Math.sqrt(n * pr * (1 - pr)) || 1e-9, cdfK = binomCdf(k, n, pr), normalApprox = normalCdf((k + 0.5 - mean) / sd);
    return { mean: r2(mean), sd: r2(sd), pmfK: r3(binomPmf(k, n, pr)), cdfK: r3(cdfK), normalApprox: r3(normalApprox), approxGap: r3(Math.abs(cdfK - normalApprox)), normalOK: mean >= 10 && n * (1 - pr) >= 10, n, p: r2(pr) };
  }
  function fieldsTdist(s) {
    const nu = Math.max(1, Math.round(s.nu)), c = Math.max(0, s.tc), tU = tUpperTail(c, nu), zU = 1 - normalCdf(c), ts = tStar(nu);
    return { tUpper: r3(tU), zUpper: r3(zU), tStar: r2(ts), excessOverZ: r2(ts - Z975), tailGap: r3(tU - zU), nu, c: r2(c) };
  }
  function fieldsFdist(s) {
    const d1 = Math.max(1, Math.round(s.d1)), d2 = Math.max(2, Math.round(s.d2)), c = Math.max(0, s.fc);
    return { fUpper: r3(fPValue(c, d1, d2)), fStar: r2(fStar(d1, d2)), fMean: d2 > 2 ? r2(d2 / (d2 - 2)) : 999, d1, d2, c: r2(c) };
  }

  /* ════ plot helpers (visualization only) ════ */
  const PW = 660, PH = 300, PADL = 46, PADR = 16, PADT = 16, PADB = 32;
  function XF(xdom, ydom) {
    const x0 = PADL, x1 = PW - PADR, y0 = PH - PADB, y1 = PADT;
    return { sx: (x) => x0 + (x - xdom[0]) / (xdom[1] - xdom[0]) * (x1 - x0), sy: (y) => y0 + (y - ydom[0]) / ((ydom[1] - ydom[0]) || 1) * (y1 - y0), x0, x1, y0, y1, xdom, ydom };
  }
  function curveD(f, xf, n = 240) { let d = ""; for (let i = 0; i <= n; i++) { const x = xf.xdom[0] + (xf.xdom[1] - xf.xdom[0]) * i / n; d += (i ? "L" : "M") + xf.sx(x).toFixed(1) + " " + xf.sy(f(x)).toFixed(1); } return d; }
  function shadeD(f, a, b, xf, n = 120) { let d = "M" + xf.sx(a).toFixed(1) + " " + xf.sy(0).toFixed(1); for (let i = 0; i <= n; i++) { const x = a + (b - a) * i / n; d += "L" + xf.sx(x).toFixed(1) + " " + xf.sy(Math.max(0, f(x))).toFixed(1); } d += "L" + xf.sx(b).toFixed(1) + " " + xf.sy(0).toFixed(1) + "Z"; return d; }
  const npdf = (x, mu, s) => Math.exp(-0.5 * ((x - mu) / s) ** 2) / (s * Math.sqrt(2 * Math.PI));
  const tpdf = (x, nu) => Math.exp(gammaln((nu + 1) / 2) - gammaln(nu / 2)) / Math.sqrt(nu * Math.PI) * (1 + x * x / nu) ** (-(nu + 1) / 2);
  function fpdf(x, d1, d2) { if (x <= 0) return 0; const lnB = gammaln(d1 / 2) + gammaln(d2 / 2) - gammaln((d1 + d2) / 2); return Math.exp(0.5 * (d1 * Math.log(d1) + d2 * Math.log(d2)) + (d1 / 2 - 1) * Math.log(x) - ((d1 + d2) / 2) * Math.log(d2 + d1 * x) - lnB); }
  function Axis({ xf, ticks, unit }) {
    return (<g>
      <line x1={xf.x0} y1={xf.y0} x2={xf.x1} y2={xf.y0} stroke={C.faint} strokeWidth="1.2" />
      {ticks.map((t, i) => (<g key={i}><line x1={xf.sx(t)} y1={xf.y0} x2={xf.sx(t)} y2={xf.y0 + 5} stroke={C.faint} strokeWidth="1" /><text x={xf.sx(t)} y={xf.y0 + 18} textAnchor="middle" fill={C.mute} fontSize="11" fontFamily="'IBM Plex Mono',monospace">{t}</text></g>))}
      {unit ? <text x={xf.x1} y={xf.y0 + 18} textAnchor="end" fill={C.faint} fontSize="10" fontFamily="'IBM Plex Mono',monospace">{unit}</text> : null}
    </g>);
  }
  function Slider({ label, value, set, min, max, step, unit, color, fmtV }) {
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 8 }}>
        <span style={{ fontSize: 12, color: C.mute, width: 140 }}><K.T>{label}</K.T></span>
        <input type="range" min={min} max={max} step={step} value={value} onChange={(e) => set(parseFloat(e.target.value))} style={{ flex: 1 }} />
        <span style={{ color: color || C.amber, width: 64, textAlign: "right", fontFamily: "'IBM Plex Mono',monospace" }}>{(fmtV ? fmtV(value) : value)}{unit ? ` ${unit}` : ""}</span>
      </div>
    );
  }
  function Frame({ children }) {
    return <svg viewBox={`0 0 ${PW} ${PH}`} style={{ width: "100%", background: SVG_BG, border: `1px solid ${C.line}`, borderRadius: 8 }}>{children}</svg>;
  }
  function Meters({ items }) {
    return <div style={{ display: "grid", gridTemplateColumns: `repeat(${items.length},1fr)`, gap: 8, marginTop: 12 }}>{items.map((m, i) => <StatMeter key={i} label={m.label} v={m.v} d={m.d} unit={m.unit} color={m.color} />)}</div>;
  }
  function Status({ ok, children }) {
    return <div style={{ marginTop: 10, padding: "8px 12px", borderRadius: 6, background: C.panel, border: `1px solid ${ok ? C.gold : C.line}`, color: ok ? C.gold : C.mute, fontSize: 12.5 }}>{children}</div>;
  }

  /* ════ Module 1: normal — probability is area ════ */
  function NormalFig({ task, setTask, tasks, report, done }) {
    const [s, setS] = useState({ mu: 0, sigma: 1, a: -1, b: 1 });
    const fld = useMemo(() => fieldsNormal(s), [s]);
    useEffect(() => { report(fld); }, [s]); // eslint-disable-line
    const set = (k) => (v) => setS((o) => ({ ...o, [k]: v }));
    const xf = XF([-8, 8], [0, 0.86]);
    const lo = Math.min(s.a, s.b), hi = Math.max(s.a, s.b), f = (x) => npdf(x, s.mu, Math.max(0.1, s.sigma));
    const t = tasks.find((x) => x.id === task) || tasks[0];
    return (<>
      <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
      <Frame>
        <Axis xf={xf} ticks={[-8, -4, 0, 4, 8]} unit="x" />
        <path d={shadeD(f, lo, hi, xf)} fill={C.gold} fillOpacity="0.32" />
        <path d={curveD(f, xf)} fill="none" stroke={C.teal} strokeWidth="2.4" />
        <line x1={xf.sx(s.mu)} y1={xf.y0} x2={xf.sx(s.mu)} y2={xf.sy(f(s.mu))} stroke={C.faint} strokeWidth="1.2" strokeDasharray="4 4" />
        {[lo, hi].map((e, i) => <line key={i} x1={xf.sx(e)} y1={xf.y0} x2={xf.sx(e)} y2={xf.y1} stroke={C.crimson} strokeWidth="1.4" strokeDasharray="4 4" />)}
        <text x={xf.x0 + 6} y={xf.y1 + 12} fill={C.gold} fontSize="13" fontFamily="'IBM Plex Mono',monospace">P(a≤X≤b) = {fmt(fld.area, 3)}</text>
      </Frame>
      <Status ok={fld.area >= 0.94 && fld.area <= 0.96}>z(a) = {fmt(fld.zA, 2)}, z(b) = {fmt(fld.zB, 2)} · area = {fmt(fld.area, 3)} · <K.T>total area is always 1.</K.T></Status>
      <Slider label="mean μ" value={s.mu} set={set("mu")} min={-6} max={6} step={0.1} color={C.gold} fmtV={(v) => fmt(v, 1)} />
      <Slider label="std dev σ" value={s.sigma} set={set("sigma")} min={0.4} max={3.4} step={0.1} color="#a98fd0" fmtV={(v) => fmt(v, 1)} />
      <Slider label="band left a" value={s.a} set={set("a")} min={-8} max={8} step={0.1} color={C.crimson} fmtV={(v) => fmt(v, 1)} />
      <Slider label="band right b" value={s.b} set={set("b")} min={-8} max={8} step={0.1} color={C.crimson} fmtV={(v) => fmt(v, 1)} />
      <Meters items={[{ label: "area P(a≤X≤b)", v: fld.area, d: 3, color: C.gold }, { label: "z(a)", v: fld.zA, d: 2, color: C.blue }, { label: "z(b)", v: fld.zB, d: 2, color: C.blue }, { label: "right tail", v: fld.rightTail, d: 3, color: C.teal }]} />
    </>);
  }

  /* ════ Module 2: binomial & normal approximation ════ */
  function BinomFig({ task, setTask, tasks, report, done }) {
    const [s, setS] = useState({ n: 20, p: 0.4, k: 8 });
    const fld = useMemo(() => fieldsBinom(s), [s]);
    useEffect(() => { report(fld); }, [s]); // eslint-disable-line
    const set = (k) => (v) => setS((o) => ({ ...o, [k]: v }));
    const n = s.n, kk = Math.max(0, Math.min(n, Math.round(s.k)));
    const peak = binomPmf(Math.round(n * s.p), n, s.p) || 0.1;
    const xf = XF([-0.5, n + 0.5], [0, peak * 1.15]);
    const sd = Math.sqrt(n * s.p * (1 - s.p)) || 1, mean = n * s.p;
    const bw = Math.max(1, (xf.sx(1) - xf.sx(0)) * 0.84);
    const t = tasks.find((x) => x.id === task) || tasks[0];
    return (<>
      <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
      <Frame>
        <Axis xf={xf} ticks={[0, Math.round(n / 4), Math.round(n / 2), Math.round(3 * n / 4), n]} unit="k" />
        {Array.from({ length: n + 1 }, (_, i) => i).map((i) => { const h = binomPmf(i, n, s.p); if (h <= 0) return null; const x = xf.sx(i) - bw / 2, y = xf.sy(h); return <rect key={i} x={x} y={y} width={bw} height={Math.max(0, xf.y0 - y)} rx="1.5" fill={i <= kk ? C.gold : "#3f6377"} fillOpacity={i <= kk ? 0.8 : 0.5} stroke="#22455c" strokeWidth="0.5" />; })}
        <path d={curveD((x) => npdf(x, mean, sd), xf)} fill="none" stroke={C.teal} strokeWidth="2.2" />
        <line x1={xf.sx(kk + 0.5)} y1={xf.y0} x2={xf.sx(kk + 0.5)} y2={xf.y1} stroke={C.crimson} strokeWidth="1.4" strokeDasharray="4 4" />
        <text x={xf.x0 + 6} y={xf.y1 + 12} fill={C.gold} fontSize="13" fontFamily="'IBM Plex Mono',monospace">P(X≤{kk}) = {fmt(fld.cdfK, 3)}</text>
      </Frame>
      <Status ok={fld.normalOK}>mean np = {fmt(fld.mean, 2)} · sd = {fmt(fld.sd, 2)} · normal approx Φ = {fmt(fld.normalApprox, 3)} (<K.T>gap</K.T> {fmt(fld.approxGap, 3)}) · {fld.normalOK ? <K.T>approximation valid ✓</K.T> : <K.T>np or n(1−p) below 10.</K.T>}</Status>
      <Slider label="trials n" value={s.n} set={set("n")} min={5} max={60} step={1} color={C.blue} />
      <Slider label="success prob p" value={s.p} set={set("p")} min={0.05} max={0.95} step={0.05} color="#a98fd0" fmtV={(v) => fmt(v, 2)} />
      <Slider label="cutoff k" value={s.k} set={set("k")} min={0} max={60} step={1} color={C.gold} />
      <Meters items={[{ label: "P(X=k)", v: fld.pmfK, d: 3, color: C.gold }, { label: "P(X≤k)", v: fld.cdfK, d: 3, color: C.teal }, { label: "normal Φ", v: fld.normalApprox, d: 3, color: C.blue }, { label: "gap", v: fld.approxGap, d: 3, color: fld.approxGap < 0.02 ? C.teal : C.crimson }]} />
    </>);
  }

  /* ════ Module 3: t versus z ════ */
  function TdistFig({ task, setTask, tasks, report, done }) {
    const [s, setS] = useState({ nu: 5, tc: 2 });
    const fld = useMemo(() => fieldsTdist(s), [s]);
    useEffect(() => { report(fld); }, [s]); // eslint-disable-line
    const set = (k) => (v) => setS((o) => ({ ...o, [k]: v }));
    const xf = XF([-5, 5], [0, 0.42]);
    const c = Math.max(0, s.tc), t = tasks.find((x) => x.id === task) || tasks[0];
    return (<>
      <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
      <Frame>
        <Axis xf={xf} ticks={[-4, -2, 0, 2, 4]} unit="t" />
        <path d={shadeD((x) => tpdf(x, s.nu), c, 5, xf)} fill={C.gold} fillOpacity="0.34" />
        <path d={curveD((x) => npdf(x, 0, 1), xf)} fill="none" stroke={C.faint} strokeWidth="1.6" strokeDasharray="5 4" />
        <path d={curveD((x) => tpdf(x, s.nu), xf)} fill="none" stroke={C.teal} strokeWidth="2.4" />
        <line x1={xf.sx(c)} y1={xf.y0} x2={xf.sx(c)} y2={xf.y1} stroke={C.crimson} strokeWidth="1.4" strokeDasharray="4 4" />
        <text x={xf.x0 + 6} y={xf.y1 + 12} fill={C.gold} fontSize="12" fontFamily="'IBM Plex Mono',monospace">P(T&gt;c) = {fmt(fld.tUpper, 3)} vs P(Z&gt;c) = {fmt(fld.zUpper, 3)}</text>
        <text x={xf.x1 - 6} y={xf.y1 + 12} textAnchor="end" fill={C.faint} fontSize="11" fontFamily="'IBM Plex Mono',monospace">t (teal) · z (grey)</text>
      </Frame>
      <Status ok={fld.excessOverZ < 0.05}>t* = {fmt(fld.tStar, 2)} (two-sided 5%) · z* = 1.96 · <K.T>excess</K.T> = {fmt(fld.excessOverZ, 2)} · {fld.excessOverZ < 0.05 ? <K.T>t ≈ z (large ν).</K.T> : <K.T>heavier tails than z.</K.T>}</Status>
      <Slider label="degrees of freedom ν" value={s.nu} set={set("nu")} min={1} max={80} step={1} color={C.blue} />
      <Slider label="cutoff c" value={s.tc} set={set("tc")} min={0} max={5} step={0.05} color={C.gold} fmtV={(v) => fmt(v, 2)} />
      <Meters items={[{ label: "P(T>c)", v: fld.tUpper, d: 3, color: C.gold }, { label: "P(Z>c)", v: fld.zUpper, d: 3, color: C.faint }, { label: "t* (5%)", v: fld.tStar, d: 2, color: C.teal }, { label: "t* − z*", v: fld.excessOverZ, d: 2, color: C.crimson }]} />
    </>);
  }

  /* ════ Module 4: F distribution ════ */
  function FdistFig({ task, setTask, tasks, report, done }) {
    const [s, setS] = useState({ d1: 3, d2: 12, fc: 3 });
    const fld = useMemo(() => fieldsFdist(s), [s]);
    useEffect(() => { report(fld); }, [s]); // eslint-disable-line
    const set = (k) => (v) => setS((o) => ({ ...o, [k]: v }));
    const xf = XF([0, 6], [0, 0.95]);
    const c = Math.max(0, s.fc), t = tasks.find((x) => x.id === task) || tasks[0];
    return (<>
      <TaskStrip tasks={tasks} cur={task} setCur={setTask} done={done} goal={t && t.goal} />
      <Frame>
        <Axis xf={xf} ticks={[0, 1, 2, 3, 4, 5, 6]} unit="F" />
        <path d={shadeD((x) => fpdf(x, s.d1, s.d2), c, 6, xf)} fill={C.gold} fillOpacity="0.34" />
        <path d={curveD((x) => fpdf(x, s.d1, s.d2), xf)} fill="none" stroke={C.teal} strokeWidth="2.4" />
        <line x1={xf.sx(c)} y1={xf.y0} x2={xf.sx(c)} y2={xf.y1} stroke={C.crimson} strokeWidth="1.4" strokeDasharray="4 4" />
        <line x1={xf.sx(fld.fStar)} y1={xf.y0} x2={xf.sx(fld.fStar)} y2={xf.y1} stroke={C.gold} strokeWidth="1.2" />
        <text x={xf.x0 + 6} y={xf.y1 + 12} fill={C.gold} fontSize="13" fontFamily="'IBM Plex Mono',monospace">P(F&gt;c) = {fmt(fld.fUpper, 3)}</text>
        <text x={xf.sx(fld.fStar) + 4} y={xf.y1 + 26} fill={C.gold} fontSize="11" fontFamily="'IBM Plex Mono',monospace">F* = {fmt(fld.fStar, 2)}</text>
      </Frame>
      <Status ok={fld.fUpper >= 0.045 && fld.fUpper <= 0.055}>upper tail P(F&gt;c) = {fmt(fld.fUpper, 3)} · F*(5%) = {fmt(fld.fStar, 2)} · mean d₂/(d₂−2) = {fmt(fld.fMean, 2)} · <K.T>right-skewed.</K.T></Status>
      <Slider label="numerator df d₁" value={s.d1} set={set("d1")} min={1} max={15} step={1} color={C.blue} />
      <Slider label="denominator df d₂" value={s.d2} set={set("d2")} min={2} max={40} step={1} color="#a98fd0" />
      <Slider label="cutoff c" value={s.fc} set={set("fc")} min={0} max={6} step={0.05} color={C.gold} fmtV={(v) => fmt(v, 2)} />
      <Meters items={[{ label: "P(F>c)", v: fld.fUpper, d: 3, color: C.gold }, { label: "F* (5%)", v: fld.fStar, d: 2, color: C.teal }, { label: "mean", v: fld.fMean, d: 2, color: C.blue }, { label: "d₁ , d₂", v: `${fld.d1}, ${fld.d2}`, color: C.mute }]} />
    </>);
  }

  const FIGS = { normal: NormalFig, binom: BinomFig, tdist: TdistFig, fdist: FdistFig };
  const MODULE_LABEL = { normal: "The normal curve — probability is area", binom: "Binomial & normal approximation", tdist: "t versus z", fdist: "The F distribution" };

  function AdvStatsFig(props) {
    const module = FIGS[props.spec && props.spec.module] ? props.spec.module : "normal";
    const Fig = FIGS[module];
    return <Fig {...props} />;
  }

  window.AdvStatsTutor = K.makeTutor(AdvStatsFig, {
    benchId: "advstats",
    moduleLabel: (spec) => MODULE_LABEL[spec && spec.module] || "Advanced statistics workbench",
  });
})();
