/* Playground — "Gain intuition of the protocol"
   Narrative copy · play/pause + speed controls · plain-English narration
   FSM steps on the left sync with the live simulation state. */

const Sim = window.UsufructSim;

/* ─── Simulation hook with pause / speed ───────────────────── */
function useSimulation() {
  const [sim, setSim]       = React.useState(() => Sim.initialState());
  const [paused, setPaused] = React.useState(false);
  const [speed, setSpeed]   = React.useState(1);
  const lastTime            = React.useRef(null);

  React.useEffect(() => {
    let raf;
    const tick = (now) => {
      if (!paused) {
        if (lastTime.current == null) lastTime.current = now;
        const realDt = Math.min(64, now - lastTime.current);
        lastTime.current = now;
        setSim(s => Sim.step(s, realDt * Sim.SIM.timeScale * speed, Math.random));
      } else {
        lastTime.current = null;
      }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [paused, speed]);

  const reset = React.useCallback(() => setSim(Sim.reset()), []);
  const bid   = React.useCallback((t, p) => setSim(s => Sim.manualBid(s, t, p)), []);
  return { sim, paused, setPaused, speed, setSpeed, reset, bid };
}

/* ─── Plain-English narration per FSM state ─────────────────── */
const NARRATION = {
  Idle: {
    headline: 'Waiting for the first bid.',
    body: 'The asset is listed at the floor price. No one is using it yet. The moment someone bids, they can borrow and use the asset immediately.',
  },
  Descent: {
    headline: 'Price descending — first bidder wins.',
    body: 'A Dutch auction is running. The access price falls from the last acquisition price toward the floor. Whoever bids first takes the rental.',
  },
  Occupied: {
    headline: 'Asset is rented.',
    body: "The asset payer's stake drains along the credit curve — the consumed portion becomes the asset issuer's earnings. Any newcomer can displace them at any time — the ask price is always known.",
  },
  Demand: {
    headline: 'Displacement in progress.',
    body: "A challenger has outbid the current asset payer stake. The current asset payer keeps borrowing the asset until the handover window ends. Then the challenger takes over and the exiting asset payer gets back their unspent deposit.",
  },
  Retired: {
    headline: 'Escrow closed.',
    body: 'The asset issuer called retire(). All accrued earnings have been released. The asset is no longer rentable through this escrow.',
  },
};

/* ─── FSM journey steps — sync with live state ──────────────── */
const FSM_STEPS = [
  { states: ['Idle'],              n: '01', label: 'Listed',   desc: 'Asset at floor price. Awaiting the first bid.' },
  { states: ['Descent'],           n: '02', label: 'Auction',  desc: 'Price descends. First bidder wins the rental.' },
  { states: ['Occupied'],          n: '03', label: 'Occupied', desc: "Deposit drains. The ask price is always known." },
  { states: ['Demand','Retired'],  n: '04', label: 'Settled',  desc: 'Challenger wins. Unspent deposit refunded. Cycle closes.' },
];

/* ─── Phase meta ─────────────────────────────────────────────── */
const PHASE_META = {
  Idle:     { label: 'IDLE',     color: 'var(--fg-2)',   dot: 'idle' },
  Descent:  { label: 'DESCENT',  color: 'var(--warn)',   dot: 'dutch' },
  Occupied: { label: 'OCCUPIED', color: 'var(--signal)', dot: 'occupied' },
  Demand:   { label: 'DEMAND',   color: 'var(--danger)', dot: 'demand' },
  Retired:  { label: 'RETIRED',  color: 'var(--fg-3)',   dot: 'retired' },
};
const PHASES_ORDER = ['Idle', 'Descent', 'Occupied', 'Demand', 'Retired'];

/* ─── ShapeChart ─────────────────────────────────────────────── */
function ShapeChart({ policyName, shape, progress, showCrosshair, yTopLabel, yBotLabel, xCenterLabel, xStartLabel, xEndLabel, yAxisLabel, hLineLabel, vCrosshair, hCrosshair, curveColor, markerColor, dimmed = false, invertY = false }) {
  const W = 520, H = 120;
  const pad = { top: 12, right: 12, bottom: 18, left: 32 };
  const w = W - pad.left - pad.right;
  const h = H - pad.top - pad.bottom;
  const N = 120;
  const yPos = (v) => invertY ? pad.top + (1 - v) * h : pad.top + v * h;
  const pts = [];
  for (let i = 0; i <= N; i++) {
    const x = i / N;
    pts.push(`${pad.left + x * w},${yPos(Sim.curveHeight(shape, x))}`);
  }
  const mh = Sim.curveHeight(shape, progress);
  const mX = pad.left + progress * w;
  const mY = yPos(mh);
  return (
    <div style={{ padding: '8px 12px', borderBottom: '1px solid var(--line-1)', opacity: dimmed ? 0.38 : 1, transition: 'opacity 300ms var(--ease-out)' }}>
      <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet" style={{ display: 'block', width: '100%', height: 'auto' }}>
        <rect x={pad.left} y={pad.top} width={w} height={h} fill="var(--bg-0)" stroke="var(--line-1)" />
        <text x={pad.left} y={pad.top - 3} fontSize="11" fontFamily="JetBrains Mono" fill="var(--fg-3)">{yTopLabel}</text>
        <text x={pad.left + 4} y={pad.top + h - 4} textAnchor="start" fontSize="11" fontFamily="JetBrains Mono" fill="var(--fg-3)">{yBotLabel}</text>
        <text x={pad.left + w / 2} y={H - 2} textAnchor="middle" fontSize="11" fontFamily="JetBrains Mono" fill={curveColor}>{xCenterLabel}</text>
        {xStartLabel && <text x={pad.left} y={pad.top + h + 13} textAnchor="start" fontSize="11" fontFamily="JetBrains Mono" fill="var(--fg-3)">{xStartLabel}</text>}
        {xEndLabel && <text x={pad.left + w} y={pad.top + h + 13} textAnchor="end" fontSize="11" fontFamily="JetBrains Mono" fill="var(--fg-3)">{xEndLabel}</text>}
        <text x={pad.left + 4} y={pad.top + 13} textAnchor="start" fontSize="11" fontFamily="JetBrains Mono" fill={curveColor}>{policyName} · {Sim.describeShape(shape)}</text>
        {yAxisLabel && showCrosshair && vCrosshair && <text x={mX + 4} y={pad.top + h - 4} fontSize="10" fontFamily="JetBrains Mono" fill={vCrosshair.color}>{yAxisLabel}</text>}
        {hLineLabel && showCrosshair && hCrosshair && <text x={pad.left + w - 4} y={mY - 4} textAnchor="end" fontSize="10" fontFamily="JetBrains Mono" fill={hCrosshair.color}>{hLineLabel}</text>}
        <polyline fill="none" stroke={curveColor} strokeWidth="1.5" points={pts.join(' ')} />
        {showCrosshair && vCrosshair && <line x1={mX} y1={pad.top} x2={mX} y2={pad.top + h} stroke={vCrosshair.color} strokeDasharray="3 3" />}
        {showCrosshair && hCrosshair && <line x1={pad.left} y1={mY} x2={pad.left + w} y2={mY} stroke={hCrosshair.color} strokeDasharray="3 3" />}
        {showCrosshair && <circle cx={mX} cy={mY} r="4" fill={markerColor} stroke="var(--bg-0)" strokeWidth="2" />}
      </svg>
    </div>
  );
}

/* ─── State pills ───────────────────────────────────────────── */
function StatePills({ phaseKind }) {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', borderBottom: '1px solid var(--line-1)', background: 'var(--bg-2)' }}>
      {PHASES_ORDER.map((k, i) => {
        const meta = PHASE_META[k];
        const active = k === phaseKind;
        return (
          <div key={k} style={{
            padding: '7px 4px', textAlign: 'center',
            borderRight: i < 4 ? '1px solid var(--line-1)' : 'none',
            fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '0.06em', textTransform: 'uppercase',
            background: active ? meta.color : 'transparent',
            color: active ? 'var(--signal-fg)' : 'var(--fg-3)',
            transition: 'background 220ms var(--ease-out), color 220ms var(--ease-out)',
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
          }}>
            {active && <span style={{ width: 5, height: 5, borderRadius: '50%', background: 'var(--signal-fg)', boxShadow: '0 0 6px var(--signal-fg)', flexShrink: 0 }} />}
            <span>{meta.label}</span>
          </div>
        );
      })}
    </div>
  );
}

/* ─── Sim controls: play/pause · speed · reset ──────────────── */
function SimControls({ paused, setPaused, speed, setSpeed, reset }) {
  const speeds = [1, 2, 4];
  const btnBase = {
    all: 'unset', boxSizing: 'border-box', cursor: 'pointer',
    fontFamily: 'var(--font-mono)', fontSize: 12, letterSpacing: '0.08em',
    transition: 'border-color 120ms, color 120ms',
  };
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px', borderBottom: '1px solid var(--line-1)', background: 'var(--bg-2)' }}>
      {/* Play / Pause */}
      <button
        style={{ ...btnBase, display: 'flex', alignItems: 'center', gap: 6, padding: '5px 11px', border: '1px solid var(--line-1)', color: 'var(--fg-0)', background: 'var(--bg-0)' }}
        onClick={() => setPaused(p => !p)}
        onMouseEnter={e => e.currentTarget.style.borderColor = 'var(--signal)'}
        onMouseLeave={e => e.currentTarget.style.borderColor = 'var(--line-1)'}
      >
        {paused ? '▶ PLAY' : '⏸ PAUSE'}
      </button>

      {/* Speed */}
      <div style={{ display: 'flex', border: '1px solid var(--line-1)', overflow: 'hidden' }}>
        {speeds.map((s, i) => (
          <button key={s} onClick={() => setSpeed(s)} style={{
            ...btnBase,
            padding: '5px 10px',
            borderRight: i < speeds.length - 1 ? '1px solid var(--line-1)' : 'none',
            background: speed === s ? 'var(--signal)' : 'var(--bg-0)',
            color: speed === s ? 'var(--signal-fg)' : 'var(--fg-2)',
          }}>{s}×</button>
        ))}
      </div>

      <div style={{ flex: 1 }} />

      {/* Reset */}
      <button style={{ ...btnBase, padding: '5px 11px', border: '1px solid var(--line-1)', color: 'var(--fg-3)', background: 'var(--bg-0)' }}
        onClick={reset}
        onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--danger)'; e.currentTarget.style.color = 'var(--danger)'; }}
        onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--line-1)'; e.currentTarget.style.color = 'var(--fg-3)'; }}
      >↺ RESET</button>
    </div>
  );
}

/* ─── Narration bar: plain-English "what's happening now" ───── */
function NarrationBar({ phaseKind }) {
  const meta = PHASE_META[phaseKind] || PHASE_META.Idle;
  const [displayPhase, setDisplayPhase] = React.useState(phaseKind);
  const [visible, setVisible]           = React.useState(true);

  React.useEffect(() => {
    if (phaseKind === displayPhase) return;
    setVisible(false);
    const t = setTimeout(() => { setDisplayPhase(phaseKind); setVisible(true); }, 220);
    return () => clearTimeout(t);
  }, [phaseKind]);

  const n    = NARRATION[displayPhase] || NARRATION.Idle;
  const dmeta = PHASE_META[displayPhase] || PHASE_META.Idle;

  return (
    <div style={{
      borderBottom: '1px solid var(--line-1)',
      borderLeft: `3px solid ${dmeta.color}`,
      padding: '12px 16px',
      background: 'var(--bg-0)',
      opacity: visible ? 1 : 0,
      transition: 'opacity 220ms var(--ease-out)',
      minHeight: 70,
    }}>
      <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600, color: dmeta.color, letterSpacing: '0.08em', marginBottom: 5 }}>
        {n.headline}
      </div>
      <div style={{ fontFamily: 'var(--font-sans)', fontSize: 13, color: 'var(--fg-1)', lineHeight: 1.5 }}>
        {n.body}
      </div>
    </div>
  );
}

/* ─── Key stats (2 per phase, large + readable) ─────────────── */
function KeyStats({ sim }) {
  const k = sim.phase.kind;
  const stats = (() => {
    if (k === 'Idle') return [
      { label: 'FLOOR PRICE',   v: Sim.SIM.cycle.floor.toFixed(1), u: 'SUI',  c: 'var(--fg-2)' },
    ];
    if (k === 'Descent') {
      const auctionEnds = Math.max(0, (sim.phase.startMs + Sim.SIM.cycle.windowMs - sim.now) / 1000);
      return [
        { label: 'DESCENT ENDS IN', v: auctionEnds.toFixed(0), u: 's',   c: 'var(--warn)' },
        { label: 'RENT PRICE',      v: Sim.livePrice(sim).toFixed(1),    u: 'SUI', c: 'var(--warn)' },
      ];
    }
    if (k === 'Occupied') {
      const cs = Sim.creditState(sim);
      const ownerEarned = cs.consumed * (1 - Sim.SIM.feeBps / 10_000);
      const remainSec = Math.max(0, (sim.phase.startMs + sim.phase.tenures * Sim.SIM.cycle.ceiling - sim.now) / 1000);
      return [
        { label: 'TENURE ENDS IN',    v: remainSec.toFixed(0),                    u: 's',   c: 'var(--signal)' },
        { label: 'ASSET PAYER STAKE USED', v: `${(cs.progress * 100).toFixed(0)}%`,  u: '',    c: 'var(--signal)' },
        { label: 'REFUNDABLE STAKE',  v: cs.refund.toFixed(1),                  u: 'SUI', c: 'var(--fg-2)'   },
      ];
    }
    if (k === 'Demand') {
      const remain = Math.max(0, (sim.phase.handoverEnd - sim.now) / 1000);
      const cs = Sim.creditState(sim);
      const ownerEarned = cs.consumed * (1 - Sim.SIM.feeBps / 10_000);
      return [
        { label: 'HANDOVER IN',             v: remain.toFixed(1),                                                        u: 's',   c: 'var(--danger)' },
        { label: 'CHALLENGER COMMIT STAKE', v: (sim.phase.challenger.stake / sim.phase.challenger.tenures).toFixed(1),  u: 'SUI', c: 'var(--danger)' },
        { label: 'REFUNDABLE STAKE',        v: cs.refund.toFixed(1),                                                     u: 'SUI', c: 'var(--fg-2)'   },
      ];
    }
    return [
      { label: 'EARNINGS',     v: sim.ownerPool.toFixed(1), u: 'SUI', c: 'var(--good)' },
      { label: 'STATUS',       v: 'closed',                 u: '',    c: 'var(--fg-3)' },
    ];
  })();

  return (
    <div style={{ display: 'grid', gridTemplateColumns: `repeat(${stats.length}, 1fr)`, borderBottom: '1px solid var(--line-1)' }}>
      {stats.map((s, i) => (
        <div key={i} style={{
          padding: '10px 16px',
          borderRight: i < stats.length - 1 ? '1px solid var(--line-1)' : 'none',
          borderLeft: `3px solid ${s.c}`,
        }}>
          <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-3)', letterSpacing: '0.14em', textTransform: 'uppercase', marginBottom: 5 }}>{s.label}</div>
          <div style={{ fontFamily: 'var(--font-sans)', fontWeight: 500, fontSize: 24, color: s.c, lineHeight: 1, letterSpacing: '-0.015em', fontVariantNumeric: 'tabular-nums' }}>
            {s.v}{s.u && <span style={{ color: 'var(--fg-3)', fontSize: 12, marginLeft: 5 }}>{s.u}</span>}
          </div>
        </div>
      ))}
    </div>
  );
}

/* ─── Owner earnings — persistent running total ─────────────── */
function OwnerEarnings({ sim }) {
  const settled  = sim.ownerPool;
  const k        = sim.phase.kind;
  const cs       = (k === 'Occupied' || k === 'Demand') ? Sim.creditState(sim) : null;
  const accruing = cs ? cs.consumed * (1 - Sim.SIM.feeBps / 10_000) : 0;
  const total    = settled + accruing;
  const hasAny   = total > 0;

  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 20,
      padding: '10px 16px',
      borderBottom: '1px solid var(--line-1)',
      borderLeft: `3px solid var(--good)`,
      background: hasAny ? 'rgba(74,158,98,0.05)' : 'var(--bg-0)',
      transition: 'background 600ms',
    }}>
      {/* Total */}
      <div style={{ flexShrink: 0 }}>
        <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-3)', letterSpacing: '0.14em', textTransform: 'uppercase', marginBottom: 4 }}>
          ASSET ISSUER EARNINGS
        </div>
        <div style={{
          fontFamily: 'var(--font-sans)', fontWeight: 500, fontSize: 22,
          color: hasAny ? 'var(--good)' : 'var(--fg-3)',
          lineHeight: 1, letterSpacing: '-0.015em', fontVariantNumeric: 'tabular-nums',
          transition: 'color 600ms',
        }}>
          {total.toFixed(1)}<span style={{ color: 'var(--fg-3)', fontSize: 12, marginLeft: 5 }}>SUI</span>
        </div>
      </div>

      {/* Breakdown when accruing */}
      {cs && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
          <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-3)', letterSpacing: '0.06em' }}>
            <span style={{ color: 'var(--fg-1)' }}>{settled.toFixed(1)} SUI</span> settled
          </div>
          <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-3)', letterSpacing: '0.06em' }}>
            <span style={{ color: 'var(--good)' }}>+{accruing.toFixed(1)} SUI</span> accruing this tenure
          </div>
        </div>
      )}

      {/* Idle hint */}
      {!cs && !hasAny && (
        <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-3)', letterSpacing: '0.06em' }}>
          accrues on first settlement
        </div>
      )}

      <div style={{ flex: 1 }} />

    </div>
  );
}


function StepBtn({ onClick, children }) {
  const holdRef = React.useRef(null);
  const start = (e) => {
    onClick(e);
    holdRef.current = setTimeout(() => {
      holdRef.current = setInterval(() => onClick(e), 80);
    }, 300);
  };
  const stop = () => {
    clearTimeout(holdRef.current);
    clearInterval(holdRef.current);
    holdRef.current = null;
  };
  return (
    <button
      onMouseDown={start} onMouseUp={stop} onMouseLeave={stop} onTouchStart={start} onTouchEnd={stop}
      style={{
        all: 'unset', boxSizing: 'border-box', cursor: 'pointer',
        padding: '4px 8px', background: 'var(--bg-2)', border: '1px solid var(--line-1)',
        fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-1)', lineHeight: 1,
      }}>{children}</button>
  );
}

function BotTenantRows({ sim }) {
  const askPrice = Sim.effectivePrice(sim);
  const [lastBid, setLastBid]     = React.useState(null);
  const [stickyMap, setStickyMap] = React.useState({}); // id → { addr, valuation, orangeAt }
  const orangeTimers              = React.useRef({});

  // Derive current bid synchronously from sim.phase — same render frame as LogStrip
  const syncBid = (() => {
    const p = sim.phase;
    if (p.kind === 'Occupied' && p.addr !== '0x__YOU__')
      return { addr: p.addr, amount: (p.stake / p.tenures).toFixed(1) };
    if (p.kind === 'Demand' && p.challenger.addr !== '0x__YOU__')
      return { addr: p.challenger.addr, amount: (p.challenger.stake / p.challenger.tenures).toFixed(1) };
    return null;
  })();

  // Persist the bid for 10s after the phase leaves (for the "bid placed" linger)
  React.useEffect(() => {
    if (!syncBid) return;
    setLastBid(prev => prev?.addr === syncBid.addr ? prev : { ...syncBid, realAt: Date.now() });
  }, [syncBid?.addr]);

  React.useEffect(() => {
    if (!lastBid) return;
    const ms = 10_000 - (Date.now() - lastBid.realAt);
    if (ms <= 0) { setLastBid(null); return; }
    const tid = setTimeout(() => setLastBid(null), ms);
    return () => clearTimeout(tid);
  }, [lastBid]);

  // Use syncBid (realtime) while phase is active; fall back to persisted lastBid after phase ends
  const activeBid = syncBid
    ? { ...syncBid, realAt: lastBid?.addr === syncBid.addr ? lastBid.realAt : Date.now() }
    : lastBid;

  const allWatching = sim.tenants
    .filter(t => t.state === 'Watching' && t.valuation >= Sim.SIM.cycle.floor)
    .sort((a, b) => b.valuation - a.valuation);

  // Record when each bot first becomes canBid; 5s real timer — not cancelled on departure
  React.useEffect(() => {
    allWatching.forEach(t => {
      if (isFinite(askPrice) && t.valuation >= askPrice && !orangeTimers.current[t.id]) {
        setStickyMap(prev => ({ ...prev, [t.id]: { addr: t.addr, valuation: t.valuation, orangeAt: Date.now() } }));
        orangeTimers.current[t.id] = setTimeout(() => {
          setStickyMap(prev => { const next = { ...prev }; delete next[t.id]; return next; });
          delete orangeTimers.current[t.id];
        }, 5000);
      }
    });
  }, [sim]);

  // Bid row: always "bid placed" immediately — syncs with LogStrip "acquired by"
  // Orange "val" stays only in the watching list (pre-bid state)
  const bidRow = activeBid ? [{
    key: 'bot-' + activeBid.addr, addr: activeBid.addr,
    status: activeBid.amount ? `bid placed · ${activeBid.amount} SUI →` : 'bid placed →',
    dot: 'var(--signal)', addr_c: 'var(--signal)', status_c: 'var(--signal)', bg: 'rgba(41,121,200,0.07)',
  }] : [];

  const watching = allWatching.filter(t => t.addr !== activeBid?.addr).slice(0, activeBid ? 2 : 3);
  const watchRows = watching.map(t => {
    const orange = !!(stickyMap[t.id] || (isFinite(askPrice) && t.valuation >= askPrice));
    return { key: 'bot-' + t.addr, addr: t.addr,
      status: `val · ${t.valuation.toFixed(1)} SUI`,
      dot:      orange ? 'var(--warn)' : 'var(--fg-3)',
      addr_c:   'var(--fg-3)',
      status_c: orange ? 'var(--warn)' : 'var(--fg-3)',
      bg: 'var(--bg-1)' };
  });

  const rows = [...bidRow, ...watchRows];
  if (rows.length === 0) return null;

  return rows.map(r => (
    <div key={r.key} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 10px', borderBottom: '1px solid var(--line-1)', background: r.bg, transition: 'background 400ms' }}>
      <span style={{ width: 5, height: 5, borderRadius: '50%', background: r.dot, transition: 'background 400ms', flexShrink: 0 }} />
      <span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: r.addr_c, letterSpacing: '0.04em', flex: 1 }}>{r.addr}</span>
      <span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: r.status_c, transition: 'color 400ms', letterSpacing: '0.04em' }}>{r.status}</span>
    </div>
  ));
}

function ManualBidRow({ sim, onBid }) {
  const [delta, setDelta] = React.useState(0);
  const phase = sim.phase.kind;
  if (phase === 'Retired') return null;

  const floor = (() => {
    const r = Sim.nextRentPrice(sim);
    return r != null ? r : Sim.livePrice(sim);
  })();
  const floorRef = React.useRef(floor);
  React.useEffect(() => {
    if (floorRef.current !== floor) { floorRef.current = floor; setDelta(0); }
  }, [floor]);

  const perTenure  = (floor || 0) + delta;
  const totalCost  = perTenure;
  const aboveFloor = delta > 0;
  const bidLabel   = phase === 'Demand' ? 'Overbid' : 'Bid';
  const stop       = e => e.stopPropagation();

  return (
    <div style={{ borderBottom: '1px solid var(--line-1)' }}>
      <div style={{ padding: '5px 12px', borderBottom: '1px solid var(--line-1)', background: 'var(--bg-2)' }}>
        <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-3)', letterSpacing: '0.12em', textTransform: 'uppercase' }}>
          ASSET PAYER POOL · <span style={{ color: 'var(--signal)' }}>+ you</span>
        </span>
      </div>
      <BotTenantRows sim={sim} />
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 10px', background: 'rgba(41,121,200,0.07)', flexWrap: 'wrap' }}>
        <span style={{ width: 7, height: 7, borderRadius: '50%', background: 'var(--signal)', boxShadow: '0 0 8px var(--signal)', flexShrink: 0 }} />
        <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600, color: 'var(--signal)', letterSpacing: '0.06em', flexShrink: 0 }}>You</span>

        <div style={{ display: 'flex', alignItems: 'center' }}>
          <StepBtn onClick={e => { e.stopPropagation(); setDelta(d => Math.max(0, d - 1)); }}>−</StepBtn>
          <div style={{
            padding: '4px 10px', fontFamily: 'var(--font-mono)', fontSize: 12,
            border: `1px solid ${aboveFloor ? 'var(--warn)' : 'var(--line-1)'}`,
            borderLeft: 0, borderRight: 0, background: 'var(--bg-0)',
            color: aboveFloor ? 'var(--warn)' : 'var(--fg-0)',
            whiteSpace: 'nowrap', letterSpacing: '0.04em', fontVariantNumeric: 'tabular-nums', minWidth: 60,
          }}>{perTenure.toFixed(1)} SUI</div>
          <StepBtn onClick={e => { e.stopPropagation(); setDelta(d => d + 1); }}>+</StepBtn>
        </div>

        <button onClick={() => onBid(1, perTenure)} style={{
          all: 'unset', boxSizing: 'border-box', cursor: 'pointer',
          padding: '6px 12px', background: 'var(--bg-2)', border: '1px solid var(--line-1)',
          fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-0)',
          letterSpacing: '0.06em', transition: 'border-color 120ms, color 120ms',
        }}
          onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--signal)'; e.currentTarget.style.color = 'var(--signal)'; }}
          onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--line-1)'; e.currentTarget.style.color = 'var(--fg-0)'; }}>
          {bidLabel} · {totalCost.toFixed(1)} SUI
        </button>

        {aboveFloor && <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--warn)', whiteSpace: 'nowrap', letterSpacing: '0.04em' }}>↑ raise the next floor to {(perTenure * (1 + Sim.SIM.escalation)).toFixed(1)} SUI</span>}
      </div>
    </div>
  );
}

/* ─── Log strip ─────────────────────────────────────────────── */
function LogStrip({ sim }) {
  const k = sim.phase.kind;
  const log = k === 'Idle'     ? `asset resting at floor price · ${Sim.SIM.cycle.floor.toFixed(1)} SUI`
            : k === 'Descent'  ? `auction in progress · bid now to acquire at ${Sim.livePrice(sim).toFixed(1)} SUI`
            : k === 'Occupied' ? (sim.phase.addr === '0x__YOU__'
                ? `acquired by you @ ${(sim.phase.stake / sim.phase.tenures).toFixed(1)} SUI · you can borrow the asset`
                : `acquired by ${sim.phase.addr} @ ${(sim.phase.stake / sim.phase.tenures).toFixed(1)} SUI · can borrow the asset`)
            : k === 'Demand'   ? `challenger ${sim.phase.challenger.addr === '0x__YOU__' ? 'you' : sim.phase.challenger.addr} outbid · handover queued`
            : `retire() · asset issuer earnings ${sim.ownerPool.toFixed(1)} SUI`;
  return (
    <div style={{ padding: '9px 14px', background: '#000', display: 'flex', alignItems: 'center', gap: 10, fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--fg-2)', letterSpacing: '0.04em' }}>
      <span style={{ color: 'var(--signal)' }}>›</span>
      <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>{log}</span>
    </div>
  );
}

/* ═══ Playground ═════════════════════════════════════════════════ */
function Playground() {
  const { sim, paused, setPaused, speed, setSpeed, reset, bid } = useSimulation();
  const phaseKind = sim.phase.kind;
  const meta      = PHASE_META[phaseKind];

  const auctionActive = phaseKind === 'Descent';
  const creditActive  = phaseKind === 'Occupied' || phaseKind === 'Demand';
  const auctionProg   = auctionActive ? Sim.descentProgress(sim) : 0;
  const creditProg    = creditActive  ? (Sim.creditState(sim)?.progress ?? 0) : 0;

  return (
    <section id="playground" className="section">
      <div className="container">
        <SectionDivider n={2} title="PLAYGROUND" arc="The Intuition" />
        <div className="section__inner">
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1.2fr', gap: 72, alignItems: 'start' }}>

            {/* ─── Left: narrative copy + FSM steps ─── */}
            <div>
              <Eyebrow style={{ marginBottom: 24 }}>LIVE SIMULATION<span style={{ color: 'var(--signal)' }}>  ·  How does it work?</span></Eyebrow>
              <h2 style={{
                fontFamily: 'var(--font-sans)', fontWeight: 500,
                fontSize: 'clamp(44px, 4.6vw, 72px)', lineHeight: 1.02, letterSpacing: '-0.028em',
                margin: 0, color: 'var(--fg-0)', textWrap: 'balance',
              }}>
                A rental,<br />happening now.
              </h2>

              <p style={{
                fontFamily: 'var(--font-sans)', fontSize: 21, lineHeight: 1.55,
                color: 'var(--fg-1)', marginTop: 24, maxWidth: 440, textWrap: 'pretty',
              }}>
                The usufruct rental engine, running in your browser — no wallet, no setup.
              </p>

              <p style={{
                fontFamily: 'var(--font-sans)', fontSize: 19, lineHeight: 1.55,
                color: 'var(--fg-2)', marginTop: 16, maxWidth: 440, textWrap: 'pretty',
                paddingLeft: 14, borderLeft: '2px solid var(--signal)',
              }}>
                An <strong style={{ color: 'var(--fg-0)', fontWeight: 500 }}>asset</strong> is any Sui object
                with <code style={{ fontFamily: 'var(--font-mono)', color: 'var(--signal)' }}>key + store</code> —
                an NFT, a DeFi position, a license, a capability, a vault, access to an AI agent, a dataset. Unlimited possibilities.
                <span style={{ display: 'block', marginTop: 10, color: 'var(--signal)', fontWeight: 500 }}>You build it, usufruct makes it rentable.</span>
              </p>

              <div style={{ marginTop: 36, display: 'flex', flexDirection: 'column', gap: 0 }}>
                {[
                  { body: 'An asset payer bids. Their stake drains as they use the asset.' },
                  { body: 'Any newcomer can displace them immediately — the ask price is always known.' },
                  { body: 'The asset issuer earns from every second occupied, across every handover.' },
                ].map(({ body }, i) => (
                  <div key={i} style={{
                    padding: '16px 0', display: 'flex', alignItems: 'baseline', gap: 12,
                    borderTop: '1px solid var(--line-1)',
                    borderBottom: i === 2 ? '1px solid var(--line-1)' : 'none',
                  }}>
                    <span style={{ fontFamily: 'var(--font-mono)', fontSize: 13, color: 'var(--signal)', flexShrink: 0 }}>→</span>
                    <span style={{ fontFamily: 'var(--font-sans)', fontSize: 23, color: 'var(--fg-1)', lineHeight: 1.5 }}>{body}</span>
                  </div>
                ))}
              </div>

              <div style={{ marginTop: 32, display: 'flex', animation: 'navCtaBreath 2.2s ease-in-out infinite' }}>
                <button
                  onClick={() => window.open('https://playground.usufruct.io/', '_blank', 'noopener')}
                  style={{
                    display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, width: '100%',
                    padding: '14px 24px', background: 'var(--signal)', border: 'none', cursor: 'pointer',
                    fontFamily: 'var(--font-mono)', fontSize: 16, fontWeight: 600,
                    color: 'var(--signal-fg)', letterSpacing: '0.04em', textTransform: 'uppercase',
                  }}
                >
                  <span>Try the full playground</span>
                  <span style={{ fontSize: 20, lineHeight: 1, fontWeight: 400 }}>↗</span>
                </button>
              </div>

            </div>

            {/* ─── Right: simulator ─── */}
            <div style={{
              background: 'var(--bg-1)', border: '1px solid var(--line-1)',
              overflow: 'hidden', boxShadow: '0 30px 80px -30px rgba(0,0,0,0.8)',
            }}>
              {/* Window chrome */}
              <div style={{
                display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                padding: '10px 14px', borderBottom: '1px solid var(--line-1)', background: 'var(--bg-2)',
              }}>
                <div style={{ display: 'flex', gap: 5 }}>
                  {[0, 1, 2].map(i => <span key={i} style={{ width: 8, height: 8, borderRadius: '50%', background: 'var(--line-1)' }} />)}
                </div>
                <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-2)', letterSpacing: '0.10em' }}>
                  playground.usufruct.io
                </span>
                <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--fg-3)', letterSpacing: '0.10em' }}>
                  escrow #0xa3..f1
                </span>
              </div>

              <StatePills phaseKind={phaseKind} />
              <SimControls paused={paused} setPaused={setPaused} speed={speed} setSpeed={setSpeed} reset={reset} />
              <NarrationBar phaseKind={phaseKind} />
              <KeyStats sim={sim} />
              <OwnerEarnings sim={sim} />

              {(phaseKind === 'Idle' || phaseKind === 'Descent') && (
                <ShapeChart
                  policyName="auction_shape" shape={Sim.SIM.auctionShape}
                  progress={auctionProg} showCrosshair={auctionActive}
                  yTopLabel="" yBotLabel="floor"
                  xCenterLabel="" xStartLabel="0" xEndLabel="end"
                  yAxisLabel="auction window"
                  hLineLabel="rent price"
                  vCrosshair={{ label: 'now', color: 'var(--fg-3)' }}
                  hCrosshair={{ label: 'price', color: 'var(--fg-3)' }}
                  curveColor="var(--fg-0)" markerColor="var(--fg-0)" dimmed={!auctionActive}
                />
              )}
              {(phaseKind === 'Occupied' || phaseKind === 'Demand') && (
                <ShapeChart
                  policyName="credit_shape" shape={Sim.SIM.creditShape}
                  progress={creditProg} showCrosshair={creditActive}
                  yTopLabel="100%" yBotLabel="0%"
                  xCenterLabel="" xEndLabel="100%"
                  yAxisLabel="tenure time"
                  hLineLabel="asset payer stake used"
                  vCrosshair={{ label: 'elapsed', color: 'var(--fg-3)' }}
                  hCrosshair={{ label: 'consumed', color: 'var(--fg-3)' }}
                  curveColor="var(--fg-0)" markerColor="var(--fg-0)" dimmed={false} invertY
                />
              )}

              <ManualBidRow sim={sim} onBid={bid} />
              <LogStrip sim={sim} />
            </div>

          </div>
        </div>
      </div>
    </section>
  );
}

window.Playground = Playground;
