// PM-Track widgets: the thesis log, the hit-rate fold, the watchlist table,
// the filing-alerts feed, and the discipline heatmap. Every widget reads a
// table-backed /vantage/api/pmtrack/* route (never the agents directly) and
// renders three honest states: loading, unreachable, and "nothing yet".
// Loaded after vantage-ui.jsx; shared through window like every tab file.

const PM_ARROW = { up: '↑', down: '↓', flat: '→' };
const pmTagChips = tags => [
  ...(tags?.tickers || []).map(t => ({ k: `t${t.symbol}`, text: `${t.symbol}${PM_ARROW[t.direction] || '?'}`, color: VG.ink })),
  ...(tags?.sectors || []).map(s => ({ k: `s${s.sector}`, text: `${s.sector}${PM_ARROW[s.direction] || '?'}`, color: VG.ink2 })),
  ...(tags?.macro || []).map(m => ({ k: `m${m.instrument}`, text: `${m.instrument}${PM_ARROW[m.direction] || '?'}`, color: VG.ink2 })),
];
const pmPct = v => v == null ? '—' : `${Math.round(v * 100)}%`;
const pmMove = (v, unit) => v == null ? 'n/a' : unit === 'bps' ? `${v > 0 ? '+' : v < 0 ? '−' : ''}${Math.abs(Math.round(v))}bp` : vgPct(v);
const PM_RESULT = { hit: ['✓ hit', VG.up], miss: ['✗ miss', VG.down], flat: ['→ flat', VG.ink3] };

// ── Thesis log: last 10 calls, tags, score, review; click for the EOD comparison ──
function VgThesisLog({ refreshKey = 0, limit = 10 }) {
  const [data, setData] = React.useState(undefined);   // undefined loading · null unreachable · { theses }
  const [hit, setHit] = React.useState(null);
  const [open, setOpen] = React.useState(null);
  React.useEffect(() => {
    let alive = true;
    vgGet(`/vantage/api/pmtrack/theses?limit=${limit}`).then(d => alive && setData(d)).catch(() => alive && setData(null));
    vgGet('/vantage/api/pmtrack/hitrate').then(d => alive && setHit(d)).catch(() => alive && setHit(null));
    return () => { alive = false; };
  }, [refreshKey, limit]);
  const theses = data?.theses || [];
  const d30 = hit?.d30?.all;
  return (
    <section style={{ background: VG.tile, borderRadius: 18, padding: '15px 18px', marginBottom: 14 }}>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginBottom: 10, flexWrap: 'wrap' }}>
        <span style={vgS.caps}>thesis log · last {limit}</span>
        {d30?.n > 0 && (
          <span style={{ fontSize: 11.5, color: VG.ink3 }}>
            30d hit rate <b style={{ color: VG.ink }}>{pmPct(d30.rate)}</b> ({d30.hits}/{d30.n}{d30.thin ? ', small sample' : ''})
          </span>
        )}
        <span style={{ flex: 1 }} />
        <span style={{ fontSize: 11, color: VG.ink4 }}>reply to the 7:30 brief to log one</span>
      </div>
      {data === undefined ? <div style={{ color: VG.ink3, fontSize: 12.5 }}>reading the log…</div>
        : data === null ? <div style={{ color: VG.ink3, fontSize: 12.5 }}>thesis log unreachable right now — returns automatically.</div>
        : theses.length === 0 ? <div style={{ color: VG.ink3, fontSize: 12.5 }}>no theses yet. The first reply to a morning brief starts the record.</div>
        : (
          <div style={{ display: 'flex', flexDirection: 'column' }}>
            {theses.map((t, i) => {
              const s = t.summary || {};
              const isOpen = open === t.id;
              const scoreText = t.scores?.length === 0 ? (t.scoredAt ? 'nothing scorable' : 'awaiting 4:30')
                : s.n ? `${s.hits}/${s.n} hit${s.flats ? ` · ${s.flats} flat` : ''}${s.unscored ? ` · ${s.unscored} unscored` : ''}`
                : t.scoredAt ? `${t.scores.length} unscored` : 'awaiting 4:30';
              return (
                <div key={t.id} style={{ padding: '8px 2px', borderBottom: i < theses.length - 1 ? `1px solid ${VG.rule}` : 'none' }}>
                  <button onClick={() => setOpen(isOpen ? null : t.id)} aria-expanded={isOpen} style={{
                    appearance: 'none', border: 0, background: 'transparent', cursor: 'pointer', width: '100%', textAlign: 'left', padding: 0, fontFamily: 'inherit',
                    display: 'grid', gridTemplateColumns: '78px 1fr auto', gap: 12, alignItems: 'baseline',
                  }}>
                    <span style={{ ...vgS.num, color: VG.ink4, fontSize: 11.5 }}>{t.thesisDate}</span>
                    <span style={{ minWidth: 0 }}>
                      <span style={{ fontSize: 12.5, color: VG.ink2, display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: isOpen ? 'normal' : 'nowrap' }}>{t.rawText}</span>
                      <span style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 4 }}>
                        {pmTagChips(t.tags).map(c => <span key={c.k} style={{ ...vgS.caps, fontSize: 9, color: c.color, background: VG.chip, borderRadius: 999, padding: '1px 7px' }}>{c.text}</span>)}
                        {t.tagsOverridden && <span style={{ ...vgS.caps, fontSize: 9, color: VG.ink4 }}>retagged</span>}
                        {t.tags?.horizon && t.tags.horizon !== 'today' && <span style={{ ...vgS.caps, fontSize: 9, color: VG.ink4 }}>{t.tags.horizon}</span>}
                      </span>
                    </span>
                    <span style={{ ...vgS.num, fontSize: 11.5, color: s.n ? (s.rate >= 0.5 ? VG.up : VG.down) : VG.ink4, whiteSpace: 'nowrap' }}>{scoreText}</span>
                  </button>
                  {isOpen && (
                    <div style={{ marginTop: 8, paddingLeft: 90 }}>
                      {t.scores?.length ? t.scores.map(sc => (
                        <div key={sc.id} style={{ display: 'grid', gridTemplateColumns: '110px 90px 90px 1fr', gap: 10, fontSize: 12, padding: '3px 0', color: VG.ink2 }}>
                          <span style={{ color: VG.ink }}>{sc.instrument} <span style={{ ...vgS.caps, fontSize: 9, color: VG.ink4 }}>{sc.category}</span></span>
                          <span>called {sc.direction_called === 'unread' ? '?' : PM_ARROW[sc.direction_called]}</span>
                          <span style={vgS.num}>{pmMove(sc.actual_move, sc.unit)}</span>
                          <span style={{ color: sc.result ? PM_RESULT[sc.result][1] : VG.ink4 }}>{sc.result ? PM_RESULT[sc.result][0] : `unscored${sc.note ? ` · ${sc.note}` : ''}`}</span>
                        </div>
                      )) : <div style={{ fontSize: 12, color: VG.ink4 }}>{t.scoredAt ? 'nothing scorable was tagged — /retag in Telegram' : 'scored at 4:30 ET'}</div>}
                      {t.reviewText && <div style={{ fontSize: 12, color: VG.ink3, fontStyle: 'italic', marginTop: 6 }}>“{t.reviewText}”</div>}
                    </div>
                  )}
                </div>
              );
            })}
          </div>
        )}
    </section>
  );
}

// ── Hit rate: 30/90 (and 7) overall and by category, with the CI ──
function VgHitRate({ hit }) {
  if (!hit) return <div style={{ color: VG.ink3, fontSize: 12.5, padding: '8px 2px' }}>hit-rate service unreachable right now.</div>;
  const wins = [['d7', '7d'], ['d30', '30d'], ['d90', '90d']];
  const cats = [['all', 'overall'], ['ticker', 'tickers'], ['sector', 'sectors'], ['macro', 'macro']];
  const cell = (s) => !s || !s.n
    ? <span style={{ color: VG.ink4 }}>—</span>
    : <span title={`${s.hits}/${s.n}${s.flats ? ` · ${s.flats} flat` : ''} · CI ${pmPct(s.lo)}–${pmPct(s.hi)}`}>
        <b style={{ color: VG.ink }}>{pmPct(s.rate)}</b> <span style={{ color: VG.ink4, fontSize: 10.5 }}>{s.hits}/{s.n}{s.thin ? ' ·' : ''}</span>
      </span>;
  return (
    <section style={{ background: VG.tile, borderRadius: 18, padding: '15px 18px', marginBottom: 14 }}>
      <div style={{ display: 'grid', gridTemplateColumns: '90px repeat(3, 1fr)', gap: 8, fontSize: 12.5, ...vgS.num }}>
        <span />
        {wins.map(([, l]) => <span key={l} style={{ ...vgS.caps, color: VG.ink4 }}>{l}</span>)}
        {cats.map(([k, label]) => (
          <React.Fragment key={k}>
            <span style={{ ...vgS.caps, color: VG.ink3 }}>{label}</span>
            {wins.map(([w]) => <span key={w}>{cell(hit[w]?.[k])}</span>)}
          </React.Fragment>
        ))}
      </div>
      <div style={{ fontSize: 11, color: VG.ink4, marginTop: 8 }}>
        {hit.divergence?.line ? hit.divergence.line : hit.divergence?.reason || ''}{hit.divergence?.divergent ? ' — diverging' : ''} · “·” marks a small sample (under 10 calls); hover a cell for the interval.
      </div>
    </section>
  );
}

// ── Watchlist table: ticker, added, note — managed by /watch and /unwatch too ──
function VgWatchlistTable({ watchlist = [], quotes = {}, onWatchChanged, onResearchSym }) {
  const [sym, setSym] = React.useState('');
  const [note, setNote] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);
  const add = async () => {
    const s = sym.trim().toUpperCase();
    if (!s) return;
    setBusy(true); setErr(null);
    try { await vgSend('/vantage/api/watchlist', 'POST', { symbol: s, note: note.trim() || null }); setSym(''); setNote(''); onWatchChanged?.(); }
    catch (ex) { setErr(ex.message); }
    setBusy(false);
  };
  return (
    <section style={{ background: VG.tile, borderRadius: 18, padding: '14px 18px', marginBottom: 14 }}>
      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginBottom: watchlist.length ? 8 : 0 }}>
        <span style={vgS.caps}>watchlist · {watchlist.length}</span>
        <span style={{ fontSize: 11, color: VG.ink4 }}>earnings in the brief · 8-Ks to Telegram · 5-day move on Sunday · /watch and /unwatch</span>
        <span style={{ flex: 1 }} />
        <VgInput value={sym} onChange={e => setSym(e.target.value)} placeholder="ticker" style={{ width: 90, textTransform: 'uppercase' }} />
        <VgInput value={note} onChange={e => setNote(e.target.value)} placeholder="note (optional)" style={{ width: 180 }} />
        <VgBtn small kind="primary" onClick={add} disabled={busy || !sym.trim()}>{busy ? 'adding…' : '+ watch'}</VgBtn>
        {err && <span style={{ fontSize: 11, color: VG.down }}>{err}</span>}
      </div>
      {watchlist.length === 0 ? (
        <div style={{ color: VG.ink3, fontSize: 12.5, paddingTop: 6 }}>nothing watched yet — a dossier's “watch” button or /watch in Telegram adds one.</div>
      ) : (
        <div>
          {watchlist.map((w, i) => {
            const q = quotes[w.symbol];
            const added = w.created_at ? vgDate(w.created_at) : '';
            return (
              <div key={w.id || w.symbol} style={{ display: 'grid', gridTemplateColumns: '80px 90px 100px 1fr auto', gap: 12, alignItems: 'baseline', padding: '6px 2px', borderBottom: i < watchlist.length - 1 ? `1px solid ${VG.rule}` : 'none', fontSize: 12.5 }}>
                <button onClick={() => onResearchSym?.(w.symbol)} title={`open the ${w.symbol} dossier`} style={{ appearance: 'none', border: 0, background: 'transparent', cursor: 'pointer', textAlign: 'left', ...vgS.num, color: VG.ink, fontWeight: 600, padding: 0 }}>{w.symbol}</button>
                <span style={{ ...vgS.num, color: VG.ink3 }}>{q?.price != null ? vgMoney(q.price) : <span style={{ color: VG.ink4 }}>no quote</span>}</span>
                <span style={{ ...vgS.num, color: VG.ink4, fontSize: 11.5 }}>{added}</span>
                <span style={{ color: VG.ink2, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{w.note || <span style={{ color: VG.ink4 }}>—</span>}</span>
                <span onClick={() => vgSend(`/vantage/api/watchlist/${w.symbol}`, 'DELETE').then(onWatchChanged).catch(() => {})} style={{ color: VG.ink4, cursor: 'pointer' }} title="stop watching">✕</span>
              </div>
            );
          })}
        </div>
      )}
    </section>
  );
}

// ── Filing alerts: 8-K / 10-Q hits for watchlist names, as pushed to Telegram ──
function VgFilingAlerts({ onResearchSym }) {
  const [data, setData] = React.useState(undefined);
  React.useEffect(() => {
    let alive = true;
    vgGet('/vantage/api/pmtrack/filings').then(d => alive && setData(d)).catch(() => alive && setData(null));
    return () => { alive = false; };
  }, []);
  const rows = data?.alerts || [];
  return (
    <section style={{ background: VG.tile, borderRadius: 18, padding: '15px 18px', marginBottom: 14 }}>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginBottom: 10, flexWrap: 'wrap' }}>
        <span style={vgS.caps}>filing alerts · watchlist 8-K / 10-Q</span>
        <span style={{ fontSize: 11, color: VG.ink4 }}>polled every 15 minutes · pushed to Telegram within 30</span>
      </div>
      {data === undefined ? <div style={{ color: VG.ink3, fontSize: 12.5 }}>reading…</div>
        : data === null ? <div style={{ color: VG.ink3, fontSize: 12.5 }}>filing alerts unreachable right now.</div>
        : rows.length === 0 ? <div style={{ color: VG.ink3, fontSize: 12.5 }}>no filings from watchlist names yet — quiet is information too.</div>
        : rows.map((f, i) => (
          <div key={f.id} style={{ display: 'grid', gridTemplateColumns: '66px 84px 60px 1fr auto', gap: 12, alignItems: 'baseline', padding: '6px 2px', borderBottom: i < rows.length - 1 ? `1px solid ${VG.rule}` : 'none', fontSize: 12.5 }}>
            <button onClick={() => onResearchSym?.(f.symbol)} style={{ appearance: 'none', border: 0, background: 'transparent', cursor: 'pointer', textAlign: 'left', ...vgS.num, color: VG.ink, fontWeight: 600, padding: 0 }}>{f.symbol}</button>
            <span style={{ ...vgS.num, color: VG.ink4 }}>{f.filedDate || '—'}</span>
            <span style={{ color: VG.ink2 }}>{f.formType}</span>
            <span style={{ fontSize: 11, color: VG.ink4 }}>{f.sentAt ? `sent ${vgDate(f.sentAt)}` : 'not pushed'}</span>
            {f.link ? <a href={f.link} target="_blank" rel="noreferrer" style={{ color: VG.ink3, fontSize: 11.5 }}>document ↗</a> : <span />}
          </div>
        ))}
    </section>
  );
}

// ── Discipline heatmap: a contribution-style year grid + three lines ──
const PM_LEVEL = { 0: { bg: 'transparent', label: 'none' }, 1: { bg: '#22c55e', label: 'green' }, 2: { bg: '#eab308', label: 'yellow' }, 3: { bg: '#ef4444', label: 'red' } };
function VgDiscipline() {
  const [data, setData] = React.useState(undefined);
  React.useEffect(() => {
    let alive = true;
    vgGet('/vantage/api/pmtrack/discipline').then(d => alive && setData(d)).catch(() => alive && setData(null));
    return () => { alive = false; };
  }, []);
  const cells = data?.cells || [];
  const weeks = [];
  for (const c of cells) { const last = weeks[weeks.length - 1]; if (!last || last.key !== c.week) weeks.push({ key: c.week, days: Array(7).fill(null) }); weeks[weeks.length - 1].days[c.dow] = c; }
  const st = data?.stats;
  return (
    <section style={{ background: VG.tile, borderRadius: 18, padding: '15px 18px', marginBottom: 14 }}>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginBottom: 10, flexWrap: 'wrap' }}>
        <span style={vgS.caps}>discipline · {data?.year || ''}</span>
        <span style={{ fontSize: 11, color: VG.ink4 }}>/smoke 0-3 in Telegram · pattern detection, not a scoreboard</span>
        <span style={{ flex: 1 }} />
        {[1, 2, 3].map(l => <span key={l} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 10.5, color: VG.ink4 }}><span style={{ width: 9, height: 9, borderRadius: 2, background: PM_LEVEL[l].bg }} />{l}</span>)}
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 10.5, color: VG.ink4 }}><span style={{ width: 9, height: 9, borderRadius: 2, border: `1px solid ${VG.rule}` }} />0</span>
      </div>
      {data === undefined ? <div style={{ color: VG.ink3, fontSize: 12.5 }}>reading…</div>
        : data === null ? <div style={{ color: VG.ink3, fontSize: 12.5 }}>discipline log unreachable right now.</div>
        : (
          <React.Fragment>
            <div style={{ overflowX: 'auto', paddingBottom: 4 }}>
              <div style={{ display: 'grid', gridAutoFlow: 'column', gridTemplateRows: 'repeat(7, 11px)', gap: 3, width: 'max-content' }}>
                {weeks.map(w => w.days.map((c, dow) => {
                  if (!c) return <span key={`${w.key}${dow}`} style={{ width: 11, height: 11 }} />;
                  const lv = c.level;
                  const bg = c.future ? 'transparent' : lv == null ? 'transparent' : PM_LEVEL[lv].bg;
                  const border = c.future ? 'transparent' : lv == null ? VG.rule : lv === 0 ? VG.ink4 : 'transparent';
                  const title = c.future ? c.d : lv == null ? `${c.d} · ${c.logged ? 'no log' : 'before tracking'}` : `${c.d} · level ${lv} (${PM_LEVEL[lv].label})${c.note ? ` — ${c.note}` : ''}`;
                  return <span key={c.d} title={title} style={{ width: 11, height: 11, borderRadius: 2, background: bg, border: `1px solid ${border}`, boxSizing: 'border-box', opacity: c.future ? 0.15 : lv === 0 ? 0.7 : 1 }} />;
                }))}
              </div>
            </div>
            <div style={{ display: 'flex', gap: 18, flexWrap: 'wrap', fontSize: 12, color: VG.ink2, marginTop: 10 }}>
              <span>30-day average: <b style={{ ...vgS.num, color: VG.ink }}>{st?.avg30 == null ? '—' : st.avg30.toFixed(2)}</b>{st?.n30 ? <span style={{ color: VG.ink4 }}> ({st.n30} logged)</span> : <span style={{ color: VG.ink4 }}> (no logs in 30 days)</span>}</span>
              <span>longest 0/1 streak: <b style={{ ...vgS.num, color: VG.ink }}>{st ? st.longestCleanStreak : '—'}</b> days</span>
              <span>red days this month: <b style={{ ...vgS.num, color: st?.redThisMonth ? VG.down : VG.ink }}>{st ? st.redThisMonth : '—'}</b></span>
            </div>
            {data.finding?.finding
              ? <div style={{ fontSize: 12, color: VG.ink2, marginTop: 8 }}>This week's pattern: {data.finding.finding.line}</div>
              : <div style={{ fontSize: 11.5, color: VG.ink4, marginTop: 8 }}>{data.finding?.reason || 'pattern detection runs Sundays'}</div>}
          </React.Fragment>
        )}
    </section>
  );
}

Object.assign(window, { VgThesisLog, VgHitRate, VgWatchlistTable, VgFilingAlerts, VgDiscipline });
