// Dashboard — mirrors apps/web/src/app/(dashboard)/page.tsx.
// Stats loaded via FastAPI (service-role key bypasses Supabase RLS → accurate counts).
// Charts derived from already-loaded recent analyses — no extra network calls.

window.Dashboard = function Dashboard({ goto, session, isElevated }) {
  const { useState, useEffect, useMemo } = React;
  // Prefer DB-sourced isElevated; fall back to hardcoded list while
  // /api/auth/me hasn't resolved yet on first render after sign-in.
  const canTrigger = isElevated === true
    ? true
    : (window.EQ_IS_AUTHORIZED ? window.EQ_IS_AUTHORIZED(session) : false);
  const triggerTooltip = 'Restricted — contact your administrator to run analyses.';
  const [triggerModalOpen, setTriggerModalOpen] = useState(false);

  // ── Stats: single FastAPI call using service-role key (bypasses RLS) ──────
  const [stats, setStats] = useState({ active_strategies: null, total_analyses: null, buy_signals: null, pending_approvals: null });
  const [statsLoading, setStatsLoading] = useState(true);

  const loadStats = () => {
    setStatsLoading(true);
    window.EQ_API.getDashboardStats()
      .then(d => setStats(d))
      .catch(e => console.error('Stats load failed:', e))
      .finally(() => setStatsLoading(false));
  };

  useEffect(loadStats, []);

  // ── Running & recent analyses ──────────────────────────────────────────────
  const running = window.useApi(() => window.EQ_API.getRunningAnalyses(), []);
  const recent  = window.useApi(() => window.EQ_API.getAnalyses({ limit: 10 }), []);

  const [ticker, setTicker] = useState('');
  const [triggerBusy, setTriggerBusy] = useState(false);
  const [triggerError, setTriggerError] = useState(null);
  const [cancelBusy, setCancelBusy] = useState(null); // analysis id currently being cancelled

  const reloadAll = () => { loadStats(); running.reload(); recent.reload(); };

  const trigger = async (rawTicker) => {
    const t = (rawTicker ?? ticker).trim().toUpperCase();
    if (!t) return;
    setTriggerBusy(true); setTriggerError(null);
    try {
      await window.EQ_API.triggerAnalysis(t);
      setTicker('');
      setTriggerModalOpen(false);
      reloadAll();
    } catch (e) {
      setTriggerError(e.message || 'Trigger failed');
      throw e;
    } finally {
      setTriggerBusy(false);
    }
  };

  const [decisionFilter, setDecisionFilter] = useState(null);

  const runningCount = running.data?.data?.length ?? 0;
  const rows = recent.data?.data || [];

  const filteredRows = useMemo(() =>
    decisionFilter ? rows.filter(r => r.decision === decisionFilter) : rows,
    [rows, decisionFilter]
  );

  // ── Chart data derived from loaded rows (no extra API calls) ──────────────
  const passCount = useMemo(() => {
    if (stats.total_analyses == null || stats.buy_signals == null) return null;
    return stats.total_analyses - stats.buy_signals;
  }, [stats]);

  const upsideRows = useMemo(() =>
    rows.filter(r => r.upside_pct != null).sort((a, b) => b.upside_pct - a.upside_pct).slice(0, 8),
    [rows]
  );

  const statusCounts = useMemo(() => {
    const counts = { completed: 0, failed: 0, running: 0, queued: 0 };
    rows.forEach(r => { if (r.status in counts) counts[r.status]++; });
    return counts;
  }, [rows]);

  return (
    <div className="page-fade">
      <window.TopBar
        title="Dashboard"
        subtitle={new Date().toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' })
          + (runningCount > 0 ? ` · ${runningCount} analysis${runningCount === 1 ? '' : 'es'} in pipeline` : ' · All systems operational')}
        right={
          <button
            onClick={() => { if (canTrigger) setTriggerModalOpen(true); }}
            disabled={!canTrigger}
            title={!canTrigger ? triggerTooltip : 'Run a new equity analysis'}
            style={{
              padding: '8px 18px', fontSize: 12.5, fontWeight: 500,
              background: !canTrigger ? 'var(--ink-300)' : 'var(--ink-900)',
              color: 'white', borderRadius: 7, border: 'none',
              cursor: !canTrigger ? 'not-allowed' : 'pointer',
              opacity: !canTrigger ? 0.55 : 1,
              fontFamily: 'inherit',
              display: 'inline-flex', alignItems: 'center', gap: 7,
              pointerEvents: 'auto',
            }}
          >
            {!canTrigger ? (
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" style={{ color: '#ffb4b4' }}>
                <path d="M5 11h14a1 1 0 011 1v8a1 1 0 01-1 1H5a1 1 0 01-1-1v-8a1 1 0 011-1zm2 0V7a5 5 0 0110 0v4"
                      stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
              </svg>
            ) : (
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none">
                <path d="M12 5v14M5 12h14" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
              </svg>
            )}
            Run Analysis
          </button>
        }
      />

      <div style={{ padding: '24px 32px', display: 'flex', flexDirection: 'column', gap: 16 }}>

        {triggerError && <window.ErrorBanner error={{ message: triggerError }}/>}

        {/* ── Stat cards ── */}
        <section style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12 }}>
          <window.StatCard
            label="Active Strategies"
            value={statsLoading ? <StatSkeleton/> : stats.active_strategies}
            accent="var(--onni-primary)"
            icon="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"
            onClick={() => goto('strategies')}
          />
          <window.StatCard
            label="Total Analyses"
            value={statsLoading ? <StatSkeleton/> : stats.total_analyses}
            icon="M4 4h16v4H4zm0 6h10v4H4zm0 6h16v4H4z"
            active={decisionFilter === null && false}
            onClick={() => setDecisionFilter(null)}
          />
          <window.StatCard
            label="Buy Signals"
            value={statsLoading ? <StatSkeleton/> : stats.buy_signals}
            accent="var(--buy)"
            icon="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
            active={decisionFilter === 'BUY'}
            onClick={() => setDecisionFilter(f => f === 'BUY' ? null : 'BUY')}
          />
          <window.StatCard
            label="Pending Approvals"
            value={statsLoading ? <StatSkeleton/> : stats.pending_approvals}
            accent="var(--warn)"
            icon="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
            onClick={() => goto('approvals')}
          />
        </section>

        {/* ── Charts row ── */}
        {!statsLoading && stats.total_analyses > 0 && (
          <section style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>

            {/* BUY vs PASS donut */}
            <ChartCard title="Decision Split">
              <div style={{ display: 'flex', alignItems: 'center', gap: 24, padding: '8px 0' }}>
                <BuyPassDonut buy={stats.buy_signals} pass={passCount}/>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                  <LegendItem color="var(--buy)" label="BUY" value={stats.buy_signals}
                    pct={stats.total_analyses > 0 ? Math.round(stats.buy_signals / stats.total_analyses * 100) : 0}/>
                  <LegendItem color="var(--pass)" label="PASS" value={passCount}
                    pct={stats.total_analyses > 0 ? Math.round(passCount / stats.total_analyses * 100) : 0}/>
                  <LegendItem color="var(--ink-300)" label="No decision" value={stats.total_analyses - stats.buy_signals - passCount}
                    pct={stats.total_analyses > 0 ? Math.round((stats.total_analyses - stats.buy_signals - passCount) / stats.total_analyses * 100) : 0}/>
                </div>
              </div>
            </ChartCard>

            {/* Upside bars from recent 10 */}
            {upsideRows.length > 0 ? (
              <ChartCard title="Upside % — Recent BUY picks">
                <UpsideBars rows={upsideRows}/>
              </ChartCard>
            ) : (
              <ChartCard title="Status Breakdown">
                <StatusBreakdown counts={statusCounts}/>
              </ChartCard>
            )}

          </section>
        )}

        {/* ── Running now ── */}
        {runningCount > 0 && (
          <section style={{ background: 'var(--paper)', border: '1px solid var(--ink-150)', borderRadius: 12, padding: '16px 20px' }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
              <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink-900)' }}>Running now</div>
              <window.DecisionBadge decision="RUNNING"/>
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
              {running.data.data.map(r => (
                <div key={r.id} style={{
                  display: 'flex', alignItems: 'center', gap: 12,
                  padding: '8px 10px', background: 'var(--ink-50)', borderRadius: 8
                }}>
                  <span style={{ fontFamily: 'var(--font-mono)', fontWeight: 600, color: 'var(--ink-900)', fontSize: 13 }}>{r.ticker}</span>
                  <span style={{ fontSize: 12, color: 'var(--ink-500)', flex: 1 }}>{r.company_name || '—'}</span>
                  <span style={{ fontSize: 11.5, color: 'var(--ink-500)', fontFamily: 'var(--font-mono)' }}>started {window.formatRelative(r.started_at)}</span>
                  <button
                    onClick={async () => {
                      if (!canTrigger) return;
                      if (!window.confirm(`Cancel analysis for ${r.ticker}? This stops the pipeline and marks the row as cancelled.`)) return;
                      setCancelBusy(r.id); setTriggerError(null);
                      try { await window.EQ_API.cancelAnalysis(r.id); reloadAll(); }
                      catch (e) { setTriggerError(e.message || 'Cancel failed'); }
                      finally { setCancelBusy(null); }
                    }}
                    disabled={!canTrigger || cancelBusy === r.id}
                    title={!canTrigger ? 'Restricted — contact your administrator to cancel analyses.' : `Cancel ${r.ticker} analysis`}
                    style={{
                      padding: '4px 10px', fontSize: 11.5, fontWeight: 500,
                      background: cancelBusy === r.id ? 'var(--ink-100)'
                                : !canTrigger ? 'var(--ink-100)'
                                : 'var(--pass-soft)',
                      color: cancelBusy === r.id ? 'var(--ink-400)'
                           : !canTrigger ? 'var(--ink-400)'
                           : 'var(--pass)',
                      border: '1px solid ' + (cancelBusy === r.id ? 'var(--ink-200)'
                                            : !canTrigger ? 'var(--ink-200)'
                                            : 'var(--pass)'),
                      borderRadius: 6,
                      cursor: !canTrigger || cancelBusy === r.id ? 'not-allowed' : 'pointer',
                      opacity: !canTrigger ? 0.6 : 1,
                      fontFamily: 'inherit', flexShrink: 0,
                    }}
                  >{cancelBusy === r.id ? 'Cancelling…' : 'Cancel'}</button>
                </div>
              ))}
            </div>
          </section>
        )}

        {/* ── Recent analyses ── */}
        <section>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink-900)' }}>Recent analyses</div>
              {decisionFilter && (
                <button onClick={() => setDecisionFilter(null)} style={{
                  fontSize: 11, fontWeight: 500, padding: '2px 8px', borderRadius: 20,
                  background: 'var(--buy-soft)', color: 'var(--buy)',
                  border: '1px solid var(--buy)', cursor: 'pointer', fontFamily: 'inherit',
                }}>
                  {decisionFilter} ×
                </button>
              )}
            </div>
            <button onClick={() => goto('analyses')} style={{
              background: 'transparent', border: 'none', color: 'var(--ink-600)',
              fontSize: 12, cursor: 'pointer', fontFamily: 'inherit'
            }}>View all →</button>
          </div>
          <window.AnalysesTable
            rows={filteredRows}
            loading={recent.loading}
            error={recent.error}
            onRetry={recent.reload}
            enablePreview
            onRowClick={(row) => goto('analysis', { analysisId: row.id })}
            onRerun={async (row) => {
              setTriggerBusy(true); setTriggerError(null);
              try { await window.EQ_API.triggerAnalysis(row.ticker); reloadAll(); }
              catch (e) { setTriggerError(e.message || 'Trigger failed'); }
              finally { setTriggerBusy(false); }
            }}
          />
        </section>

      </div>

      {triggerModalOpen && (
        <window.RunAnalysisModal
          recentTickers={Array.from(new Set(rows.map(r => r.ticker))).slice(0, 6)}
          onClose={() => { setTriggerModalOpen(false); setTriggerError(null); }}
          onSubmit={trigger}
          busy={triggerBusy}
          error={triggerError}
        />
      )}
    </div>
  );
};

// ─────────── local chart helpers ───────────

function StatSkeleton() {
  return (
    <div style={{
      height: 28, width: 48, borderRadius: 6,
      background: 'linear-gradient(90deg, var(--ink-100) 25%, var(--ink-50) 50%, var(--ink-100) 75%)',
      backgroundSize: '200% 100%',
      animation: 'shimmer 1.4s infinite',
    }}/>
  );
}

function ChartCard({ title, children }) {
  return (
    <div className="card card-hover" style={{ background: 'var(--paper)', border: '1px solid var(--ink-150)', borderRadius: 12, padding: '16px 20px' }}>
      <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-500)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 14 }}>{title}</div>
      {children}
    </div>
  );
}

function BuyPassDonut({ buy, pass }) {
  const total = (buy || 0) + (pass || 0);
  if (!total) return <div style={{ width: 96, height: 96, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--ink-300)', fontSize: 11 }}>No data</div>;

  const r = 36; const cx = 48; const cy = 48; const sw = 14;
  const circ = 2 * Math.PI * r;
  const buyFrac = buy / total;
  const passFrac = pass / total;

  // With transform: rotate(-90deg) both arcs start at 12 o'clock and draw
  // clockwise. BUY has no offset (starts at top); PASS is pushed clockwise
  // by the BUY arc-length so it begins exactly where BUY ends, leaving no gap.
  const arc = (frac) => {
    const len = frac * circ;
    return `${len} ${circ - len}`;
  };

  return (
    <svg width="96" height="96" viewBox="0 0 96 96" style={{ flexShrink: 0 }}>
      {/* track */}
      <circle cx={cx} cy={cy} r={r} fill="none" stroke="var(--ink-100)" strokeWidth={sw}/>
      {/* PASS arc */}
      {pass > 0 && (
        <circle cx={cx} cy={cy} r={r} fill="none"
          stroke="var(--pass-soft)" strokeWidth={sw}
          strokeDasharray={arc(passFrac)}
          strokeDashoffset={-buyFrac * circ}
          strokeLinecap="butt"
          style={{ transform: 'rotate(-90deg)', transformOrigin: '48px 48px' }}
        />
      )}
      {/* BUY arc */}
      {buy > 0 && (
        <circle cx={cx} cy={cy} r={r} fill="none"
          stroke="var(--buy)" strokeWidth={sw}
          strokeDasharray={arc(buyFrac)}
          strokeLinecap="butt"
          style={{ transform: 'rotate(-90deg)', transformOrigin: '48px 48px' }}
        />
      )}
      {/* center label */}
      <text x={cx} y={cy - 5} textAnchor="middle" fontSize="15" fontWeight="700"
        fill="var(--ink-900)" fontFamily="var(--font-mono)">{total}</text>
      <text x={cx} y={cy + 10} textAnchor="middle" fontSize="9"
        fill="var(--ink-500)" fontFamily="var(--font-sans)" letterSpacing="0.05em">TOTAL</text>
    </svg>
  );
}

function LegendItem({ color, label, value, pct }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
      <div style={{ width: 10, height: 10, borderRadius: 3, background: color, flexShrink: 0 }}/>
      <div style={{ fontSize: 12, color: 'var(--ink-700)', flex: 1 }}>{label}</div>
      <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink-900)', fontFamily: 'var(--font-mono)', minWidth: 28, textAlign: 'right' }}>{value ?? 0}</div>
      <div style={{ fontSize: 11, color: 'var(--ink-400)', fontFamily: 'var(--font-mono)', minWidth: 36, textAlign: 'right' }}>{pct}%</div>
    </div>
  );
}

function UpsideBars({ rows }) {
  const max = Math.max(...rows.map(r => Math.abs(r.upside_pct || 0)), 1);
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
      {rows.map(r => {
        const pct = r.upside_pct || 0;
        const barWidth = Math.min(Math.abs(pct) / max * 100, 100);
        const isPos = pct >= 0;
        return (
          <div key={r.id} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{ fontSize: 11, fontFamily: 'var(--font-mono)', fontWeight: 600, color: 'var(--ink-700)', width: 42, flexShrink: 0 }}>{r.ticker}</div>
            <div style={{ flex: 1, background: 'var(--ink-100)', borderRadius: 3, height: 8, overflow: 'hidden' }}>
              <div style={{
                height: '100%', borderRadius: 3,
                background: isPos ? 'var(--buy)' : 'var(--pass)',
                width: barWidth + '%',
                transition: 'width 0.4s ease',
              }}/>
            </div>
            <div style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: isPos ? 'var(--buy)' : 'var(--pass)', fontWeight: 600, width: 48, textAlign: 'right', flexShrink: 0 }}>
              {isPos ? '+' : ''}{pct.toFixed(1)}%
            </div>
          </div>
        );
      })}
    </div>
  );
}

function StatusBreakdown({ counts }) {
  const items = [
    { label: 'Completed', key: 'completed', color: 'var(--buy)' },
    { label: 'Running',   key: 'running',   color: 'var(--warn)' },
    { label: 'Queued',    key: 'queued',     color: 'var(--ink-400)' },
    { label: 'Failed',    key: 'failed',     color: 'var(--pass)' },
  ];
  const total = Object.values(counts).reduce((a, b) => a + b, 0) || 1;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {items.map(it => (
        <div key={it.key} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <div style={{ fontSize: 11.5, color: 'var(--ink-600)', width: 72, flexShrink: 0 }}>{it.label}</div>
          <div style={{ flex: 1, background: 'var(--ink-100)', borderRadius: 3, height: 8, overflow: 'hidden' }}>
            <div style={{
              height: '100%', borderRadius: 3, background: it.color,
              width: (counts[it.key] / total * 100) + '%',
              transition: 'width 0.4s ease',
            }}/>
          </div>
          <div style={{ fontSize: 11.5, fontFamily: 'var(--font-mono)', fontWeight: 600, color: 'var(--ink-700)', width: 24, textAlign: 'right', flexShrink: 0 }}>{counts[it.key]}</div>
        </div>
      ))}
    </div>
  );
}

// ─────────── Shared table (Dashboard + Analyses list) ───────────
// onRowClick(row)   — navigate to detail
// onRerun(row)      — re-trigger analysis for this ticker
// enablePreview     — when true, hovering a row shows a floating preview
//                     card with the analysis's exec summary + bull/bear.
//                     Auto-disabled on mobile (<768px viewport).
//
// Module-level cache: avoid refetching when the cursor revisits a row.
const _PREVIEW_CACHE = new Map();
async function _fetchPreview(id) {
  if (_PREVIEW_CACHE.has(id)) return _PREVIEW_CACHE.get(id);
  const data = await window.EQ_API.getAnalysis(id);
  _PREVIEW_CACHE.set(id, data);
  return data;
}

window.AnalysesTable = function AnalysesTable({ rows, loading, error, onRetry, onRowClick, onRerun, enablePreview }) {
  const { useState, useRef } = React;
  const [rerunBusy, setRerunBusy] = useState(null);

  // Hover preview state (desktop only). Set to {row, data?, loading?, error?,
  // pos: {top, left}} while a row is hovered; null otherwise.
  const [preview, setPreview] = useState(null);
  const showTimerRef  = useRef(null);
  const hideTimerRef  = useRef(null);
  const isMobile      = (typeof window !== 'undefined') && window.innerWidth < 768;
  const previewActive = !!enablePreview && !isMobile;

  const _clearTimers = () => {
    if (showTimerRef.current) { clearTimeout(showTimerRef.current); showTimerRef.current = null; }
    if (hideTimerRef.current) { clearTimeout(hideTimerRef.current); hideTimerRef.current = null; }
  };
  const _computePos = (rect) => {
    // Float to the right of the row by default; flip left if it would overflow.
    const W = 380, H = 320, MARGIN = 12;
    let left = rect.right + MARGIN;
    if (left + W > window.innerWidth - 8) left = rect.left - W - MARGIN;
    if (left < 8) left = Math.max(8, (window.innerWidth - W) / 2);
    let top = (rect.top + rect.bottom) / 2 - H / 2;
    top = Math.max(8, Math.min(top, window.innerHeight - H - 8));
    return { top, left };
  };
  const handleRowEnter = (row, e) => {
    if (!previewActive) return;
    if (row.status !== 'completed') return; // only completed rows have content worth previewing
    _clearTimers();
    const target = e.currentTarget;
    showTimerRef.current = setTimeout(async () => {
      const pos = _computePos(target.getBoundingClientRect());
      // Optimistic open: render with row data immediately, fetch full detail
      setPreview({ row, pos, loading: !_PREVIEW_CACHE.has(row.id), data: _PREVIEW_CACHE.get(row.id) || null, error: null });
      if (!_PREVIEW_CACHE.has(row.id)) {
        try {
          const data = await _fetchPreview(row.id);
          setPreview(p => (p && p.row.id === row.id) ? { ...p, loading: false, data } : p);
        } catch (err) {
          setPreview(p => (p && p.row.id === row.id) ? { ...p, loading: false, error: err.message || 'Failed to load' } : p);
        }
      }
    }, 250);
  };
  const handleRowLeave = (rowId) => {
    if (!previewActive) return;
    if (showTimerRef.current) { clearTimeout(showTimerRef.current); showTimerRef.current = null; }
    hideTimerRef.current = setTimeout(() => {
      setPreview(p => (p && p.row.id === rowId) ? null : p);
    }, 180);
  };
  const handlePopoverEnter = () => {
    if (hideTimerRef.current) { clearTimeout(hideTimerRef.current); hideTimerRef.current = null; }
  };
  const handlePopoverLeave = () => {
    hideTimerRef.current = setTimeout(() => setPreview(null), 120);
  };

  if (error)   return <window.ErrorBanner error={error} onRetry={onRetry}/>;
  if (loading) return <div style={{ padding: 20, color: 'var(--ink-500)', fontSize: 12.5 }}>Loading…</div>;
  if (!rows.length) return <window.EmptyState title="No analyses yet" hint="Trigger an analysis using the input above."/>;

  const cell = { padding: '10px 14px', fontSize: 12.5, color: 'var(--ink-800)', borderBottom: '1px solid var(--ink-100)' };
  const head = { padding: '10px 14px', fontSize: 10.5, color: 'var(--ink-500)', textTransform: 'uppercase', letterSpacing: '0.06em', fontWeight: 500, textAlign: 'left', borderBottom: '1px solid var(--ink-200)', background: 'var(--ink-50)' };

  const handleRerun = async (e, r) => {
    e.stopPropagation();
    if (!onRerun || rerunBusy) return;
    setRerunBusy(r.id);
    try { await onRerun(r); } finally { setRerunBusy(null); }
  };

  return (
    <div style={{ background: 'var(--paper)', border: '1px solid var(--ink-150)', borderRadius: 12, overflow: 'hidden' }}>
      <table style={{ width: '100%', borderCollapse: 'collapse' }}>
        <thead>
          <tr>
            <th style={head}>Company</th>
            <th style={head}>Decision</th>
            <th style={{ ...head, textAlign: 'right' }}>Price Target</th>
            <th style={{ ...head, textAlign: 'right' }}>Upside</th>
            <th style={head}>Status</th>
            <th style={{ ...head, textAlign: 'right' }}>Date</th>
            <th style={{ ...head, textAlign: 'right' }}>Actions</th>
          </tr>
        </thead>
        <tbody>
          {rows.map(r => (
            <tr key={r.id}
                onClick={() => onRowClick && onRowClick(r)}
                style={{ cursor: onRowClick ? 'pointer' : 'default' }}
                onMouseEnter={e => {
                  if (onRowClick) e.currentTarget.style.background = 'var(--ink-50)';
                  handleRowEnter(r, e);
                }}
                onMouseLeave={e => {
                  e.currentTarget.style.background = 'transparent';
                  handleRowLeave(r.id);
                }}>
              <td style={cell}>
                <div style={{ fontFamily: 'var(--font-mono)', fontWeight: 600, color: 'var(--ink-900)' }}>{r.ticker}</div>
                {r.company_name && r.company_name !== r.ticker && (
                  <div style={{ fontSize: 11, color: 'var(--ink-500)', marginTop: 2 }}>{r.company_name}</div>
                )}
              </td>
              <td style={cell}>{r.decision ? <window.DecisionBadge decision={r.decision}/> : '—'}</td>
              <td style={{ ...cell, textAlign: 'right', fontFamily: 'var(--font-mono)' }}>{window.formatPrice(r.price_target)}</td>
              <td style={{ ...cell, textAlign: 'right', fontFamily: 'var(--font-mono)' }}>{window.formatPct(r.upside_pct)}</td>
              <td style={cell}><window.StatusBadge status={r.status}/></td>
              <td style={{ ...cell, textAlign: 'right', color: 'var(--ink-500)' }}>{window.formatDate(r.created_at)}</td>
              <td style={{ ...cell, textAlign: 'right' }}>
                <div style={{ display: 'inline-flex', gap: 6 }} onClick={e => e.stopPropagation()}>
                  {onRowClick && (
                    <button onClick={e => { e.stopPropagation(); onRowClick(r); }} style={{
                      padding: '4px 10px', fontSize: 11.5, background: 'transparent',
                      color: 'var(--ink-600)', border: '1px solid var(--ink-200)',
                      borderRadius: 6, cursor: 'pointer', fontFamily: 'inherit'
                    }}>View →</button>
                  )}
                  {onRerun && (
                    <button onClick={e => handleRerun(e, r)} disabled={rerunBusy === r.id}
                      style={{
                        padding: '4px 8px', fontSize: 11.5, background: 'var(--ink-100)',
                        color: 'var(--ink-700)', border: '1px solid var(--ink-200)',
                        borderRadius: 6, cursor: rerunBusy === r.id ? 'default' : 'pointer',
                        opacity: rerunBusy === r.id ? 0.5 : 1,
                        display: 'inline-flex', alignItems: 'center', gap: 4,
                        fontFamily: 'inherit'
                      }}>
                      <svg width="11" height="11" viewBox="0 0 24 24" fill="none">
                        <path d="M4 12a8 8 0 0114.93-4H15m3.93 4A8 8 0 019 19.93V17" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
                      </svg>
                      {rerunBusy === r.id ? '…' : 'Re-run'}
                    </button>
                  )}
                </div>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
      {preview && (
        <window.AnalysisHoverPreview
          row={preview.row}
          data={preview.data}
          loading={preview.loading}
          error={preview.error}
          pos={preview.pos}
          onMouseEnter={handlePopoverEnter}
          onMouseLeave={handlePopoverLeave}
          onView={() => { setPreview(null); onRowClick && onRowClick(preview.row); }}
        />
      )}
    </div>
  );
};

// ─────────── Hover preview popover (Recent Analyses) ───────────
window.AnalysisHoverPreview = function AnalysisHoverPreview({
  row, data, loading, error, pos, onMouseEnter, onMouseLeave, onView,
}) {
  const exec = (data && data.executive_summary) || row.executive_summary || '';
  const bull = (data && data.bull_summary) || '';
  const bear = (data && data.bear_summary) || '';
  const clip = (s, n) => {
    if (!s) return '';
    const t = String(s).replace(/\s+/g, ' ').trim();
    return t.length > n ? t.slice(0, n).replace(/[\s,;:]+\S*$/, '') + '…' : t;
  };
  return (
    <div
      onMouseEnter={onMouseEnter}
      onMouseLeave={onMouseLeave}
      style={{
        position: 'fixed',
        top: pos.top, left: pos.left,
        width: 380,
        background: 'var(--paper)',
        border: '1px solid var(--ink-150)',
        borderRadius: 12,
        boxShadow: '0 12px 32px rgba(10,22,40,0.14), 0 2px 6px rgba(10,22,40,0.06)',
        zIndex: 40,
        overflow: 'hidden',
        animation: 'eq-fade-in 120ms ease-out',
      }}
    >
      {/* Header — same aesthetic as Card header */}
      <div style={{
        padding: '10px 14px',
        borderBottom: '1px solid var(--ink-150)',
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        gap: 10,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
          <span style={{
            fontFamily: 'var(--font-mono)', fontWeight: 700, fontSize: 13,
            color: 'var(--ink-900)',
          }}>{row.ticker}</span>
          {row.company_name && row.company_name !== row.ticker && (
            <span style={{
              fontSize: 11, color: 'var(--ink-500)',
              overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
              minWidth: 0,
            }}>{row.company_name}</span>
          )}
        </div>
        {row.decision && <window.DecisionBadge decision={row.decision}/>}
      </div>

      {/* Stats row */}
      <div style={{
        padding: '10px 14px',
        display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8,
        borderBottom: '1px solid var(--ink-100)',
        background: 'var(--ink-50)',
      }}>
        {[
          { label: 'Target', value: window.formatPrice(row.price_target) },
          { label: 'Upside', value: window.formatPct(row.upside_pct) },
          { label: 'Date',   value: window.formatDate(row.created_at) },
        ].map(({ label, value }) => (
          <div key={label}>
            <div style={{ fontSize: 9.5, color: 'var(--ink-500)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 2 }}>{label}</div>
            <div style={{ fontSize: 12, fontFamily: 'var(--font-mono)', fontWeight: 600, color: 'var(--ink-900)' }}>{value || '—'}</div>
          </div>
        ))}
      </div>

      {/* Body */}
      <div style={{ padding: '12px 14px' }}>
        {loading && !data && (
          <div style={{ fontSize: 12, color: 'var(--ink-500)' }}>Loading preview…</div>
        )}
        {error && !data && (
          <div style={{ fontSize: 12, color: 'var(--pass)' }}>Couldn't load preview: {error}</div>
        )}
        {(data || exec) && (
          <>
            {exec && (
              <div style={{ marginBottom: 10 }}>
                <div style={{ fontSize: 9.5, fontWeight: 600, color: 'var(--ink-500)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 4 }}>Executive Summary</div>
                <div style={{ fontSize: 12, color: 'var(--ink-800)', lineHeight: 1.5 }}>{clip(exec, 320)}</div>
              </div>
            )}
            {(bull || bear) && (
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginBottom: 4 }}>
                <div>
                  <div style={{ fontSize: 9.5, fontWeight: 600, color: 'var(--buy)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 4 }}>Bull</div>
                  <div style={{ fontSize: 11.5, color: 'var(--ink-700)', lineHeight: 1.5 }}>{clip(bull, 130) || '—'}</div>
                </div>
                <div>
                  <div style={{ fontSize: 9.5, fontWeight: 600, color: 'var(--pass)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 4 }}>Bear</div>
                  <div style={{ fontSize: 11.5, color: 'var(--ink-700)', lineHeight: 1.5 }}>{clip(bear, 130) || '—'}</div>
                </div>
              </div>
            )}
          </>
        )}
      </div>

      {/* Footer */}
      <div style={{
        padding: '8px 14px',
        borderTop: '1px solid var(--ink-100)',
        display: 'flex', justifyContent: 'flex-end',
        background: 'var(--ink-50)',
      }}>
        <button onClick={onView} style={{
          background: 'transparent', border: 'none', color: 'var(--onni-primary)',
          fontSize: 11.5, fontWeight: 600, cursor: 'pointer',
          fontFamily: 'inherit', padding: 0,
        }}>View full analysis →</button>
      </div>
    </div>
  );
};

// ─────────── Run Analysis modal (Trigger New) ───────────
// Centered, large input + recent ticker quick-picks. Submits via the parent's
// onSubmit(ticker). Closes via onClose().
window.RunAnalysisModal = function RunAnalysisModal({ recentTickers, onClose, onSubmit, busy, error }) {
  const { useState, useRef, useEffect } = React;
  const [t, setT] = useState('');
  const inputRef = useRef(null);
  useEffect(() => { inputRef.current?.focus(); }, []);

  const submit = async () => {
    const cleaned = t.trim().toUpperCase();
    if (!cleaned || busy) return;
    try { await onSubmit(cleaned); }
    catch { /* parent surfaces the error */ }
  };

  return (
    <div
      onClick={(e) => { if (e.target === e.currentTarget && !busy) onClose(); }}
      style={{
        position: 'fixed', inset: 0, zIndex: 60,
        background: 'rgba(10,20,40,0.5)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        padding: 20,
      }}
    >
      <div style={{
        width: 520, maxWidth: '92vw',
        background: 'var(--paper)', borderRadius: 16, overflow: 'hidden',
        border: '1px solid var(--ink-150)',
        boxShadow: '0 28px 70px rgba(10,22,40,0.28)',
      }}>
        <div style={{ padding: '18px 24px', borderBottom: '1px solid var(--ink-150)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
          <div>
            <div style={{ fontSize: 9.5, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--ink-500)', marginBottom: 3 }}>
              New Analysis
            </div>
            <div style={{ fontSize: 16, fontWeight: 700, color: 'var(--ink-900)' }}>
              Run Equity Analysis
            </div>
          </div>
          <button
            onClick={() => !busy && onClose()}
            disabled={busy}
            title="Close"
            style={{
              width: 28, height: 28, borderRadius: 6, background: 'transparent',
              border: 'none', color: 'var(--ink-500)',
              cursor: busy ? 'default' : 'pointer',
            }}
          >
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none">
              <path d="M6 6l12 12M18 6l-12 12" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
            </svg>
          </button>
        </div>

        <div style={{ padding: '24px 26px' }}>
          <label style={{ display: 'block', marginBottom: 18 }}>
            <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-500)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 8 }}>
              Ticker
            </div>
            <input
              ref={inputRef}
              value={t}
              onChange={e => setT(e.target.value.toUpperCase())}
              onKeyDown={e => { if (e.key === 'Enter') submit(); }}
              placeholder="AAPL, MSFT, NVDA…"
              disabled={busy}
              autoCapitalize="characters"
              autoComplete="off"
              spellCheck="false"
              style={{
                width: '100%', padding: '13px 16px',
                fontSize: 22, fontFamily: 'var(--font-mono)', fontWeight: 600,
                color: 'var(--ink-900)', background: 'var(--ink-50)',
                border: '1px solid var(--ink-200)', borderRadius: 10,
                outline: 'none', letterSpacing: '0.04em',
                boxSizing: 'border-box',
              }}
            />
          </label>

          {recentTickers && recentTickers.length > 0 && (
            <div style={{ marginBottom: 18 }}>
              <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-500)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 8 }}>
                Recent
              </div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                {recentTickers.map(rt => (
                  <button
                    key={rt}
                    onClick={() => setT(rt)}
                    disabled={busy}
                    style={{
                      padding: '5px 11px', fontSize: 12,
                      fontFamily: 'var(--font-mono)', fontWeight: 600,
                      background: t === rt ? 'var(--onni-primary)' : 'var(--ink-100)',
                      color: t === rt ? 'white' : 'var(--ink-700)',
                      border: '1px solid ' + (t === rt ? 'var(--onni-primary)' : 'var(--ink-200)'),
                      borderRadius: 6, cursor: busy ? 'default' : 'pointer',
                      letterSpacing: '0.02em',
                    }}
                  >{rt}</button>
                ))}
              </div>
            </div>
          )}

          {error && (
            <div style={{
              padding: '8px 12px', borderRadius: 7, marginBottom: 14, fontSize: 12.5,
              background: 'var(--pass-soft)', color: 'var(--pass)',
              border: '1px solid var(--pass-soft)',
            }}>✗ {error}</div>
          )}

          <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
            <button
              onClick={onClose}
              disabled={busy}
              style={{
                padding: '9px 18px', fontSize: 12.5, fontWeight: 500,
                background: 'var(--ink-100)', color: 'var(--ink-700)',
                border: '1px solid var(--ink-200)', borderRadius: 7,
                cursor: busy ? 'default' : 'pointer', fontFamily: 'inherit',
              }}
            >Cancel</button>
            <button
              onClick={submit}
              disabled={busy || !t.trim()}
              style={{
                padding: '9px 22px', fontSize: 12.5, fontWeight: 600,
                background: (busy || !t.trim()) ? 'var(--ink-300)' : 'var(--ink-900)',
                color: 'white', border: 'none', borderRadius: 7,
                cursor: (busy || !t.trim()) ? 'default' : 'pointer', fontFamily: 'inherit',
                display: 'inline-flex', alignItems: 'center', gap: 6,
              }}
            >
              {busy ? 'Triggering…' : 'Run Analysis →'}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};
