// Verdict — live HL outcome-market block.
// Renders the (currently 1) live Hyperliquid HIP-4 binary outcome with streaming YES/NO prices.

(function () {
  const { useState, useEffect, useRef } = React;
  const FlashNum  = window.VDFlash.FlashNum;
  const TickArrow = window.VDFlash.TickArrow;

  function fmtCountdown(ms) {
    if (ms == null || isNaN(ms)) return '—';
    if (ms <= 0) return 'settling…';
    const s = Math.floor(ms / 1000);
    const h = Math.floor(s / 3600);
    const m = Math.floor((s % 3600) / 60);
    const sec = s % 60;
    if (h >= 24) {
      const d = Math.floor(h / 24);
      return `${d}d ${h % 24}h ${m}m`;
    }
    return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
  }

  function fmtCents(p) {
    if (p == null) return '—';
    // Show as cents (0..100) with 1 dp — matches Verdict's existing convention.
    return (p * 100).toFixed(1) + '¢';
  }

  function fmtSize(sz) {
    if (sz == null) return '—';
    if (sz >= 1000) return (sz / 1000).toFixed(1) + 'k';
    return Math.round(sz).toString();
  }

  function fmtUsd0(v) {
    if (v == null) return '—';
    return '$' + Math.round(v).toLocaleString();
  }

  function questionFromOutcome(o) {
    const p = o.parsed || {};
    if (p.class === 'priceBinary' && p.underlying && p.targetPrice) {
      return `${p.underlying} > $${(+p.targetPrice).toLocaleString()} at settle?`;
    }
    return o.name || 'Outcome';
  }

  function expirySublineFromOutcome(o) {
    const p = o.parsed || {};
    if (!p.expiryMs) return o.name || '';
    const d = new Date(p.expiryMs);
    const dateStr = d.toUTCString().replace('GMT', 'UTC');
    return `${p.period === '1d' ? 'Daily · ' : ''}${dateStr}`;
  }

  function StatusPill({ status }) {
    const tone =
      status === 'open' ? { label: 'Live', color: 'var(--mint)', dot: 'var(--mint)' } :
      status === 'connecting' ? { label: 'Connecting', color: 'var(--amber)', dot: 'var(--amber)' } :
      status === 'error' ? { label: 'Reconnecting', color: 'var(--coral)', dot: 'var(--coral)' } :
      { label: 'Offline', color: 'var(--text-3)', dot: 'var(--text-3)' };
    return (
      <span className="cap-sm" style={{ display: 'inline-flex', alignItems: 'center', gap: 6, color: tone.color }}>
        <span className="live-dot" style={{ background: tone.dot, width: 6, height: 6, borderRadius: '50%', display: 'inline-block' }} />
        {tone.label}
      </span>
    );
  }

  // Probability bar showing YES mid (cents) on a 0..100 scale.
  function ProbBar({ midProb }) {
    const pct = midProb != null ? Math.max(0, Math.min(1, midProb)) * 100 : 0;
    return (
      <div style={{ position: 'relative', height: 8, background: 'var(--bg-elev-3)', borderRadius: 'var(--r)' }}>
        <div style={{
          position: 'absolute', left: 0, top: 0, bottom: 0, width: pct + '%',
          background: 'linear-gradient(90deg, var(--mint-dim), var(--mint))',
          borderRadius: 'var(--r)',
          transition: 'width 240ms var(--ease)',
        }} />
        <div style={{
          position: 'absolute', left: pct + '%', top: -3, bottom: -3,
          width: 1, background: 'var(--text)', opacity: 0.85,
        }} />
      </div>
    );
  }

  function SideQuote({ label, color, bid, ask, bidSz, askSz, onClick }) {
    const mid = bid != null && ask != null ? (bid + ask) / 2 : (bid != null ? bid : ask);
    const spreadCents = bid != null && ask != null ? Math.round((ask - bid) * 100) : null;
    return (
      <div style={{
        border: '1px solid var(--line-subtle)', padding: '14px 16px',
        background: 'var(--bg-elev)',
      }}>
        <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 8 }}>
          <span className="cap-sm" style={{ color, fontWeight: 600, display: 'inline-flex', alignItems: 'center', gap: 4 }}>
            {label} <TickArrow value={mid} />
          </span>
          <span className="num" style={{ fontSize: 11, color: 'var(--text-3)' }}>
            {spreadCents != null ? `spread ${spreadCents}¢` : '—'}
          </span>
        </div>
        <div className="num" style={{ fontSize: 32, fontWeight: 500, color, letterSpacing: '-0.02em', lineHeight: 1 }}>
          <FlashNum value={mid}>{fmtCents(mid)}</FlashNum>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginTop: 12 }}>
          <div>
            <div className="cap-sm" style={{ marginBottom: 2 }}>Bid</div>
            <div className="num" style={{ fontSize: 13 }}>{fmtCents(bid)}</div>
            <div className="num" style={{ fontSize: 10, color: 'var(--text-3)' }}>{fmtSize(bidSz)}</div>
          </div>
          <div>
            <div className="cap-sm" style={{ marginBottom: 2 }}>Ask</div>
            <div className="num" style={{ fontSize: 13 }}>{fmtCents(ask)}</div>
            <div className="num" style={{ fontSize: 10, color: 'var(--text-3)' }}>{fmtSize(askSz)}</div>
          </div>
        </div>
        {onClick && (
          <button
            onClick={(e) => { e.stopPropagation(); onClick(); }}
            className="cap-sm"
            style={{
              marginTop: 12, width: '100%', padding: '8px 0',
              border: `1px solid ${color}`, color,
              background: 'transparent',
              transition: 'background var(--d-fast) var(--ease)',
            }}
            onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg-elev-2)'}
            onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
          >
            Buy {label} →
          </button>
        )}
      </div>
    );
  }

  function OutcomeCard({ outcome, btcMid, onOpen }) {
    const [, force] = useState(0);
    useEffect(() => {
      const id = setInterval(() => force((n) => n + 1), 1000);
      return () => clearInterval(id);
    }, []);

    const yes = outcome.books.yes || {};
    const no = outcome.books.no || {};
    const midProb = outcome.mid; // YES mid 0..1
    const target = outcome.parsed.targetPriceNum;
    const expiryMs = outcome.parsed.expiryMs;
    const distance = btcMid != null && target != null ? ((btcMid - target) / target) * 100 : null;
    const ChartC = window.VDLiveChart && window.VDLiveChart.LiveBTCChart;

    return (
      <div onClick={onOpen} className="card" style={{
        padding: 24, border: '1px solid var(--line)',
        background: 'linear-gradient(180deg, var(--bg-elev-2), var(--bg-elev))',
        cursor: onOpen ? 'pointer' : 'default',
        transition: 'border-color var(--d-fast) var(--ease)',
      }}
      onMouseEnter={(e) => { if (onOpen) e.currentTarget.style.borderColor = 'var(--acid)'; }}
      onMouseLeave={(e) => { if (onOpen) e.currentTarget.style.borderColor = 'var(--line)'; }}>
        {/* Header: question + countdown */}
        <div style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: 24, alignItems: 'flex-start', marginBottom: 18 }}>
          <div>
            <div className="cap-sm" style={{ marginBottom: 8, color: 'var(--acid)' }}>↳ Hyperliquid HIP-4 · Outcome #{outcome.outcome}</div>
            <div className="serif" style={{ fontSize: 28, lineHeight: 1.15, letterSpacing: '-0.02em' }}>
              {questionFromOutcome(outcome)}
            </div>
            <div style={{ color: 'var(--text-3)', fontSize: 12, marginTop: 6 }}>
              {expirySublineFromOutcome(outcome)}
            </div>
          </div>
          <div style={{ textAlign: 'right' }}>
            <div className="cap-sm" style={{ marginBottom: 4 }}>Settles in</div>
            <div className="num" style={{ fontSize: 22, fontWeight: 500, letterSpacing: '-0.02em' }}>
              {fmtCountdown(expiryMs ? expiryMs - Date.now() : null)}
            </div>
          </div>
        </div>

        {/* Underlying spot vs target */}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12, marginBottom: 18, padding: '12px 0', borderTop: '1px solid var(--line-subtle)', borderBottom: '1px solid var(--line-subtle)' }}>
          <div>
            <div className="cap-sm" style={{ marginBottom: 4, display: 'inline-flex', alignItems: 'center', gap: 4 }}>
              BTC spot (HL) <TickArrow value={btcMid} />
            </div>
            <div className="num" style={{ fontSize: 18, fontWeight: 500 }}>
              <FlashNum value={btcMid}>{fmtUsd0(btcMid)}</FlashNum>
            </div>
          </div>
          <div>
            <div className="cap-sm" style={{ marginBottom: 4 }}>Target</div>
            <div className="num" style={{ fontSize: 18, fontWeight: 500 }}>{fmtUsd0(target)}</div>
          </div>
          <div>
            <div className="cap-sm" style={{ marginBottom: 4 }}>Distance</div>
            <div className="num" style={{
              fontSize: 18, fontWeight: 500,
              color: distance == null ? 'var(--text)' : (distance >= 0 ? 'var(--mint)' : 'var(--coral)'),
            }}>
              <FlashNum value={distance}>
                {distance == null ? '—' : (distance >= 0 ? '+' : '') + distance.toFixed(2) + '%'}
              </FlashNum>
            </div>
          </div>
        </div>

        {/* Mini live BTC chart with target line — preview of the full chart in the detail view */}
        {ChartC && (
          <div style={{ marginBottom: 18 }} onClick={(e) => e.stopPropagation()}>
            <ChartC targetPrice={target} height={180} compact={false} />
          </div>
        )}

        {/* Implied prob bar */}
        <div style={{ marginBottom: 18 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
            <span className="cap-sm" style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
              YES implied probability <TickArrow value={midProb} />
            </span>
            <span className="num" style={{ fontSize: 12, color: 'var(--mint)' }}>
              <FlashNum value={midProb}>{midProb != null ? (midProb * 100).toFixed(1) + '%' : '—'}</FlashNum>
            </span>
          </div>
          <ProbBar midProb={midProb} />
        </div>

        {/* YES / NO quotes */}
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
          <SideQuote
            label={outcome.yesLabel}
            color="var(--mint)"
            bid={yes.bid} ask={yes.ask} bidSz={yes.bidSz} askSz={yes.askSz}
            onClick={onOpen}
          />
          <SideQuote
            label={outcome.noLabel}
            color="var(--coral)"
            bid={no.bid} ask={no.ask} bidSz={no.bidSz} askSz={no.askSz}
            onClick={onOpen}
          />
        </div>

        {/* Hint */}
        <div className="cap-sm" style={{ marginTop: 14, textAlign: 'right', color: 'var(--text-3)' }}>
          click for chart, full order book, and trade →
        </div>
      </div>
    );
  }

  function shortAddr(a) { return a ? a.slice(0, 6) + '…' + a.slice(-4) : ''; }

  // Self-contained pill: subscribes to VDLive, handles connect/disconnect, used in TopNav.
  function WalletPill() {
    const [wallet, setWallet] = useState(window.VDLive ? window.VDLive.state.wallet : { address: null });
    const [err, setErr] = useState(null);
    const [busy, setBusy] = useState(false);
    useEffect(() => {
      if (!window.VDLive) return;
      return window.VDLive.subscribe((s) => setWallet({ ...s.wallet }));
    }, []);
    async function onConnect() {
      try { setErr(null); setBusy(true); await window.VDLive.connectWallet(); }
      catch (e) { setErr(e.message || String(e)); }
      finally { setBusy(false); }
    }
    function onDisconnect() { window.VDLive.disconnectWallet(); }

    if (!wallet || !wallet.address) {
      return (
        <button onClick={onConnect} disabled={busy} title={err || 'Connect injected wallet (MetaMask, Rabby, etc.)'} style={{
          height: 36, padding: '0 14px', borderRadius: 4,
          border: '1px solid var(--acid)',
          color: busy ? 'var(--text-3)' : 'var(--acid)',
          background: busy ? 'var(--bg-elev-2)' : 'var(--acid-bg10)',
          fontFamily: 'JetBrains Mono, monospace', fontSize: 11, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase',
          display: 'inline-flex', alignItems: 'center', gap: 8,
          cursor: busy ? 'wait' : 'pointer',
        }}>
          <span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--acid)', boxShadow: '0 0 6px var(--acid)' }} />
          {busy ? 'Connecting…' : 'Connect Wallet'}
        </button>
      );
    }
    return (
      <button onClick={onDisconnect} title="Click to disconnect" style={{
        height: 36, padding: '0 14px', borderRadius: 4,
        border: '1px solid var(--line)', background: 'var(--bg-elev-2)',
        color: 'var(--text)',
        fontFamily: 'JetBrains Mono, monospace', fontSize: 12, fontWeight: 500, letterSpacing: '0.04em',
        display: 'inline-flex', alignItems: 'center', gap: 8,
      }}>
        <span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--mint)', boxShadow: '0 0 6px var(--mint)' }} />
        {shortAddr(wallet.address)}
      </button>
    );
  }

  // ───────────────────────── Bucket card (multi-outcome questions) ─────────────────────────
  // Renders a question's named outcomes as a horizontal probability distribution.
  // Each row = one bucket; click any row to open that specific bucket's detail.
  function BucketCard({ question, btcMid, onOpen }) {
    const [, force] = useState(0);
    useEffect(() => {
      const id = setInterval(() => force(n => n + 1), 1000);
      return () => clearInterval(id);
    }, []);

    const expiryMs = question.parsed && question.parsed.expiryMs;
    const buckets = question.namedOutcomes || [];
    // Derive a "winning" bucket given current spot — purely visual hint
    const ths = (question.parsed && question.parsed.priceThresholdNums) || [];
    const winningIdx = (() => {
      if (btcMid == null || !ths.length) return -1;
      for (let i = 0; i < ths.length; i++) if (btcMid < ths[i]) return i;
      return ths.length;
    })();

    // Total implied prob (should be ~100%, but live spreads inflate)
    const sumProb = buckets.reduce((s, b) => s + (b.mid || 0), 0);

    return (
      <div className="card" style={{
        padding: 24, border: '1px solid var(--line)',
        background: 'linear-gradient(180deg, var(--bg-elev-2), var(--bg-elev))',
      }}>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: 24, alignItems: 'flex-start', marginBottom: 18 }}>
          <div>
            <div className="cap-sm" style={{ marginBottom: 8, color: 'var(--acid)' }}>↳ HIP-4 question · Multi-bucket · {buckets.length} ranges</div>
            <div className="serif" style={{ fontSize: 26, lineHeight: 1.15, letterSpacing: '-0.02em' }}>
              Where does {question.parsed.underlying || 'BTC'} settle?
            </div>
            <div style={{ color: 'var(--text-3)', fontSize: 12, marginTop: 6 }}>
              {question.parsed.period === '1d' ? 'Daily · ' : ''}
              {expiryMs ? new Date(expiryMs).toUTCString().replace('GMT','UTC') : '—'}
            </div>
          </div>
          <div style={{ textAlign: 'right' }}>
            <div className="cap-sm" style={{ marginBottom: 4 }}>Settles in</div>
            <div className="num" style={{ fontSize: 22, fontWeight: 500, letterSpacing: '-0.02em' }}>
              {expiryMs ? fmtCountdown(expiryMs - Date.now()) : '—'}
            </div>
          </div>
        </div>

        {/* Spot vs buckets context */}
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 18, padding: '12px 0', borderTop: '1px solid var(--line-subtle)', borderBottom: '1px solid var(--line-subtle)' }}>
          <div>
            <div className="cap-sm" style={{ marginBottom: 4, display: 'inline-flex', alignItems: 'center', gap: 4 }}>
              {question.parsed.underlying || 'BTC'} spot <TickArrow value={btcMid} />
            </div>
            <div className="num" style={{ fontSize: 18, fontWeight: 500 }}>
              <FlashNum value={btcMid}>{fmtUsd0(btcMid)}</FlashNum>
            </div>
          </div>
          <div>
            <div className="cap-sm" style={{ marginBottom: 4 }}>Implied total</div>
            <div className="num" style={{ fontSize: 14, color: 'var(--text-2)' }}>
              {(sumProb * 100).toFixed(1)}%
              <span className="cap-sm" style={{ marginLeft: 6, color: 'var(--text-4)' }}>
                spread inflation: +{((sumProb - 1) * 100).toFixed(1)}%
              </span>
            </div>
          </div>
        </div>

        {/* Bucket rows */}
        <div style={{ display: 'grid', gap: 6 }}>
          {buckets.map((b, i) => {
            const prob = b.mid;
            const yes = b.books.yes || {};
            const isWinning = i === winningIdx;
            return (
              <button key={b.outcome} onClick={(e) => { e.stopPropagation(); onOpen(b.outcome); }} style={{
                display: 'grid', gridTemplateColumns: '180px 1fr 90px 90px',
                alignItems: 'center', gap: 14,
                padding: '12px 14px',
                background: 'var(--bg-elev)',
                border: '1px solid ' + (isWinning ? 'var(--mint)' : 'var(--line-subtle)'),
                textAlign: 'left',
                transition: 'border-color var(--d-fast) var(--ease), background var(--d-fast) var(--ease)',
              }}
              onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--bg-elev-2)'; }}
              onMouseLeave={(e) => { e.currentTarget.style.background = 'var(--bg-elev)'; }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                  {isWinning && <span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--mint)', boxShadow: '0 0 6px var(--mint)' }} />}
                  <span className="num" style={{ fontSize: 12.5, fontWeight: 600 }}>{b.bucketLabel || b.name}</span>
                </div>
                {/* Probability bar */}
                <div style={{ position: 'relative', height: 10, background: 'var(--bg-elev-3)', borderRadius: 'var(--r)' }}>
                  <div style={{
                    position: 'absolute', left: 0, top: 0, bottom: 0,
                    width: ((prob || 0) * 100).toFixed(1) + '%',
                    background: 'linear-gradient(90deg, var(--mint-dim), var(--mint))',
                    borderRadius: 'var(--r)',
                    transition: 'width 240ms var(--ease)',
                  }} />
                </div>
                <div className="num" style={{ textAlign: 'right', fontSize: 14, fontWeight: 600, color: 'var(--mint)' }}>
                  <FlashNum value={prob}>{prob != null ? (prob * 100).toFixed(1) + '%' : '—'}</FlashNum>
                </div>
                <div className="num" style={{ textAlign: 'right', fontSize: 11, color: 'var(--text-3)' }}>
                  {yes.bid != null ? fmtCents(yes.bid) : '—'} / {yes.ask != null ? fmtCents(yes.ask) : '—'}
                </div>
              </button>
            );
          })}
        </div>

        <div className="cap-sm" style={{ marginTop: 14, textAlign: 'right', color: 'var(--text-3)' }}>
          click any bucket for chart, full order book, and trade →
        </div>
      </div>
    );
  }

  // ───────────────────────── Filter bar ─────────────────────────
  function FilterBar({ filter, setFilter, sort, setSort, counts }) {
    const filters = [
      { key: 'all',    label: 'All',     count: counts.all },
      { key: 'binary', label: 'Binary',  count: counts.binary },
      { key: 'bucket', label: 'Multi',   count: counts.bucket },
      { key: 'today',  label: 'Settles today',   count: counts.today },
    ];
    const sorts = [
      { key: 'expiry', label: 'Soonest' },
      { key: 'liquid', label: 'Tightest spread' },
      { key: 'extreme', label: 'Most extreme' },
    ];
    return (
      <div style={{ display: 'flex', gap: 16, alignItems: 'center', flexWrap: 'wrap', marginBottom: 14, padding: '10px 0', borderBottom: '1px solid var(--line-subtle)' }}>
        <div style={{ display: 'flex', gap: 6 }}>
          {filters.map(f => {
            const active = f.key === filter;
            return (
              <button key={f.key} onClick={() => setFilter(f.key)} style={{
                padding: '6px 12px',
                background: active ? 'var(--acid-bg10)' : 'transparent',
                border: '1px solid ' + (active ? 'var(--acid)' : 'var(--line-subtle)'),
                color: active ? 'var(--acid)' : 'var(--text-2)',
                fontFamily: 'JetBrains Mono, monospace', fontSize: 11, fontWeight: 600, letterSpacing: '0.06em',
                display: 'inline-flex', alignItems: 'center', gap: 6,
              }}>
                {f.label}
                <span className="num" style={{
                  fontSize: 9.5, color: active ? 'var(--acid-dim)' : 'var(--text-4)',
                  padding: '1px 5px', border: '1px solid currentColor', borderRadius: 999,
                }}>{f.count}</span>
              </button>
            );
          })}
        </div>
        <div style={{ flex: 1 }} />
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span className="cap-sm">Sort</span>
          <div style={{ display: 'flex', border: '1px solid var(--line-subtle)' }}>
            {sorts.map(s => {
              const active = s.key === sort;
              return (
                <button key={s.key} onClick={() => setSort(s.key)} className="cap-sm" style={{
                  padding: '6px 10px',
                  background: active ? 'var(--bg-elev-2)' : 'transparent',
                  color: active ? 'var(--text)' : 'var(--text-3)',
                  borderRight: '1px solid var(--line-subtle)',
                }}>{s.label}</button>
              );
            })}
          </div>
        </div>
      </div>
    );
  }

  function LiveBlock() {
    const [snap, setSnap] = useState(window.VDLive ? window.VDLive.state : null);
    const [openId, setOpenId] = useState(null);
    const [filter, setFilter] = useState('all');
    const [sort, setSort] = useState('expiry');

    useEffect(() => {
      if (!window.VDLive) return;
      return window.VDLive.subscribe((s) => setSnap({
        ...s,
        outcomes: s.outcomes.map(o => ({ ...o, books: { ...o.books }, fullBook: { ...o.fullBook } })),
        standalone: (s.standalone || []).slice(),
        questions: (s.questions || []).slice(),
        wallet: { ...s.wallet },
        btc: { ...s.btc },
      }));
    }, []);

    async function onConnect() {
      try { await window.VDLive.connectWallet(); }
      catch (e) { /* surfaced inside WalletPill / DetailModal */ }
    }

    if (!snap) return null;

    // Build unified market list
    const standalone = (snap.standalone && snap.standalone.length) ? snap.standalone : (snap.outcomes || []);
    const questions = snap.questions || [];

    // Each item: {kind, key, expiryMs, spreadAvg, extremeness, payload}
    const items = [];
    for (const o of standalone) {
      const expiryMs = o.parsed && o.parsed.expiryMs;
      const spread = o.books.yes && o.books.yes.bid != null && o.books.yes.ask != null
        ? (o.books.yes.ask - o.books.yes.bid) : 1;
      const extremeness = o.mid != null ? Math.abs(o.mid - 0.5) : 0;
      items.push({ kind: 'binary', key: 'b' + o.outcome, expiryMs, spread, extremeness, payload: o });
    }
    for (const q of questions) {
      const expiryMs = q.parsed && q.parsed.expiryMs;
      const spreads = q.namedOutcomes
        .map(b => (b.books.yes && b.books.yes.bid != null && b.books.yes.ask != null) ? b.books.yes.ask - b.books.yes.bid : null)
        .filter(s => s != null);
      const spreadAvg = spreads.length ? spreads.reduce((a, b) => a + b, 0) / spreads.length : 1;
      // Extremeness: max bucket prob (most "decided" market = most concentrated)
      const extremeness = Math.max(...q.namedOutcomes.map(b => b.mid || 0), 0);
      items.push({ kind: 'bucket', key: 'q' + q.question, expiryMs, spread: spreadAvg, extremeness, payload: q });
    }

    // Filter
    const todayCutoff = (() => {
      const d = new Date();
      d.setUTCHours(23, 59, 59, 999);
      return d.getTime();
    })();
    function passesFilter(it) {
      if (filter === 'all') return true;
      if (filter === 'binary') return it.kind === 'binary';
      if (filter === 'bucket') return it.kind === 'bucket';
      if (filter === 'today')  return it.expiryMs && it.expiryMs <= todayCutoff;
      return true;
    }
    const counts = {
      all:    items.length,
      binary: items.filter(it => it.kind === 'binary').length,
      bucket: items.filter(it => it.kind === 'bucket').length,
      today:  items.filter(it => it.expiryMs && it.expiryMs <= todayCutoff).length,
    };
    let visible = items.filter(passesFilter);

    // Sort
    if (sort === 'expiry')  visible = visible.slice().sort((a, b) => (a.expiryMs || Infinity) - (b.expiryMs || Infinity));
    if (sort === 'liquid')  visible = visible.slice().sort((a, b) => a.spread - b.spread);
    if (sort === 'extreme') visible = visible.slice().sort((a, b) => b.extremeness - a.extremeness);

    // Resolve openId — could be either a binary outcome or a bucket outcome
    const flatOutcomes = snap.outcomes || [];
    const openOutcome = openId != null ? flatOutcomes.find(o => o.outcome === openId) : null;
    const Detail = window.VDLiveDetail && window.VDLiveDetail.DetailModal;
    const btcMid = snap.btc && snap.btc.mid;

    return (
      <div style={{ marginTop: 32 }}>
        <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 14, gap: 12 }}>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
            <span className="cap" style={{ color: 'var(--text-2)' }}>↳ Live on Hyperliquid · HIP-4 outcome markets</span>
          </div>
          <StatusPill status={snap.status} />
        </div>

        {items.length === 0 ? (
          <div className="card" style={{ padding: 24, textAlign: 'center', color: 'var(--text-3)' }}>
            <div className="cap-sm" style={{ marginBottom: 8 }}>No outcomes returned</div>
            <div style={{ fontSize: 12 }}>Awaiting outcomeMeta from api.hyperliquid.xyz…</div>
          </div>
        ) : (
          <FilterBar filter={filter} setFilter={setFilter} sort={sort} setSort={setSort} counts={counts} />
        )}

        <div style={{ display: 'grid', gap: 16 }}>
          {visible.map((it) => {
            if (it.kind === 'binary') {
              return (
                <OutcomeCard key={it.key} outcome={it.payload} btcMid={btcMid}
                  onOpen={() => setOpenId(it.payload.outcome)} />
              );
            }
            return (
              <BucketCard key={it.key} question={it.payload} btcMid={btcMid}
                onOpen={(outcomeId) => setOpenId(outcomeId)} />
            );
          })}

          {visible.length === 0 && (
            <div className="card" style={{ padding: 24, textAlign: 'center', color: 'var(--text-3)' }}>
              <div className="cap-sm">No markets match this filter.</div>
            </div>
          )}
        </div>

        {openOutcome && Detail && (
          <Detail
            outcome={openOutcome}
            btcMid={btcMid}
            wallet={snap.wallet}
            onConnect={onConnect}
            onClose={() => setOpenId(null)}
          />
        )}
      </div>
    );
  }

  window.VDLiveBlock = { LiveBlock, WalletPill };
})();
