// LiveFeed — the information-bias trader's primary surface.
//
// Aggregates EVERY autonomous signal the system produced into one
// chronological feed: persona memos (cron + event + alert + twitter +
// manual + team), raw Twitter signals (matched but maybe not yet fired),
// event-trigger fires, recent order fills. Auto-refresh 30s. Bounded
// to last 24h by default; expandable to 7d.
//
// Design decisions tuned for "what changed since I last looked":
//   - Time-stamped left rail with "5m ago / 2h ago / yesterday" deltas
//   - Source pill color (memo type / twitter / fire / fill)
//   - Ticker chips inline + clickable filter
//   - "since-you-last-looked" badge on the topbar trigger
//   - Auto-scroll to top on new items so the most recent always anchored
//   - Compact rendering — info-density over whitespace
//
// Not duplicating the Memo Timeline (which is the audit/forensic surface,
// drill-down focused). LiveFeed is the "I'm at my desk, what's happening
// NOW" surface.

const { useState: lfUseState, useEffect: lfUseEffect, useCallback: lfUseCallback,
        useMemo: lfUseMemo, useRef: lfUseRef } = React;

const LF_ROLE_META = {
  'phd-researcher':                { label: 'PhD',           color: '#8db4d8' },
  'hedging-specialist':            { label: 'Hedging',       color: '#d96a6a' },
  'framework-portfolio-modeling':  { label: 'Sizing',        color: '#5fb37c' },
  'framework-fundamentals':        { label: 'Fundamentals',  color: '#d9a85f' },
  'framework-value-investing':     { label: 'Value',         color: '#b48dd8' },
  'framework-growth-investing':    { label: 'Growth',        color: '#7fc7d9' },
  'framework-mental-models':       { label: 'Mental',        color: '#d8a07f' },
  'managing-director':             { label: 'MD',            color: '#ffffff' },
  'jarvis':                        { label: 'Tibeb',         color: 'var(--accent)' },
  'editor-jarvis':                 { label: 'Editor',        color: 'var(--accent)' },
  'regime-watcher':                { label: 'Regime',        color: '#ff9c5e' },
  'promotion-proposer':            { label: 'Promote',       color: '#5fd9b3' },
};

const LF_SOURCE_META = {
  cron:    { label: 'cron',     color: 'var(--text-dim)', bg: 'rgba(255,255,255,0.04)' },
  event:   { label: 'EVENT',    color: '#d96a6a',         bg: 'rgba(217,106,106,0.14)' },
  alert:   { label: 'ALERT',    color: '#d9a85f',         bg: 'rgba(217,168,95,0.14)' },
  twitter: { label: 'TWITTER',  color: '#5fb3d9',         bg: 'rgba(95,179,217,0.14)' },
  team:    { label: 'team',     color: '#b48dd8',         bg: 'rgba(180,141,216,0.10)' },
  manual:  { label: 'manual',   color: 'var(--text-2)',   bg: 'transparent' },
  solo:    { label: 'solo',     color: 'var(--text-2)',   bg: 'transparent' },
  fill:    { label: 'FILL',     color: '#5fb37c',         bg: 'rgba(95,179,124,0.14)' },
  signal:  { label: 'signal',   color: '#5fb3d9',         bg: 'rgba(95,179,217,0.08)' },
};

// Relative time — "5m ago", "2h ago", "yesterday at 3:42pm"
function relativeTime(ts) {
  const t = new Date(ts).getTime();
  const now = Date.now();
  const delta = now - t;
  if (delta < 60_000) return 'just now';
  if (delta < 3600_000) return `${Math.floor(delta / 60_000)}m ago`;
  if (delta < 86400_000) return `${Math.floor(delta / 3600_000)}h ago`;
  if (delta < 172800_000) return `yesterday`;
  const days = Math.floor(delta / 86400_000);
  if (days < 7) return `${days}d ago`;
  return new Date(ts).toLocaleDateString();
}

function LiveFeed({ open, onClose }) {
  if (!open) return null;
  const [items, setItems] = lfUseState([]);
  const [loading, setLoading] = lfUseState(true);
  const [err, setErr] = lfUseState('');
  const [filter, setFilter] = lfUseState({ ticker: '', source: '', role: '' });
  const [windowH, setWindowH] = lfUseState(24);  // hours to look back
  const lastSeenRef = lfUseRef(
    Number(localStorage.getItem('jarvis.livefeed.lastSeen') || 0)
  );

  const load = lfUseCallback(async () => {
    setLoading(true);
    setErr('');
    try {
      const cutoff = Date.now() - windowH * 3600_000;
      const cutoffISO = new Date(cutoff).toISOString();
      const [memosR, signalsR, ordersR] = await Promise.all([
        fetch(`/api/personas/memos?limit=50&since=${encodeURIComponent(cutoffISO)}`).catch(() => null),
        fetch(`/api/twitter/signals?limit=50`).catch(() => null),
        fetch(`/api/orders?limit=20`).catch(() => null),
      ]);

      const all = [];

      if (memosR && memosR.ok) {
        const j = await memosR.json();
        for (const m of (j.memos || [])) {
          all.push({
            kind: 'memo',
            id: m.path,
            ts: m.ts,
            role: m.role,
            source: m.source || 'cron',
            topic: m.topic,
            excerpt: m.excerpt,
            ticker_hits: m.ticker_hits || [],
            tickers: m.tickers || [],
            verdict: m.verdict,
            path: m.path,
          });
        }
      }

      if (signalsR && signalsR.ok) {
        const j = await signalsR.json();
        for (const s of (j.signals || [])) {
          // Drop signals whose memo we already loaded — avoid double-count
          if (s.memo_path && all.some(a => a.kind === 'memo' && a.path === s.memo_path)) {
            continue;
          }
          // Drop signals older than the window
          const sigTs = new Date(s.created || s.fired_at || s.posted_at || 0).getTime();
          if (sigTs < cutoff) continue;
          all.push({
            kind: 'signal',
            id: `sig:${s.tweet_id}`,
            ts: s.created || s.fired_at || s.posted_at,
            handle: s.handle,
            sector: s.sector,
            text: s.text,
            engagement: s.engagement,
            matched_keywords: s.matched_keywords || [],
            routed_to: s.routed_to,
            memo_path: s.memo_path,
            source: 'signal',
          });
        }
      }

      if (ordersR && ordersR.ok) {
        const j = await ordersR.json();
        for (const o of (j.orders || [])) {
          if (o.status !== 'filled' && o.status !== 'partially_filled') continue;
          const ts = o.filled_at || o.created;
          if (!ts) continue;
          if (new Date(ts).getTime() < cutoff) continue;
          all.push({
            kind: 'fill',
            id: `ord:${o.id}`,
            ts: ts,
            ticker: o.ticker,
            side: o.side,
            qty: o.filled_qty || o.qty,
            price: o.fill_price,
            source: 'fill',
          });
        }
      }

      // Newest first
      all.sort((a, b) => new Date(b.ts) - new Date(a.ts));
      setItems(all);
    } catch (e) {
      setErr(String(e.message || e));
    } finally {
      setLoading(false);
    }
  }, [windowH]);

  // Auto-poll every 30s while open
  lfUseEffect(() => {
    if (!open) return;
    load();
    const id = setInterval(load, 30_000);
    return () => clearInterval(id);
  }, [open, load]);

  // Mark as seen when panel opens
  lfUseEffect(() => {
    if (open) {
      lastSeenRef.current = Date.now();
      try { localStorage.setItem('jarvis.livefeed.lastSeen', String(Date.now())); } catch {}
    }
  }, [open]);

  // Apply filters
  const filtered = lfUseMemo(() => {
    return items.filter(it => {
      if (filter.source && it.source !== filter.source) return false;
      if (filter.role && it.kind === 'memo' && it.role !== filter.role) return false;
      if (filter.ticker) {
        const tk = filter.ticker.replace(/^\$/, '').toUpperCase();
        const inMemo = it.kind === 'memo' &&
          ((it.tickers || []).some(t => t.toUpperCase() === tk) ||
           (it.ticker_hits || []).some(t => t.toUpperCase() === tk) ||
           (it.topic || '').toUpperCase().includes(tk));
        const inSignal = it.kind === 'signal' &&
          (it.text || '').toUpperCase().includes(tk);
        const inFill = it.kind === 'fill' &&
          (it.ticker || '').toUpperCase() === tk;
        if (!(inMemo || inSignal || inFill)) return false;
      }
      return true;
    });
  }, [items, filter]);

  // Distinct tickers seen across the feed — clickable chips
  const tickersInFeed = lfUseMemo(() => {
    const counts = {};
    for (const it of items) {
      const ts = (it.tickers || []).concat(it.ticker_hits || []);
      if (it.ticker) ts.push(it.ticker);
      for (const t of ts) {
        if (t) counts[t.toUpperCase()] = (counts[t.toUpperCase()] || 0) + 1;
      }
    }
    return Object.entries(counts).sort((a, b) => b[1] - a[1]).slice(0, 12);
  }, [items]);

  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 100,
      background: 'rgba(10,10,12,0.85)', backdropFilter: 'blur(8px)',
      display: 'flex', alignItems: 'flex-start', justifyContent: 'center',
      padding: '40px 20px',
    }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', maxWidth: 920, maxHeight: 'calc(100vh - 80px)', overflowY: 'auto',
        background: 'var(--bg)', border: '1px solid var(--line-2)',
        borderRadius: 6, padding: 20, fontFamily: 'var(--font-body)',
      }}>
        <div style={{
          display: 'flex', alignItems: 'baseline', gap: 12,
          paddingBottom: 12, marginBottom: 12, borderBottom: '1px solid var(--line)',
        }}>
          <h1 style={{
            fontFamily: 'Instrument Serif, serif', fontStyle: 'italic',
            fontSize: 24, margin: 0, color: 'var(--text)',
          }}>Live Feed</h1>
          <span style={{
            fontSize: 10, letterSpacing: '0.18em', textTransform: 'uppercase',
            color: 'var(--text-dim)',
          }}>
            last {windowH}h · {filtered.length} of {items.length}
            {loading && ' · refreshing…'}
          </span>
          <select
            value={windowH}
            onChange={(e) => setWindowH(Number(e.target.value))}
            style={{
              padding: '3px 8px', fontSize: 10,
              background: 'var(--bg-2)', color: 'var(--text)',
              border: '1px solid var(--line)', borderRadius: 3,
              fontFamily: "'JetBrains Mono', monospace",
            }}
          >
            <option value={1}>1h</option>
            <option value={6}>6h</option>
            <option value={24}>24h</option>
            <option value={72}>3d</option>
            <option value={168}>7d</option>
          </select>
          <button onClick={load} disabled={loading} style={{
            padding: '4px 10px', fontSize: 10, fontFamily: "'JetBrains Mono', monospace",
            background: 'transparent', color: 'var(--text-2)',
            border: '1px solid var(--line)', borderRadius: 3, cursor: 'pointer',
          }}>↻</button>
          <button onClick={onClose} style={{
            padding: '4px 10px', fontSize: 10, fontFamily: "'JetBrains Mono', monospace",
            background: 'transparent', color: 'var(--text-dim)',
            border: '1px solid var(--line)', borderRadius: 3, cursor: 'pointer',
          }}>close</button>
        </div>

        {err && (
          <div style={{
            padding: 10, marginBottom: 12, fontSize: 11,
            background: 'rgba(217,106,106,0.12)', color: '#d96a6a',
            border: '1px solid rgba(217,106,106,0.4)', borderRadius: 4,
          }}>{err}</div>
        )}

        {/* Filter row */}
        <div style={{ display: 'flex', gap: 8, marginBottom: 10, flexWrap: 'wrap' }}>
          <input
            type="text"
            value={filter.ticker}
            onChange={(e) => setFilter(f => ({ ...f, ticker: e.target.value.toUpperCase() }))}
            placeholder="Ticker — NVDA, AMAT…"
            maxLength={6}
            style={{
              width: 130, padding: '6px 10px', fontSize: 11,
              background: 'var(--bg-2)', color: 'var(--text)',
              border: '1px solid var(--line-2)', borderRadius: 3,
              fontFamily: "'JetBrains Mono', monospace",
            }}
          />
          <select
            value={filter.source}
            onChange={(e) => setFilter(f => ({ ...f, source: e.target.value }))}
            style={{
              padding: '6px 10px', fontSize: 11,
              background: 'var(--bg-2)', color: 'var(--text)',
              border: '1px solid var(--line-2)', borderRadius: 3,
            }}
          >
            <option value="">all sources</option>
            <option value="cron">cron memos</option>
            <option value="event">event fires</option>
            <option value="alert">alert fires</option>
            <option value="twitter">twitter memos</option>
            <option value="team">team consults</option>
            <option value="signal">raw twitter signals</option>
            <option value="fill">order fills</option>
          </select>
          {(filter.ticker || filter.source || filter.role) && (
            <button
              onClick={() => setFilter({ ticker: '', source: '', role: '' })}
              style={{
                padding: '6px 10px', fontSize: 10, fontFamily: "'JetBrains Mono', monospace",
                background: 'transparent', color: 'var(--text-dim)',
                border: '1px solid var(--line)', borderRadius: 3, cursor: 'pointer',
              }}
            >
              ✕ clear
            </button>
          )}
        </div>

        {/* Hot tickers — click to filter */}
        {tickersInFeed.length > 0 && !filter.ticker && (
          <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap', marginBottom: 10 }}>
            {tickersInFeed.map(([t, count]) => (
              <button
                key={t}
                onClick={() => setFilter(f => ({ ...f, ticker: t }))}
                style={{
                  padding: '3px 8px', fontSize: 10,
                  fontFamily: "'JetBrains Mono', monospace",
                  background: 'rgba(255,255,255,0.04)',
                  color: 'var(--accent)',
                  border: '1px solid var(--line)',
                  borderRadius: 3, cursor: 'pointer',
                }}
              >
                ${t} · {count}
              </button>
            ))}
          </div>
        )}

        {/* Feed */}
        {!loading && filtered.length === 0 && (
          <div style={{ padding: 30, textAlign: 'center', color: 'var(--text-dim)', fontSize: 12 }}>
            No items in window. Try widening to 7d or clearing filters.
          </div>
        )}
        {filtered.map((it) => {
          const sourceMeta = LF_SOURCE_META[it.source] || LF_SOURCE_META.cron;
          return (
            <FeedRow key={it.id} item={it} sourceMeta={sourceMeta}
                     onTickerClick={(t) => setFilter(f => ({ ...f, ticker: t }))}/>
          );
        })}
      </div>
    </div>
  );
}

function FeedRow({ item, sourceMeta, onTickerClick }) {
  const [expanded, setExpanded] = lfUseState(false);

  if (item.kind === 'memo') {
    const roleMeta = LF_ROLE_META[item.role] || { label: item.role, color: 'var(--text)' };
    const ts = relativeTime(item.ts);
    const allTickers = [...new Set([...(item.tickers || []), ...(item.ticker_hits || [])])];
    return (
      <div style={{
        marginBottom: 6, padding: '8px 10px',
        background: 'var(--bg-2)',
        borderLeft: `3px solid ${roleMeta.color}`,
        borderTop: '1px solid var(--line)',
        borderRight: '1px solid var(--line)',
        borderBottom: '1px solid var(--line)',
        borderRadius: 3,
        cursor: 'pointer',
      }} onClick={() => setExpanded(!expanded)}>
        <div style={{ display: 'flex', gap: 8, alignItems: 'baseline', marginBottom: 3 }}>
          <span style={{
            fontSize: 9, letterSpacing: '0.14em', color: roleMeta.color,
            fontWeight: 700, fontFamily: "'JetBrains Mono', monospace",
            textTransform: 'uppercase',
          }}>{roleMeta.label}</span>
          <span style={{
            padding: '1px 6px', fontSize: 8,
            letterSpacing: '0.14em', fontWeight: 700,
            color: sourceMeta.color, background: sourceMeta.bg,
            border: `1px solid ${sourceMeta.color}33`,
            borderRadius: 2,
            fontFamily: "'JetBrains Mono', monospace",
          }}>{sourceMeta.label}</span>
          {item.verdict && (
            <span style={{
              padding: '1px 6px', fontSize: 9,
              fontFamily: "'JetBrains Mono', monospace",
              background: 'rgba(255,255,255,0.06)',
              color: 'var(--text)',
              border: '1px solid var(--line-2)', borderRadius: 2,
            }}>verdict: {item.verdict}</span>
          )}
          {allTickers.map(t => (
            <button
              key={t}
              onClick={(e) => {
                e.stopPropagation();
                // Shift-click filters the feed; plain click opens drilldown
                if (e.shiftKey) onTickerClick(t);
                else window.openTickerContext?.(t);
              }}
              title="Click for team context · Shift+click to filter the feed"
              style={{
                padding: '0 5px', fontSize: 9,
                fontFamily: "'JetBrains Mono', monospace",
                background: 'transparent', color: 'var(--accent)',
                border: '1px solid var(--line)', borderRadius: 2,
                cursor: 'pointer',
              }}
            >${t}</button>
          ))}
          <span style={{
            marginLeft: 'auto', fontSize: 9, color: 'var(--text-dim)',
            fontFamily: "'JetBrains Mono', monospace",
          }}>{ts}</span>
        </div>
        <div style={{ fontSize: 11, color: 'var(--text)', lineHeight: 1.4 }}>
          {item.topic}
        </div>
        {expanded && item.excerpt && (
          <div style={{
            marginTop: 6, paddingTop: 6,
            borderTop: '1px dashed var(--line)',
            fontSize: 11, color: 'var(--text-2)', lineHeight: 1.5,
          }}>
            {item.excerpt}
            <div style={{ marginTop: 4, fontSize: 9, color: 'var(--text-dim)' }}>
              {item.path}
            </div>
          </div>
        )}
      </div>
    );
  }

  if (item.kind === 'signal') {
    const ts = relativeTime(item.ts);
    return (
      <div style={{
        marginBottom: 6, padding: '8px 10px',
        background: 'rgba(95,179,217,0.04)',
        borderLeft: `3px solid #5fb3d9`,
        border: '1px solid var(--line)',
        borderRadius: 3,
      }}>
        <div style={{ display: 'flex', gap: 8, alignItems: 'baseline', marginBottom: 3 }}>
          <span style={{
            fontSize: 9, letterSpacing: '0.14em', color: '#5fb3d9',
            fontWeight: 700, fontFamily: "'JetBrains Mono', monospace",
          }}>@{item.handle}</span>
          <span style={{
            padding: '1px 6px', fontSize: 8,
            letterSpacing: '0.14em', fontWeight: 700,
            color: '#5fb3d9', background: 'rgba(95,179,217,0.10)',
            border: '1px solid rgba(95,179,217,0.30)', borderRadius: 2,
            fontFamily: "'JetBrains Mono', monospace",
          }}>{item.sector || 'twitter'}</span>
          {item.routed_to && (
            <span style={{
              fontSize: 9, color: 'var(--text-dim)',
              fontFamily: "'JetBrains Mono', monospace",
            }}>→ {LF_ROLE_META[item.routed_to]?.label || item.routed_to}</span>
          )}
          {item.matched_keywords?.length > 0 && (
            <span style={{
              fontSize: 9, color: 'var(--text-dim)',
              fontFamily: "'JetBrains Mono', monospace",
            }}>kw: {item.matched_keywords.slice(0, 3).join(', ')}</span>
          )}
          <span style={{
            marginLeft: 'auto', fontSize: 9, color: 'var(--text-dim)',
            fontFamily: "'JetBrains Mono', monospace",
          }}>♥ {item.engagement} · {ts}</span>
        </div>
        <div style={{
          fontSize: 11, color: 'var(--text-2)', lineHeight: 1.4,
          fontStyle: 'italic',
        }}>
          {(item.text || '').slice(0, 240)}{(item.text || '').length > 240 ? '…' : ''}
        </div>
      </div>
    );
  }

  if (item.kind === 'fill') {
    const ts = relativeTime(item.ts);
    const sideColor = item.side === 'buy' ? '#5fb37c' : '#d96a6a';
    return (
      <div style={{
        marginBottom: 6, padding: '8px 10px',
        background: 'rgba(95,179,124,0.04)',
        borderLeft: `3px solid ${sideColor}`,
        border: '1px solid var(--line)',
        borderRadius: 3,
        display: 'flex', gap: 12, alignItems: 'baseline',
      }}>
        <span style={{
          fontSize: 9, letterSpacing: '0.18em', color: sideColor,
          fontWeight: 700, fontFamily: "'JetBrains Mono', monospace",
          textTransform: 'uppercase',
        }}>FILL · {item.side}</span>
        <button
          onClick={(e) => {
            e.stopPropagation();
            if (e.shiftKey) onTickerClick(item.ticker);
            else window.openTickerContext?.(item.ticker);
          }}
          title="Click for team context · Shift+click to filter the feed"
          style={{
            padding: '0 6px', fontSize: 11,
            fontFamily: "'JetBrains Mono', monospace",
            background: 'transparent', color: 'var(--accent)',
            border: '1px solid var(--line)', borderRadius: 2,
            cursor: 'pointer', fontWeight: 700,
          }}
        >${item.ticker}</button>
        <span style={{ fontSize: 11, color: 'var(--text)' }}>
          {item.qty} @ ${item.price?.toFixed?.(2) || '?'}
        </span>
        <span style={{
          marginLeft: 'auto', fontSize: 9, color: 'var(--text-dim)',
          fontFamily: "'JetBrains Mono', monospace",
        }}>{ts}</span>
      </div>
    );
  }

  return null;
}

window.LiveFeed = LiveFeed;

// Topbar "Live" badge — small counter showing items NEW since last visit.
// Polls /api/personas/memos every 60s in the background even when the
// panel is closed, so the trader sees "8 new" when they walk back to
// their desk and decides whether to drill in.
function LiveFeedBadge({ onClick }) {
  const [newCount, setNewCount] = lfUseState(0);
  // Original document.title — preserve so we can restore when count hits zero.
  const baseTitleRef = lfUseRef(typeof document !== 'undefined' ? document.title : 'Tibeb');
  lfUseEffect(() => {
    const refresh = async () => {
      try {
        const lastSeen = Number(localStorage.getItem('jarvis.livefeed.lastSeen') || 0);
        if (!lastSeen) { setNewCount(0); return; }
        const cutoff = new Date(lastSeen).toISOString();
        const r = await fetch(`/api/personas/memos?limit=50&since=${encodeURIComponent(cutoff)}`);
        if (!r.ok) return;
        const j = await r.json();
        setNewCount(j.total || 0);
      } catch {}
    };
    refresh();
    const id = setInterval(refresh, 60_000);
    return () => clearInterval(id);
  }, []);

  // Mirror the count to the browser tab title so users see new memos
  // even when the dashboard is in a background tab. Restores the base
  // title when count drops to 0. Common pattern for messaging apps.
  lfUseEffect(() => {
    if (typeof document === 'undefined') return;
    const base = baseTitleRef.current || 'Tibeb';
    document.title = newCount > 0 ? `(${newCount}) ${base}` : base;
  }, [newCount]);

  return (
    <button
      className="agents-btn"
      onClick={onClick}
      title={newCount > 0
        ? `${newCount} new since you last looked — click to open Live Feed`
        : 'Live Feed — chronological stream of every autonomous signal'}
      style={{ position: 'relative' }}
    >
      <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5">
        <circle cx="8" cy="8" r="2" fill="currentColor"/>
        <path d="M3 8a5 5 0 0 1 10 0M2 8a6 6 0 0 1 12 0" strokeLinecap="round"/>
      </svg>
      Live
      {newCount > 0 && (
        <span style={{
          position: 'absolute', top: -2, right: -2,
          minWidth: 14, height: 14,
          padding: '0 3px',
          background: '#d96a6a', color: 'var(--bg)',
          borderRadius: 7,
          fontSize: 9, fontWeight: 700,
          fontFamily: "'JetBrains Mono', monospace",
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
          lineHeight: 1,
        }}>{newCount > 99 ? '99+' : newCount}</span>
      )}
    </button>
  );
}

window.LiveFeedBadge = LiveFeedBadge;
