// Verdict — live trade tape (recent activity) + mispricing scanner.
// Activity feed: streams every HIP-4 trade print live from the WS trades channel.
// Scanner: derives box-arb opportunities (binary) and bucket-arb opportunities (multi-bucket)
// in real time from the books we already subscribe to.

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

  function fmtCents(p) { return p == null ? '—' : (p * 100).toFixed(1) + '¢'; }
  function fmtSize(sz) {
    if (sz == null) return '—';
    if (Math.abs(sz) >= 1000) return (sz / 1000).toFixed(2) + 'k';
    return Math.abs(sz) >= 1 ? (+sz).toFixed(0) : (+sz).toFixed(2);
  }
  function fmtUsd2(v) { if (v == null) return '—'; return (v < 0 ? '-$' : '$') + Math.abs(v).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }
  function fmtUsd0(v) { if (v == null) return '—'; const n = Math.round(v); return (n < 0 ? '-$' : '$') + Math.abs(n).toLocaleString(); }
  function fmtTimeAgo(ms) {
    if (!ms) return '—';
    const d = Date.now() - ms;
    if (d < 5_000) return 'now';
    if (d < 60_000) return Math.floor(d / 1000) + 's';
    if (d < 3_600_000) return Math.floor(d / 60_000) + 'm';
    if (d < 86_400_000) return Math.floor(d / 3_600_000) + 'h';
    return Math.floor(d / 86_400_000) + 'd';
  }

  // ───────────── Live trade tape ─────────────
  function ActivityFeed() {
    const [snap, setSnap] = useState(window.VDLive ? window.VDLive.state : null);
    const [, force] = useState(0);
    useEffect(() => {
      if (!window.VDLive) return;
      return window.VDLive.subscribe((s) => setSnap({
        ...s, recentTrades: (s.recentTrades || []).slice(),
      }));
    }, []);
    // Re-render every second so the "time-ago" label ticks
    useEffect(() => {
      const id = setInterval(() => force(n => n + 1), 1000);
      return () => clearInterval(id);
    }, []);

    if (!snap) return null;
    const trades = snap.recentTrades || [];

    // Aggregate stats over the last hour (or what we have)
    const now = Date.now();
    const lastHour = trades.filter(t => now - t.time < 60 * 60 * 1000);
    const totalNotional = lastHour.reduce((s, t) => s + t.px * t.sz, 0);
    const buyCount = lastHour.filter(t => t.tone === 'mint').length;
    const sellCount = lastHour.filter(t => t.tone === 'coral').length;

    return (
      <div>
        <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 14, gap: 12 }}>
          <span className="cap" style={{ color: 'var(--text-2)' }}>↳ Live activity · HIP-4 trades</span>
          <span className="cap-sm" style={{ color: 'var(--text-3)' }}>
            <span className="live-dot" style={{ display: 'inline-block', width: 6, height: 6, borderRadius: '50%', background: 'var(--mint)', marginRight: 6 }} />
            streaming
          </span>
        </div>

        {/* Stats row */}
        <div className="card" style={{ padding: 0, marginBottom: 12 }}>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', padding: '12px 18px' }}>
            <div>
              <div className="cap-sm" style={{ marginBottom: 4 }}>Trades · 1h</div>
              <div className="num" style={{ fontSize: 18, fontWeight: 600 }}>
                <FlashNum value={lastHour.length}>{lastHour.length}</FlashNum>
              </div>
            </div>
            <div>
              <div className="cap-sm" style={{ marginBottom: 4 }}>Notional · 1h</div>
              <div className="num" style={{ fontSize: 18, fontWeight: 600 }}>
                <FlashNum value={totalNotional}>{fmtUsd0(totalNotional)}</FlashNum>
              </div>
            </div>
            <div>
              <div className="cap-sm" style={{ marginBottom: 4 }}>Yes-up / Yes-down</div>
              <div className="num" style={{ fontSize: 18, fontWeight: 600 }}>
                <span style={{ color: 'var(--mint)' }}>{buyCount}</span>
                <span style={{ color: 'var(--text-4)', margin: '0 6px' }}>/</span>
                <span style={{ color: 'var(--coral)' }}>{sellCount}</span>
              </div>
            </div>
          </div>
        </div>

        <div className="card" style={{ padding: 0, maxHeight: 400, overflowY: 'auto' }}>
          {trades.length === 0 ? (
            <div style={{ padding: 28, textAlign: 'center', color: 'var(--text-3)', fontSize: 12 }}>
              Listening for trades… the tape will fill as orders cross.
            </div>
          ) : (
            trades.map((t, i) => {
              const color = t.tone === 'mint' ? 'var(--mint)' : 'var(--coral)';
              const sideTone = t.sideName === 'YES' ? 'var(--mint)' : 'var(--coral)';
              return (
                <div key={t.hash || (t.time + ':' + t.coin + ':' + i)} style={{
                  display: 'grid', gridTemplateColumns: '40px 80px 1.2fr 1fr 1fr 60px',
                  alignItems: 'center', padding: '8px 14px',
                  borderBottom: i === trades.length - 1 ? 'none' : '1px solid var(--line-subtle)',
                  gap: 10,
                  // Subtle colored left edge for direction
                  borderLeft: `2px solid ${color}`,
                  paddingLeft: 12,
                  // Fade in newest trades — top 5 get a shimmer
                  background: i < 3 ? 'var(--bg-elev-2)' : 'transparent',
                  transition: 'background 600ms var(--ease)',
                }}>
                  <span className="cap-sm" style={{
                    color, fontWeight: 700, letterSpacing: '0.04em', fontSize: 9.5,
                  }}>{t.taker}</span>
                  <span className="cap-sm" style={{
                    color: sideTone, fontWeight: 700, letterSpacing: '0.04em', fontSize: 9.5,
                    padding: '2px 6px', border: `1px solid ${sideTone}`, textAlign: 'center',
                  }}>{t.sideName}</span>
                  <span style={{ fontSize: 12, color: 'var(--text-2)' }}>{t.label}</span>
                  <span className="num" style={{ textAlign: 'right', fontSize: 12, fontWeight: 600 }}>
                    {fmtSize(t.sz)} <span style={{ color: 'var(--text-3)' }}>×</span> {fmtCents(t.px)}
                  </span>
                  <span className="num" style={{ textAlign: 'right', fontSize: 11, color: 'var(--text-3)' }}>
                    {fmtUsd2(t.px * t.sz)}
                  </span>
                  <span className="cap-sm" style={{ textAlign: 'right', fontSize: 9.5, color: 'var(--text-3)' }}>
                    {fmtTimeAgo(t.time)}
                  </span>
                </div>
              );
            })
          )}
        </div>
      </div>
    );
  }

  // ───────────── Mispricing scanner ─────────────
  // Looks at every HIP-4 market's full ask side and surfaces real arbitrage opportunities:
  //   • Binary box: YES_ask + NO_ask < 1.0  → buy both, collect $1 - cost at settle
  //   • Bucket box: sum(bucket YES asks) < 1.0  → buy all buckets, exactly one wins
  // Only opportunities with positive edge after a conservative fee assumption show up.
  function MispricingScanner({ onOpen }) {
    const [snap, setSnap] = useState(window.VDLive ? window.VDLive.state : null);
    useEffect(() => {
      if (!window.VDLive) return;
      return window.VDLive.subscribe((s) => setSnap(s));
    }, []);
    if (!snap) return null;

    // HL taker fee on outcomes is ~0.025%; conservatively use 0.5¢ per dollar (5bps).
    const ESTIMATED_FEE_PER_DOLLAR = 0.005;

    const opps = [];

    // Binary box arb: YES_ask + NO_ask < 1
    const standalone = (snap.standalone && snap.standalone.length) ? snap.standalone : (snap.outcomes || []);
    for (const o of standalone) {
      const ya = o.books && o.books.yes && o.books.yes.ask;
      const na = o.books && o.books.no  && o.books.no.ask;
      if (ya == null || na == null) continue;
      const cost = ya + na;
      const grossEdge = 1 - cost;
      const netEdge = grossEdge - ESTIMATED_FEE_PER_DOLLAR;
      // Liquidity: per-side ask size (smaller of the two)
      const yaSz = (o.books.yes.askSz || 0);
      const naSz = (o.books.no.askSz || 0);
      const maxContracts = Math.min(yaSz, naSz);
      if (grossEdge > 0) {
        opps.push({
          kind: 'box', outcome: o,
          label: `${o.parsed.underlying || ''} > $${(o.parsed.targetPriceNum || 0).toLocaleString()}`,
          cost, grossEdge, netEdge, maxContracts,
          components: [{ side: 'YES', ask: ya }, { side: 'NO', ask: na }],
        });
      }
    }

    // Bucket box arb: sum of all bucket YES asks (incl. fallback) < 1
    for (const q of (snap.questions || [])) {
      const buckets = (q.namedOutcomes || []).slice();
      if (q.fallbackOutcome) buckets.push(q.fallbackOutcome);
      const asks = buckets.map(b => b.books && b.books.yes && b.books.yes.ask);
      if (asks.some(a => a == null)) continue;
      const cost = asks.reduce((s, a) => s + a, 0);
      const grossEdge = 1 - cost;
      const netEdge = grossEdge - asks.length * ESTIMATED_FEE_PER_DOLLAR;
      const sizes = buckets.map(b => (b.books.yes && b.books.yes.askSz) || 0);
      const maxContracts = Math.min(...sizes);
      if (grossEdge > 0) {
        opps.push({
          kind: 'bucket-box', question: q,
          label: `${q.parsed.underlying || ''} settle range · ${buckets.length} buckets`,
          cost, grossEdge, netEdge, maxContracts,
          components: buckets.map((b, i) => ({ side: b.bucketLabel || 'fallback', ask: asks[i] })),
        });
      }
    }

    // Sort by gross edge desc
    opps.sort((a, b) => b.grossEdge - a.grossEdge);

    return (
      <div>
        <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 14, gap: 12 }}>
          <span className="cap" style={{ color: 'var(--text-2)' }}>↳ Mispricing scanner · live arb</span>
          <span className="cap-sm" style={{ color: 'var(--text-3)' }}>
            scanning {standalone.length + (snap.questions || []).length} markets
          </span>
        </div>
        <div className="card" style={{ padding: 0 }}>
          {opps.length === 0 ? (
            <div style={{ padding: 24, textAlign: 'center', color: 'var(--text-3)' }}>
              <div className="cap-sm" style={{ marginBottom: 6 }}>No live arb opportunities</div>
              <div style={{ fontSize: 11.5, lineHeight: 1.5 }}>
                Every market's combined YES/NO ask sums to ≥ $1.00 right now.
                Spreads tighten through the day; check back when liquidity thins out (overnight, around new question launches).
              </div>
            </div>
          ) : (
            opps.map((op, i) => {
              const profitable = op.netEdge > 0;
              return (
                <div key={i} onClick={() => onOpen && op.outcome && onOpen(op.outcome.outcome)} style={{
                  display: 'grid', gridTemplateColumns: '90px 1.4fr 100px 100px 1fr',
                  alignItems: 'center', padding: '14px 16px', gap: 14,
                  borderBottom: i === opps.length - 1 ? 'none' : '1px solid var(--line-subtle)',
                  borderLeft: '3px solid ' + (profitable ? 'var(--mint)' : 'var(--amber)'),
                  paddingLeft: 13,
                  cursor: op.outcome ? 'pointer' : 'default',
                  transition: 'background var(--d-fast) var(--ease)',
                }}
                onMouseEnter={(e) => { if (op.outcome) e.currentTarget.style.background = 'var(--bg-elev-2)'; }}
                onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}>
                  <span className="cap-sm" style={{
                    color: 'var(--acid)', fontWeight: 700, letterSpacing: '0.06em', fontSize: 10,
                    padding: '4px 8px', border: '1px solid var(--acid)', textAlign: 'center',
                  }}>{op.kind === 'box' ? 'BOX' : 'BUCKETS'}</span>
                  <div>
                    <div style={{ fontSize: 13, fontWeight: 600 }}>{op.label}</div>
                    <div className="cap-sm" style={{ marginTop: 4, fontSize: 9.5, color: 'var(--text-3)' }}>
                      buy {op.components.length} legs at ask · combined cost {fmtCents(op.cost)}
                    </div>
                  </div>
                  <div className="num" style={{ textAlign: 'right' }}>
                    <div className="cap-sm" style={{ fontSize: 9 }}>Gross edge</div>
                    <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--mint)' }}>
                      <FlashNum value={op.grossEdge}>+{(op.grossEdge * 100).toFixed(1)}¢</FlashNum>
                    </div>
                  </div>
                  <div className="num" style={{ textAlign: 'right' }}>
                    <div className="cap-sm" style={{ fontSize: 9 }}>Net of fees</div>
                    <div style={{ fontSize: 14, fontWeight: 700, color: profitable ? 'var(--mint)' : 'var(--amber)' }}>
                      {(op.netEdge * 100 >= 0 ? '+' : '')}{(op.netEdge * 100).toFixed(1)}¢
                    </div>
                  </div>
                  <div className="num" style={{ textAlign: 'right' }}>
                    <div className="cap-sm" style={{ fontSize: 9 }}>Max size at this edge</div>
                    <div style={{ fontSize: 12.5, fontWeight: 600 }}>
                      {op.maxContracts ? fmtSize(op.maxContracts) + ' × $' + (op.netEdge).toFixed(3) : '—'}
                    </div>
                    <div className="cap-sm" style={{ fontSize: 9, color: 'var(--text-3)', marginTop: 2 }}>
                      ~{fmtUsd2(op.maxContracts * op.netEdge)} risk-free
                    </div>
                  </div>
                </div>
              );
            })
          )}
        </div>
        <div className="cap-sm" style={{ marginTop: 8, color: 'var(--text-4)', fontSize: 9.5, lineHeight: 1.5 }}>
          Net edge subtracts ~5bps per leg for HL taker fees. Actual fill price may differ if size exceeds top-of-book; max size shown reflects only the top ask level.
        </div>
      </div>
    );
  }

  window.VDLiveActivity = { ActivityFeed, MispricingScanner };
})();
