// Phase 2E — Watchlist panel.
//
// First-class editable surface for config/watchlist.json + thresholded alerts.
// AlphaSense-inspired: each watched ticker shows live price, day %, sparkline,
// and any active alerts. Inline form to add a new ticker or set a price-cross
// alert. Auto-refreshes every 30s while the panel is open.

const {
  useState: wlUseState, useEffect: wlUseEffect, useCallback: wlUseCallback,
  useMemo: wlUseMemo, useRef: wlUseRef,
} = React;

// Mini sparkline — same compact format as the rest of the app's spark cards.
function WlSpark({ data }) {
  if (!Array.isArray(data) || data.length < 2) return <span className="wl-spark wl-spark-empty">—</span>;
  const min = Math.min(...data), max = Math.max(...data);
  const range = max - min || 1;
  const w = 60, h = 18;
  const pts = data.map((v, i) => {
    const x = (i / (data.length - 1)) * w;
    const y = h - ((v - min) / range) * h;
    return `${x.toFixed(1)},${y.toFixed(1)}`;
  }).join(' ');
  const dir = data[data.length - 1] >= data[0] ? 'up' : 'down';
  return (
    <svg className={`wl-spark wl-spark-${dir}`} viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none">
      <polyline points={pts} fill="none" strokeWidth="1.4" />
    </svg>
  );
}

// Parse the thesis-driven watchlist (brain/4-Stocks/Watchlist/Watchlist.md)
// into themed tables. The file is structured as `## Theme` sections, each
// containing one or more markdown tables with columns starting with Ticker.
// We don't try to be a full markdown parser — just enough to extract
// {theme, rows} pairs for browse-by-theme rendering.
function parseVaultWatchlist(md) {
  if (!md) return [];
  // Strip YAML frontmatter
  const fmMatch = md.match(/^---\s*\n[\s\S]*?\n---\s*\n/);
  const body = fmMatch ? md.slice(fmMatch[0].length) : md;

  // Split on `\n## ` — first chunk is the file intro, subsequent chunks
  // each start with the theme name on the first line.
  const parts = body.split(/\n## /);
  const themes = [];
  for (let i = 1; i < parts.length; i++) {
    const chunk = parts[i];
    const newlineAt = chunk.indexOf('\n');
    const themeName = (newlineAt >= 0 ? chunk.slice(0, newlineAt) : chunk).trim();
    const themeBody = newlineAt >= 0 ? chunk.slice(newlineAt + 1) : '';

    // Find every markdown table — they look like:
    //   | A | B |
    //   |---|---|
    //   | a | b |
    //   ...
    // Match the header row + separator + body rows until a blank line.
    const tables = [];
    const lines = themeBody.split('\n');
    let cur = null;          // {label, headers, rows}
    let pendingLabel = null; // last "**ETFs:**" type label seen before a table
    for (const line of lines) {
      const t = line.trim();
      // Stop at the next theme separator (---) but only if not in a table
      if (t === '---' && !cur) continue;
      // Track inline section labels that precede sub-tables
      const labelMatch = t.match(/^\*\*(.+?):?\*\*$/);
      if (labelMatch && !cur) {
        pendingLabel = labelMatch[1].trim();
        continue;
      }
      // Table line?
      if (t.startsWith('|')) {
        const cells = t.slice(1, t.endsWith('|') ? -1 : undefined)
                       .split('|').map(c => c.trim());
        // Header line — followed by separator. Detect by first row in fresh table.
        if (!cur) {
          cur = { label: pendingLabel, headers: cells, rows: [] };
          pendingLabel = null;
        } else if (cells.every(c => /^-+$/.test(c.replace(/[: ]/g, '')))) {
          // separator — skip
          continue;
        } else {
          cur.rows.push(cells);
        }
      } else if (cur) {
        // Blank line or non-table — close the current table
        if (cur.headers.length && cur.rows.length) tables.push(cur);
        cur = null;
      }
    }
    if (cur && cur.headers.length && cur.rows.length) tables.push(cur);
    if (tables.length) themes.push({ name: themeName, tables });
  }
  return themes;
}

// Strip Obsidian-style [[wikilinks]] for display — keep just the visible text.
function stripWikilinks(s) {
  return String(s || '').replace(/\[\[(?:[^\]|]*\|)?([^\]]+)\]\]/g, '$1');
}

function WatchlistPanel({ open, onClose }) {
  if (!open) return null;
  const [mode, setMode] = wlUseState('live'); // 'live' | 'theme'
  const [tickers, setTickers] = wlUseState([]);
  const [quotes, setQuotes] = wlUseState({});
  const [alerts, setAlerts] = wlUseState([]);
  const [loading, setLoading] = wlUseState(false);
  const [err, setErr] = wlUseState('');
  const [activeTicker, setActiveTicker] = wlUseState(null);
  const [newTicker, setNewTicker] = wlUseState('');
  const [adding, setAdding] = wlUseState(false);
  const [alertForm, setAlertForm] = wlUseState({ condition: 'price_above', threshold: '', note: '' });
  const [busyAlert, setBusyAlert] = wlUseState(false);
  const [themeData, setThemeData] = wlUseState(null);   // {themes, mtime?}
  const [themeLoading, setThemeLoading] = wlUseState(false);
  const [themeFilter, setThemeFilter] = wlUseState('');   // free-text filter applied across all rows
  const [openThemes, setOpenThemes] = wlUseState({});    // themeName → bool (collapsed)
  const refreshTimerRef = wlUseRef(null);

  const refresh = wlUseCallback(async () => {
    setLoading(true);
    setErr('');
    try {
      const [wlR, qR, alR] = await Promise.all([
        fetch('/api/watchlist'),
        fetch('/api/watchlist/quotes'),
        fetch('/api/alerts?active_only=true'),
      ]);
      if (!wlR.ok) throw new Error(`watchlist HTTP ${wlR.status}`);
      const wl = await wlR.json();
      setTickers(wl.tickers || []);
      if (qR.ok) {
        const q = await qR.json();
        setQuotes(q.quotes || {});
      }
      if (alR.ok) {
        const a = await alR.json();
        setAlerts(a.alerts || []);
      }
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setLoading(false);
    }
  }, []);

  // Initial load + 30s polling refresh while open
  wlUseEffect(() => {
    if (!open) return;
    refresh();
    refreshTimerRef.current = setInterval(refresh, 30000);
    return () => {
      if (refreshTimerRef.current) clearInterval(refreshTimerRef.current);
      refreshTimerRef.current = null;
    };
  }, [open, refresh]);

  // Lazy-load the by-theme markdown when the user first switches to that tab.
  // Cache on the panel; user can refresh by closing/reopening or hitting the
  // refresh button. The vault file is 50+KB so we only fetch on demand.
  const loadThemeData = wlUseCallback(async () => {
    setThemeLoading(true);
    setErr('');
    try {
      const r = await fetch('/api/vault/note?path=' + encodeURIComponent('4-Stocks/Watchlist/Watchlist.md'));
      if (!r.ok) throw new Error(`vault note HTTP ${r.status}`);
      const j = await r.json();
      const md = j.content || j.body || '';
      const themes = parseVaultWatchlist(md);
      setThemeData({ themes, mtime: j.mtime || null, raw_chars: md.length });
      // Default: all themes expanded
      const allOpen = {};
      themes.forEach(t => { allOpen[t.name] = true; });
      setOpenThemes(allOpen);
    } catch (e) {
      setErr(`Vault watchlist load: ${e.message || e}`);
    } finally {
      setThemeLoading(false);
    }
  }, []);

  wlUseEffect(() => {
    if (mode === 'theme' && !themeData && !themeLoading) {
      loadThemeData();
    }
  }, [mode, themeData, themeLoading, loadThemeData]);

  const addTicker = async () => {
    const t = (newTicker || '').trim().toUpperCase();
    if (!t || adding) return;
    setAdding(true);
    setErr('');
    try {
      const r = await fetch('/api/watchlist/add', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ticker: t }),
      });
      if (!r.ok) {
        const tx = await r.text().catch(() => '');
        throw new Error(`Add failed (${r.status}): ${tx.slice(0, 120)}`);
      }
      setNewTicker('');
      refresh();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setAdding(false);
    }
  };

  const removeTicker = async (t) => {
    if (!window.confirm(`Remove ${t} from your watchlist?`)) return;
    try {
      const r = await fetch(`/api/watchlist/${encodeURIComponent(t)}`, { method: 'DELETE' });
      if (!r.ok) throw new Error(`Remove failed (${r.status})`);
      if (activeTicker === t) setActiveTicker(null);
      refresh();
    } catch (e) {
      setErr(e.message || String(e));
    }
  };

  const createAlert = async (ticker) => {
    if (busyAlert) return;
    const condition = alertForm.condition;
    let threshold = alertForm.threshold ? parseFloat(alertForm.threshold) : null;
    // price_above / price_below need a numeric threshold; news / earnings don't
    const needsThreshold = ['price_above', 'price_below'].includes(condition);
    if (needsThreshold && (threshold == null || isNaN(threshold))) {
      setErr('Set a numeric price threshold for price alerts.');
      return;
    }
    setBusyAlert(true);
    setErr('');
    try {
      const r = await fetch('/api/alerts', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          ticker,
          condition,
          threshold: needsThreshold ? threshold : null,
          note: alertForm.note || `${condition} on ${ticker}`,
        }),
      });
      if (!r.ok) throw new Error(`Create alert failed (${r.status})`);
      setAlertForm({ condition: 'price_above', threshold: '', note: '' });
      refresh();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusyAlert(false);
    }
  };

  const dismissAlert = async (alertId) => {
    try {
      const r = await fetch(`/api/alerts/${encodeURIComponent(alertId)}`, { method: 'DELETE' });
      if (!r.ok) throw new Error(`Dismiss failed (${r.status})`);
      refresh();
    } catch (e) {
      setErr(e.message || String(e));
    }
  };

  // Group alerts by ticker for inline display under each row
  const alertsByTicker = wlUseMemo(() => {
    const m = {};
    for (const a of alerts) {
      if (!m[a.ticker]) m[a.ticker] = [];
      m[a.ticker].push(a);
    }
    return m;
  }, [alerts]);

  const activeQuote = activeTicker ? quotes[activeTicker] : null;
  const activeAlertsList = activeTicker ? (alertsByTicker[activeTicker] || []) : [];

  return (
    <div className="wl-overlay" onClick={(e) => { if (e.target.classList.contains('wl-overlay')) onClose(); }}>
      <div className="wl-shell">
        <div className="wl-head">
          <div>
            <div className="wl-eyebrow">Watchlist · alerts</div>
            <div className="wl-title">Your <em>watchlist</em></div>
            <div className="wl-sub">Live prices, day %, sparklines, threshold alerts. Auto-refreshes every 30s. Add tickers below; click a row to set a price-cross alert.</div>
          </div>
          <button className="wl-close" onClick={onClose}>Close</button>
        </div>

        {err && <div className="wl-err">⚠ {err}</div>}

        {/* Mode tabs — Live (default, existing functionality) | By theme
            (parses brain/4-Stocks/Watchlist/Watchlist.md). Different views
            of the same idea: live = which tickers am I tracking right now;
            theme = what's on the radar by sector/thesis axis. */}
        <div style={{
          display: 'flex', gap: 4, padding: '4px 0 12px',
          borderBottom: '1px dashed var(--line)', marginBottom: 12,
        }}>
          {[
            { id: 'live', label: 'Live · alerts' },
            { id: 'theme', label: 'By theme' },
          ].map(tab => (
            <button
              key={tab.id}
              onClick={() => setMode(tab.id)}
              style={{
                padding: '6px 14px',
                fontSize: 11, letterSpacing: '0.06em',
                fontFamily: 'var(--font-body)',
                background: mode === tab.id ? 'var(--accent-2)' : 'transparent',
                color: mode === tab.id ? 'var(--accent)' : 'var(--text-dim)',
                border: '1px solid ' + (mode === tab.id ? 'var(--accent)' : 'var(--line)'),
                borderRadius: 3,
                cursor: 'pointer',
                fontWeight: mode === tab.id ? 600 : 400,
              }}
            >
              {tab.label}
            </button>
          ))}
        </div>

        {mode === 'theme' ? (
          <ThemeView
            data={themeData}
            loading={themeLoading}
            filter={themeFilter}
            onFilter={setThemeFilter}
            openThemes={openThemes}
            onToggleTheme={(name) => setOpenThemes(t => ({ ...t, [name]: !t[name] }))}
            onReload={loadThemeData}
          />
        ) : (
        <>
        <div className="wl-add">
          <input
            type="text"
            className="wl-add-input"
            value={newTicker}
            onChange={(e) => setNewTicker(e.target.value.toUpperCase())}
            onKeyDown={(e) => { if (e.key === 'Enter') addTicker(); }}
            placeholder="Add ticker (e.g. CRWV)"
            maxLength={6}
            autoComplete="off"
          />
          <button className="wl-add-btn" onClick={addTicker} disabled={adding || !newTicker.trim()}>
            {adding ? 'Adding…' : '+ Add'}
          </button>
          <button className="wl-refresh-btn" onClick={refresh} disabled={loading} title="Refresh quotes">
            {loading ? '⟳ refreshing…' : '↻'}
          </button>
        </div>

        <div className="wl-grid">
          {/* List on the left */}
          <div className="wl-list">
            {tickers.length === 0 && !loading && (
              <div className="wl-empty">No tickers yet. Add one above to start tracking.</div>
            )}
            {tickers.map(t => {
              const q = quotes[t] || {};
              const pct = q.changePct;
              const pctColor = pct == null ? 'var(--text-dim)' : (pct >= 0 ? 'var(--accent)' : 'var(--danger)');
              const tickerAlerts = alertsByTicker[t] || [];
              return (
                <button
                  key={t}
                  className={`wl-row ${activeTicker === t ? 'active' : ''}`}
                  onClick={() => setActiveTicker(t === activeTicker ? null : t)}
                >
                  <span className="wl-row-ticker">${t}</span>
                  <span className="wl-row-price">
                    {q.price != null ? `$${q.price.toFixed(2)}` : '—'}
                  </span>
                  <span className="wl-row-pct" style={{ color: pctColor }}>
                    {pct == null ? '—' : `${pct >= 0 ? '+' : ''}${pct.toFixed(2)}%`}
                  </span>
                  <WlSpark data={q.spark}/>
                  <span className="wl-row-alerts">
                    {tickerAlerts.length > 0 && (
                      <span className="wl-alert-badge" title={`${tickerAlerts.length} active alert${tickerAlerts.length === 1 ? '' : 's'}`}>
                        ◉ {tickerAlerts.length}
                      </span>
                    )}
                  </span>
                </button>
              );
            })}
          </div>

          {/* Detail / alert form on the right */}
          <div className="wl-detail">
            {!activeTicker && (
              <div className="wl-detail-empty">
                <div style={{ fontSize: 14, color: 'var(--text-2)', marginBottom: 8 }}>Click a ticker to set an alert.</div>
                <div style={{ fontSize: 12, color: 'var(--text-dim)', lineHeight: 1.6 }}>
                  Threshold alerts (price_above / price_below) check on the next quote poll. The backend already polls; this UI just adds the threshold rules. News + earnings alerts hook into the existing alerts table — they fire on the next news / earnings event for that ticker.
                </div>
              </div>
            )}
            {activeTicker && (
              <>
                <div className="wl-detail-head">
                  <div className="wl-detail-ticker">${activeTicker}</div>
                  <div className="wl-detail-stats">
                    {activeQuote?.price != null && <span className="wl-stat"><span className="wl-stat-lbl">Last</span><b>${activeQuote.price.toFixed(2)}</b></span>}
                    {activeQuote?.changePct != null && (
                      <span className="wl-stat"><span className="wl-stat-lbl">Day</span>
                        <b style={{ color: activeQuote.changePct >= 0 ? 'var(--accent)' : 'var(--danger)' }}>
                          {activeQuote.changePct >= 0 ? '+' : ''}{activeQuote.changePct.toFixed(2)}%
                        </b>
                      </span>
                    )}
                    {activeQuote?.volume != null && <span className="wl-stat"><span className="wl-stat-lbl">Vol</span><b>{(activeQuote.volume / 1e6).toFixed(1)}M</b></span>}
                  </div>
                  <button className="wl-remove" onClick={() => removeTicker(activeTicker)} title="Remove from watchlist">
                    ✕ remove
                  </button>
                </div>

                {activeAlertsList.length > 0 && (
                  <div className="wl-active-alerts">
                    <div className="wl-section-eyebrow">Active alerts ({activeAlertsList.length})</div>
                    {activeAlertsList.map(a => (
                      <div key={a.id} className="wl-alert-row">
                        <span className="wl-alert-cond">{a.condition}</span>
                        {a.threshold != null && <span className="wl-alert-thresh">@ ${a.threshold}</span>}
                        {a.note && <span className="wl-alert-note">{a.note}</span>}
                        <button className="wl-alert-dismiss" onClick={() => dismissAlert(a.id)} title="Dismiss alert">✕</button>
                      </div>
                    ))}
                  </div>
                )}

                <div className="wl-form">
                  <div className="wl-section-eyebrow">Set new alert</div>
                  <div className="wl-form-row">
                    <select
                      className="wl-form-select"
                      value={alertForm.condition}
                      onChange={(e) => setAlertForm(f => ({ ...f, condition: e.target.value }))}
                    >
                      <option value="price_above">Price crosses above</option>
                      <option value="price_below">Price crosses below</option>
                      <option value="news">Any news event</option>
                      <option value="earnings">Earnings release</option>
                    </select>
                    {['price_above', 'price_below'].includes(alertForm.condition) && (
                      <input
                        type="number"
                        step="0.01"
                        className="wl-form-input"
                        value={alertForm.threshold}
                        onChange={(e) => setAlertForm(f => ({ ...f, threshold: e.target.value }))}
                        placeholder={`Price (e.g. ${activeQuote?.price ? (activeQuote.price * 1.05).toFixed(2) : '210'})`}
                      />
                    )}
                  </div>
                  <input
                    type="text"
                    className="wl-form-input wl-form-note"
                    value={alertForm.note}
                    onChange={(e) => setAlertForm(f => ({ ...f, note: e.target.value }))}
                    placeholder="Note (optional) — why does this threshold matter?"
                    maxLength={200}
                  />
                  <button className="wl-form-submit" onClick={() => createAlert(activeTicker)} disabled={busyAlert}>
                    {busyAlert ? 'Saving…' : '+ Create alert'}
                  </button>
                </div>
              </>
            )}
          </div>
        </div>
        </>
        )}
      </div>
    </div>
  );
}

// ─────── By-theme view ───────
// Renders the parsed structure from brain/4-Stocks/Watchlist/Watchlist.md
// as collapsible theme groups. Filter input narrows rows by free-text
// match across ticker, company, and "why watching" cells. Click a ticker
// to open its $TICKER.md deep dive in a new tab via /api/vault/note.
function ThemeView({ data, loading, filter, onFilter, openThemes, onToggleTheme, onReload }) {
  const themes = data?.themes || [];
  const filterLower = (filter || '').trim().toLowerCase();

  const matchesFilter = (row) => {
    if (!filterLower) return true;
    return row.some(cell => stripWikilinks(cell).toLowerCase().includes(filterLower));
  };

  // Total candidate count (post-filter) for header summary
  const totalRows = themes.reduce((sum, t) => {
    return sum + t.tables.reduce((ss, tbl) => ss + tbl.rows.filter(matchesFilter).length, 0);
  }, 0);

  if (loading) {
    return (
      <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-dim)' }}>
        Parsing vault watchlist…
      </div>
    );
  }
  if (!data || themes.length === 0) {
    return (
      <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-dim)' }}>
        <div style={{ marginBottom: 12 }}>No themes parsed yet.</div>
        <button onClick={onReload} className="wl-add-btn">↻ Reload</button>
      </div>
    );
  }

  const expandAll = () => {
    themes.forEach(t => { if (!openThemes[t.name]) onToggleTheme(t.name); });
  };
  const collapseAll = () => {
    themes.forEach(t => { if (openThemes[t.name]) onToggleTheme(t.name); });
  };

  return (
    <div>
      <div style={{
        display: 'flex', gap: 8, alignItems: 'center', marginBottom: 12,
      }}>
        <input
          type="text"
          value={filter}
          onChange={(e) => onFilter(e.target.value)}
          placeholder="Filter — ticker / sector / why watching…"
          style={{
            flex: 1, padding: '8px 10px', fontSize: 12,
            background: 'var(--bg-2)', color: 'var(--text)',
            border: '1px solid var(--line-2)', borderRadius: 3,
            fontFamily: 'var(--font-body)',
          }}
        />
        <button onClick={expandAll} style={themeBtnStyle}>expand</button>
        <button onClick={collapseAll} style={themeBtnStyle}>collapse</button>
        <button onClick={onReload} style={themeBtnStyle} title="Re-parse Watchlist.md">↻</button>
      </div>

      <div style={{
        fontSize: 10, color: 'var(--text-dim)', marginBottom: 12,
        fontFamily: "'JetBrains Mono', monospace",
      }}>
        {themes.length} themes · {totalRows} candidate{totalRows === 1 ? '' : 's'}
        {filterLower && ` (matching "${filter}")`}
        {data.raw_chars ? ` · ${(data.raw_chars / 1024).toFixed(1)}KB source` : ''}
      </div>

      {themes.map(theme => {
        const isOpen = openThemes[theme.name];
        const filteredTables = theme.tables.map(tbl => ({
          ...tbl, rows: tbl.rows.filter(matchesFilter),
        })).filter(tbl => tbl.rows.length > 0);
        const themeRowCount = filteredTables.reduce((s, t) => s + t.rows.length, 0);
        // Skip rendering theme header if filter eliminates all its rows
        if (filterLower && themeRowCount === 0) return null;
        return (
          <div key={theme.name} style={{
            marginBottom: 14,
            border: '1px solid var(--line)',
            borderRadius: 4,
            overflow: 'hidden',
          }}>
            <button
              onClick={() => onToggleTheme(theme.name)}
              style={{
                width: '100%', textAlign: 'left',
                padding: '10px 14px',
                background: 'var(--bg-2)',
                color: 'var(--text)',
                border: 'none', borderBottom: isOpen ? '1px solid var(--line)' : 'none',
                cursor: 'pointer',
                display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                fontFamily: 'var(--font-body)',
                fontSize: 12,
              }}
            >
              <span>
                <span style={{
                  display: 'inline-block', width: 12,
                  color: 'var(--accent)', marginRight: 8,
                  fontFamily: "'JetBrains Mono', monospace",
                }}>{isOpen ? '▾' : '▸'}</span>
                <span style={{ fontWeight: 600 }}>{theme.name}</span>
              </span>
              <span style={{
                fontSize: 10, fontFamily: "'JetBrains Mono', monospace",
                color: 'var(--text-dim)',
              }}>
                {themeRowCount} row{themeRowCount === 1 ? '' : 's'}
              </span>
            </button>
            {isOpen && filteredTables.map((tbl, i) => (
              <ThemeTable key={i} table={tbl}/>
            ))}
          </div>
        );
      })}
    </div>
  );
}

function ThemeTable({ table }) {
  return (
    <div style={{
      borderTop: table.label ? '1px dashed var(--line)' : 'none',
    }}>
      {table.label && (
        <div style={{
          padding: '8px 14px 4px', fontSize: 9,
          letterSpacing: '0.18em', textTransform: 'uppercase',
          color: 'var(--accent)', fontWeight: 700,
        }}>
          {table.label}
        </div>
      )}
      <div style={{ overflowX: 'auto' }}>
        <table style={{
          width: '100%', borderCollapse: 'collapse',
          fontSize: 11, fontFamily: 'var(--font-body)',
        }}>
          <thead>
            <tr style={{ borderBottom: '1px solid var(--line)' }}>
              {table.headers.map((h, i) => (
                <th key={i} style={{
                  textAlign: 'left', padding: '8px 10px',
                  fontSize: 9, letterSpacing: '0.14em', textTransform: 'uppercase',
                  color: 'var(--text-dim)', fontWeight: 600,
                  whiteSpace: 'nowrap',
                }}>{h}</th>
              ))}
            </tr>
          </thead>
          <tbody>
            {table.rows.map((row, ri) => (
              <tr key={ri} style={{
                borderBottom: '1px solid var(--line)',
                background: ri % 2 === 0 ? 'transparent' : 'rgba(255,255,255,0.015)',
              }}>
                {row.map((cell, ci) => {
                  const stripped = stripWikilinks(cell);
                  // First column tends to be Ticker — make it tighter and accent-colored
                  const isFirst = ci === 0;
                  return (
                    <td key={ci} style={{
                      padding: '8px 10px',
                      verticalAlign: 'top',
                      color: isFirst ? 'var(--accent)' : 'var(--text)',
                      fontFamily: isFirst ? "'JetBrains Mono', monospace" : 'var(--font-body)',
                      fontWeight: isFirst ? 700 : 400,
                      lineHeight: 1.5,
                      maxWidth: ci >= 3 ? 360 : 'none',
                    }}>
                      {stripped}
                    </td>
                  );
                })}
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

const themeBtnStyle = {
  padding: '6px 12px', fontSize: 10, fontFamily: "'JetBrains Mono', monospace",
  background: 'transparent', color: 'var(--text-2)',
  border: '1px solid var(--line)', borderRadius: 3,
  cursor: 'pointer', letterSpacing: '0.08em', textTransform: 'lowercase',
};

window.WatchlistPanel = WatchlistPanel;
