// ═══════════════════════════════════════════════════════════════════════════
// Three instruments, one per research question. Each is drawn from the
// mathematics of its own question, so they share a colour law (the ramp =
// cushion) but not a layout.
//
//   PolicyDg    f_BGZ = κ̄(n)·d/b — exposure against cushion; evidence n bends
//               the slope. "Double prudence": less cushion or less data → less risk.
//   TailDg      Two generalised-Pareto tails that agree inside the data and part
//               beyond it. Sizing to a quantile past the data edge inherits the
//               tail-index error; the error grows with depth, not with n.
//   CeilingDg   The private-markets share a drawdown mandate can carry, against
//               what static rules imply. Calls drain the liquid sleeve faster
//               than distributions refill it.
// ═══════════════════════════════════════════════════════════════════════════

const useSize = (ref) => {
  const [s, setS] = React.useState({ w: 0, h: 0 });
  React.useLayoutEffect(() => {
    const el = ref.current;
    if (!el) return;
    const ro = new ResizeObserver(([e]) => {
      const r = e.contentRect;
      setS({ w: Math.round(r.width), h: Math.round(r.height) });
    });
    ro.observe(el);
    return () => ro.disconnect();
  }, []);
  return s;
};

const Range = ({ label, value, min, max, step, onChange, display, color }) => {
  const p = ((value - min) / (max - min)) * 100;
  return (
    <label className="dg-ctl">
      <span className="label">{label}</span>
      <input className="range" type="range" min={min} max={max} step={step} value={value}
        onChange={(e) => onChange(parseFloat(e.target.value))}
        style={{ '--p': p + '%', '--rc': color }} />
      <span className="num" style={{ fontSize: 15, minWidth: 64, textAlign: 'right' }}>{display}</span>
    </label>
  );
};

const Read = ({ items }) => (
  <div className="dg-read">
    {items.map(([k, v, c]) => (
      <div key={k}>
        <div className="label">{k}</div>
        <div className="v" style={c ? { color: c } : null}>{v}</div>
      </div>
    ))}
  </div>
);

// ── 1 · Policy ──────────────────────────────────────────────────────────────
const PolicyDg = ({ area }) => {
  const box = React.useRef(null);
  const { w, h } = useSize(box);
  const stops = useRamp();
  const [logN, setLogN] = React.useState(Math.log10(60));
  const n = Math.round(10 ** logN);
  const K = 2.4, NP = 140;
  const kBar = K * n / (n + NP);
  const id = React.useId().replace(/:/g, '');

  const L = 10, R = w - 12, T = 16, B = h - 30;
  const X = (d) => L + d * (R - L);
  const Y = (f) => B - (f / 2.8) * (B - T);

  return (
    <div className="dg">
      <div className="dg-head">
        <span className="label">{area.formulaLabel}</span>
        <Fx className="dg-formula">{area.formula}</Fx>
      </div>
      <div className="dg-plot" ref={box}>
        {w > 0 && (
          <svg width={w} height={h} role="img" aria-label={`Exposure against cushion. With n = ${n}, the Bayes rule's slope is ${kBar.toFixed(2)} against ${K} for plug-in Kelly.`}>
            <defs>
              <linearGradient id={`g${id}`} x1="0" x2="1">
                {[0, .25, .5, .75, 1].map((t) => <stop key={t} offset={t} stopColor={rampAt(stops, t)} />)}
              </linearGradient>
            </defs>
            {[0.25, 0.5, 0.75].map((t) => <line key={t} x1={X(t)} x2={X(t)} y1={T} y2={B} stroke="var(--line)" />)}
            <line x1={L} x2={R} y1={B + .5} y2={B + .5} stroke="var(--line-2)" />
            {/* plug-in: ignores the barrier */}
            <line x1={X(0)} x2={X(1)} y1={Y(K)} y2={Y(K)} stroke="var(--ink-3)" strokeWidth="1.5" strokeDasharray="4 5" />
            <text className="svg-t" x={X(0) + 4} y={Y(K) - 8}>plug-in Kelly · f = κ̂, whatever the cushion</text>
            {/* GZ with a known edge */}
            <line x1={X(0)} y1={Y(0)} x2={X(1)} y2={Y(K)} stroke="var(--ink-3)" strokeWidth="1.2" />
            <text className="svg-t" x={X(1) - 4} y={Y(K) + 18} textAnchor="end">Grossman–Zhou, edge known</text>
            {/* Bayes GZ */}
            <path d={`M${X(0)} ${Y(0)} L${X(1)} ${Y(kBar)} L${X(1)} ${Y(0)} Z`} fill={`url(#g${id})`} opacity=".16" style={{ transition: 'd .5s' }} />
            <line x1={X(0)} y1={Y(0)} x2={X(1)} y2={Y(kBar)} stroke={`url(#g${id})`} strokeWidth="3" strokeLinecap="round" />
            <circle cx={X(1)} cy={Y(kBar)} r="5" fill="var(--bg-2)" stroke={rampAt(stops, 1)} strokeWidth="2.4" />
            <text className="svg-t" x={X(1) - 12} y={Y(kBar) - 12} textAnchor="end" style={{ fill: 'var(--ink)' }}>Bayes GZ · n = {n}</text>
            <text className="svg-t" x={X(0)} y={h - 8}>barrier · d = 0</text>
            <text className="svg-t" x={X(1)} y={h - 8} textAnchor="end">peak · d = 1</text>
          </svg>
        )}
      </div>
      <Range label="Evidence n" min={1} max={3.3} step={0.01} value={logN} onChange={setLogN}
        display={n.toLocaleString('en-US')} color="var(--ink)" />
      <Read items={[
        ['κ̄(n)', kBar.toFixed(2)],
        ['Shrinkage', '−' + Math.round((1 - kBar / K) * 100) + '%'],
        ['f at half cushion', (kBar * 0.5).toFixed(2) + '×'],
      ]} />
    </div>
  );
};

// ── 2 · Tail ────────────────────────────────────────────────────────────────
const TAIL = { XI: 0.40, XI_HAT: 0.15, BETA: 1, S0: 1e-3 };
const xTrue = (p) => (TAIL.BETA / TAIL.XI) * (p ** -TAIL.XI - 1);
const X0 = xTrue(TAIL.S0);
const B0 = TAIL.BETA + TAIL.XI * X0;           // threshold stability of the GPD
const xFit = (p) => (p >= TAIL.S0 ? xTrue(p) : X0 + (B0 / TAIL.XI_HAT) * ((p / TAIL.S0) ** -TAIL.XI_HAT - 1));
const sTrue = (x) => (1 + TAIL.XI * x / TAIL.BETA) ** (-1 / TAIL.XI);
const sFit = (x) => (x <= X0 ? sTrue(x) : TAIL.S0 * (1 + TAIL.XI_HAT * (x - X0) / B0) ** (-1 / TAIL.XI_HAT));

const TailDg = ({ area }) => {
  const box = React.useRef(null);
  const { w, h } = useSize(box);
  const stops = useRamp();
  const [lp, setLp] = React.useState(5);          // −log10 p
  const p = 10 ** -lp;
  const depth = Math.max(0, Math.log(TAIL.S0 / p));
  const over = xTrue(p) / xFit(p);

  const narrow = w < 440;
  const L = 10, R = w - 12, T = 14, B = h - 30;
  const lx0 = 0, lx1 = 3;                          // log10 x
  const ly0 = 0, ly1 = -6.3;                       // log10 S
  const X = (x) => L + ((Math.log10(Math.max(1, x)) - lx0) / (lx1 - lx0)) * (R - L);
  const Y = (s) => T + ((Math.log10(s) - ly0) / (ly1 - ly0)) * (B - T);

  const curve = (S) => {
    let d = '';
    for (let i = 0; i <= 120; i++) {
      const x = 10 ** (lx0 + (i / 120) * (lx1 - lx0));
      const s = S(x);
      if (s < 10 ** ly1) break;
      d += (i ? 'L' : 'M') + X(x).toFixed(1) + ' ' + Y(s).toFixed(1);
    }
    return d;
  };

  const c = Math.max(0, 1 - depth / 8);            // deeper = less cushion
  const col = rampAt(stops, c);

  return (
    <div className="dg">
      <div className="dg-head">
        <span className="label">{area.formulaLabel}</span>
        <Fx className="dg-formula">{area.formula}</Fx>
      </div>
      <div className="dg-plot" ref={box}>
        {w > 0 && (
          <svg width={w} height={h} role="img" aria-label={`Log-log tail. At p = 1e-${lp.toFixed(1)}, the fitted quantile understates the true one by ${over.toFixed(2)} times.`}>
            <rect x={L} y={T} width={Math.max(0, X(X0) - L)} height={B - T} fill="var(--bg-3)" />
            <text className="svg-t" x={L + 8} y={T + 16}>observed data</text>
            <line x1={X(X0)} x2={X(X0)} y1={T} y2={B} stroke="var(--line-2)" />
            <text className="svg-t" x={X(X0) + 8} y={T + 16}>{narrow ? 'beyond →' : 'extrapolation →'}</text>
            {[-1, -2, -3, -4, -5, -6].map((k) => (
              <g key={k}>
                <line x1={L} x2={R} y1={Y(10 ** k)} y2={Y(10 ** k)} stroke="var(--line)" strokeDasharray="1 4" />
                <text className="svg-t" x={R} y={Y(10 ** k) - 4} textAnchor="end">1e{k}</text>
              </g>
            ))}
            <path d={curve(sTrue)} fill="none" stroke="var(--ink-3)" strokeWidth="1.6" />
            <path d={curve(sFit)} fill="none" stroke={rampAt(stops, 1)} strokeWidth="2.6" strokeLinecap="round" />
            {/* the target quantile, both readings */}
            <line x1={L} x2={X(xTrue(p))} y1={Y(p)} y2={Y(p)} stroke={col} strokeWidth="1.2" strokeDasharray="3 4" />
            <line x1={X(xFit(p))} x2={X(xTrue(p))} y1={Y(p)} y2={Y(p)} stroke={col} strokeWidth="4" strokeLinecap="round" opacity=".85" />
            <circle cx={X(xFit(p))} cy={Y(p)} r="4.5" fill="var(--bg-2)" stroke={rampAt(stops, 1)} strokeWidth="2.2" />
            <circle cx={X(xTrue(p))} cy={Y(p)} r="4.5" fill="var(--bg-2)" stroke="var(--ink-3)" strokeWidth="2" />
            <text className="svg-t" x={X(xTrue(p)) + 8} y={Y(p) + 18}>true</text>
            <text className="svg-t" x={X(xFit(p)) - 10} y={Y(p) - 10} textAnchor="end" style={{ fill: 'var(--ink)' }}>fitted ξ̂ = {TAIL.XI_HAT}</text>
            <text className="svg-t" x={L} y={h - 8}>{narrow ? 'loss (log)' : 'loss, log scale'}</text>
            <text className="svg-t" x={R} y={h - 8} textAnchor="end">{narrow ? 'P(loss > x), log' : 'P(loss > x), log scale'}</text>
          </svg>
        )}
      </div>
      <Range label="Target breach p" min={2} max={6} step={0.05} value={lp} onChange={setLp}
        display={'1e−' + lp.toFixed(1)} color={col} />
      <Read items={[
        ['Depth past data  L', depth.toFixed(2)],
        ['Tail index  ξ̂ / ξ', `${TAIL.XI_HAT.toFixed(2)}/${TAIL.XI.toFixed(2)}`],
        ['Position oversized', over.toFixed(2) + '×', over > 1.01 ? 'var(--bar-ink)' : undefined],
      ]} />
    </div>
  );
};

// ── 3 · Ceiling ─────────────────────────────────────────────────────────────
const pct = (s) => parseFloat(String(s).replace('%', ''));

const CeilingDg = ({ area }) => {
  const stops = useRamp();
  const m = Object.fromEntries((SITE.models.find((x) => x.id === 'ceiling') || { metrics: [] }).metrics);
  const ceil = pct(m['True ceiling'] || '12.4%');
  const perceived = pct(m['Perceived'] || '49%');
  const typ = String(m['Typically observed'] || '27–30%').replace('%', '').split(/[–-]/).map(parseFloat);
  const [eta, setEta] = React.useState(25);
  const MAX = 60;

  const c = Math.max(0, 1 - eta / ceil);
  const over = eta > ceil;
  const col = rampAt(stops, c);
  const at = (v) => `${(v / MAX) * 100}%`;
  const calls = Math.max(1, Math.round(eta / 6));
  const dists = Math.max(1, Math.round(Math.min(eta, ceil) / 6));

  return (
    <div className="dg">
      <div className="dg-head">
        <span className="label">{area.formulaLabel}</span>
        <Fx className="dg-formula">{area.formula}</Fx>
      </div>

      <div className="dg-plot" style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 28, minHeight: 250 }}>
        {/* the portfolio: liquid sleeve left, untradeable sleeve right */}
        <div style={{ position: 'relative', height: 92 }}>
          <div className="flow flow-call" aria-hidden="true">
            {Array.from({ length: calls }).map((_, i) => <i key={i} style={{ '--k': i / calls, '--dur': `${3.6 - Math.min(2, eta / 30)}s`, background: col }} />)}
          </div>
          <div style={{ position: 'absolute', left: 0, right: 0, top: 24, height: 44, display: 'flex', borderRadius: 14, overflow: 'hidden', boxShadow: 'inset 0 0 0 1px var(--line)' }}>
            <div style={{ flex: `${100 - eta} 1 0`, background: `color-mix(in oklab, ${col} 22%, var(--bg-2))`, display: 'flex', alignItems: 'center', padding: '0 14px', transition: 'flex .6s var(--ease-out), background-color .4s', minWidth: 0 }}>
              <span className="label" style={{ color: 'var(--ink-2)', whiteSpace: 'nowrap' }}>liquid {100 - eta}%</span>
            </div>
            <div className="hatch" style={{ flex: `${eta} 1 0`, display: 'flex', alignItems: 'center', justifyContent: 'flex-end', padding: '0 12px', transition: 'flex .6s var(--ease-out)', minWidth: 0 }}>
              <span className="label" style={{ color: 'var(--ink-2)', whiteSpace: 'nowrap' }}>{eta >= 12 ? `illiquid ${eta}%` : ''}</span>
            </div>
          </div>
          <div className="flow flow-dist" aria-hidden="true">
            {Array.from({ length: dists }).map((_, i) => <i key={i} style={{ '--k': i / dists, '--dur': '5.2s' }} />)}
          </div>
        </div>

        {/* allocation scale */}
        <div style={{ position: 'relative', height: 46 }}>
          <div style={{ position: 'absolute', left: 0, right: 0, top: 22, height: 2, background: 'var(--line-2)', borderRadius: 2 }} />
          <div style={{ position: 'absolute', left: at(typ[0]), width: at(typ[1] - typ[0]), top: 16, height: 14, borderRadius: 7, background: 'var(--bg-3)', boxShadow: 'inset 0 0 0 1px var(--line-2)' }} />
          {[[ceil, rampAt(stops, 1), true], [perceived, 'var(--ink-3)', false]].map(([v, cl, strong]) => (
            <div key={v} style={{ position: 'absolute', left: at(v), top: 4, width: strong ? 3 : 1.5, height: strong ? 38 : 30, marginLeft: strong ? -1.5 : -0.75, background: cl, borderRadius: 2 }} />
          ))}
          <div style={{ position: 'absolute', left: at(eta), top: 12, width: 22, height: 22, marginLeft: -11, borderRadius: 99, background: 'var(--bg-2)', boxShadow: `0 0 0 2.5px ${col}, 0 4px 14px oklch(0% 0 0 / .18)`, transition: 'left .25s var(--ease-out)' }} />
        </div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px 20px', marginTop: -8 }}>
          <span className="lg"><i style={{ width: 3, height: 14, background: rampAt(stops, 1) }} />true ceiling {ceil}%</span>
          <span className="lg"><i style={{ width: 18, height: 10, borderRadius: 5, background: 'var(--bg-3)', boxShadow: 'inset 0 0 0 1px var(--line-2)' }} />typically observed {typ[0]}–{typ[1]}%</span>
          <span className="lg"><i style={{ width: 1.5, height: 14, background: 'var(--ink-3)' }} />perceived {perceived}%</span>
        </div>
      </div>

      <Range label="Illiquid share η" min={0} max={MAX} step={0.5} value={eta} onChange={setEta}
        display={eta.toFixed(1) + '%'} color={col} />
      <Read items={[
        ['Ceiling  η*', ceil + '%'],
        ['Headroom', over ? '—' : (ceil - eta).toFixed(1) + ' pp'],
        ['Mandate', over ? 'infeasible' : 'feasible', over ? 'var(--bar-ink)' : undefined],
      ]} />
      <p className="dg-note">Breach probability at a 25% allocation: {m['Breach @ 25%'] || '24.7%'}. Calls (above) drain the liquid sleeve faster than distributions (below) refill it.</p>
    </div>
  );
};

Object.assign(window, { PolicyDg, TailDg, CeilingDg, useSize });
