// MemoTimeline — chronological view of every persona memo.
//
// The autonomous system fires personas on cron, event triggers, alerts,
// and (soon) Twitter inputs. Each fire produces a memo at
//   brain/0-Agents/Memory/<role>/{YYYY-MM-DD}-<slug>.md
//
// Without this view, the trader has to grep through filesystem dumps to
// audit what the team has been saying. This panel surfaces the timeline
// with persona / ticker / source filters + click-to-expand full body.
//
// Reads /api/personas/memos (list) + /api/vault/note (full body on
// expand). Pure consumer — no writes.

const { useState: mUseState, useEffect: mUseEffect, useCallback: mUseCallback,
        useMemo: mUseMemo } = React;

// Persona role → display name + accent color. Mirror the brain's role
// IDs so the chip color is consistent across UI surfaces.
const ROLE_META = {
  'phd-researcher':                { label: 'PhD Researcher',      color: '#8db4d8' },
  'hedging-specialist':            { label: 'Hedging Specialist',  color: '#d96a6a' },
  'framework-portfolio-modeling':  { label: 'Portfolio Modeling',  color: '#5fb37c' },
  'framework-fundamentals':        { label: 'Fundamentals',        color: '#d9a85f' },
  'framework-value-investing':     { label: 'Value Investing',     color: '#b48dd8' },
  'framework-growth-investing':    { label: 'Growth Investing',    color: '#7fc7d9' },
  'framework-mental-models':       { label: 'Mental Models',       color: '#d8a07f' },
  'managing-director':             { label: 'Managing Director',   color: '#ffffff' },
  'jarvis':                        { label: 'Tibeb',               color: 'var(--accent)' },
  'editor-jarvis':                 { label: 'Editor Tibeb',        color: 'var(--accent)' },
  'regime-watcher':                { label: 'Regime Watcher',      color: '#ff9c5e' },
  'promotion-proposer':            { label: 'Promotion Proposer',  color: '#5fd9b3' },
};

const 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.12)' },
  alert:   { label: 'ALERT',   color: '#d9a85f',         bg: 'rgba(217,168,95,0.12)' },
  twitter: { label: 'TWITTER', color: '#5fb3d9',         bg: 'rgba(95,179,217,0.12)' },
  team:    { label: 'TEAM',    color: '#b48dd8',         bg: 'rgba(180,141,216,0.12)' },
  solo:    { label: 'SOLO',    color: 'var(--text-2)',   bg: 'transparent' },
  manual:  { label: 'MANUAL',  color: 'var(--text-2)',   bg: 'transparent' },
};

function MemoTimeline({ open, onClose }) {
  if (!open) return null;
  const [memos, setMemos] = mUseState([]);
  const [total, setTotal] = mUseState(0);
  const [loading, setLoading] = mUseState(true);
  const [err, setErr] = mUseState('');
  const [filter, setFilter] = mUseState({ role: '', ticker: '', search: '', source: '' });
  const [expanded, setExpanded] = mUseState({});  // path → bool
  const [bodies, setBodies] = mUseState({});      // path → full body content

  const load = mUseCallback(async () => {
    setLoading(true);
    setErr('');
    try {
      const params = new URLSearchParams();
      if (filter.role) params.set('role', filter.role);
      if (filter.ticker) params.set('ticker', filter.ticker.toUpperCase().replace(/^\$/, ''));
      // Server-side body search when query is 3+ chars — narrow enough that
      // the SQL-like filter is meaningful, broad enough that users see fast
      // results. Sub-3-char client-side filter handles incremental typing.
      const q = (filter.search || '').trim();
      if (q.length >= 3) params.set('q', q);
      params.set('limit', '200');
      const r = await fetch('/api/personas/memos?' + params.toString());
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const j = await r.json();
      setMemos(j.memos || []);
      setTotal(j.total || 0);
    } catch (e) {
      setErr(String(e.message || e));
    } finally {
      setLoading(false);
    }
  }, [filter.role, filter.ticker, filter.search]);

  mUseEffect(() => {
    if (!open) return;
    // Debounce search: when filter.search changes, wait 300ms before re-fetching
    // so we don't fire a request per keystroke.
    const t = setTimeout(load, 300);
    return () => clearTimeout(t);
  }, [open, load]);

  // Source filter applied client-side over the loaded set. Free-text search
  // ≥ 3 chars is server-side (see load); shorter strings filter client-side.
  const filteredMemos = mUseMemo(() => {
    const q = filter.search.trim().toLowerCase();
    const useClientFilter = q.length > 0 && q.length < 3;
    return memos.filter(m => {
      if (filter.source && m.source !== filter.source) return false;
      if (!useClientFilter) return true;
      const hay = ((m.topic || '') + ' ' + (m.excerpt || '')).toLowerCase();
      return hay.includes(q);
    });
  }, [memos, filter.search, filter.source]);

  const expandMemo = mUseCallback(async (path) => {
    setExpanded(e => ({ ...e, [path]: !e[path] }));
    if (bodies[path]) return;  // already loaded
    try {
      const r = await fetch('/api/vault/note?path=' + encodeURIComponent(path));
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const j = await r.json();
      setBodies(b => ({ ...b, [path]: j.content || j.body || '' }));
    } catch (e) {
      setBodies(b => ({ ...b, [path]: `_(failed to load: ${e.message})_` }));
    }
  }, [bodies]);

  // Distinct roles in the loaded set, for the role pills
  const rolesPresent = mUseMemo(() => {
    const counts = {};
    for (const m of memos) {
      counts[m.role] = (counts[m.role] || 0) + 1;
    }
    return Object.entries(counts).sort((a, b) => b[1] - a[1]);
  }, [memos]);

  const [firing, setFiring] = mUseState(false);
  const [fireMsg, setFireMsg] = mUseState('');

  // Manual persona fire — POSTs /api/personas/{role}/fire which spawns
  // a background asyncio task and returns 202 immediately. The memo
  // lands in 0-Agents/Memory/<role>/ in 20-60s; the timeline auto-
  // refreshes after firing so the user sees it appear.
  const fireRole = mUseCallback(async (role) => {
    if (firing) return;
    setFiring(true);
    setFireMsg(`firing ${ROLE_META[role]?.label || role}…`);
    try {
      const r = await fetch(`/api/personas/${encodeURIComponent(role)}/fire`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({}),  // use the persona's default cron_topic
      });
      if (!r.ok) {
        const t = await r.text();
        throw new Error(`HTTP ${r.status}: ${t.slice(0, 120)}`);
      }
      setFireMsg(`queued ${ROLE_META[role]?.label || role} — memo lands in 20-60s`);
      // Re-poll memos after 30s to catch the new fire
      setTimeout(load, 30_000);
      setTimeout(load, 60_000);
    } catch (e) {
      setFireMsg(`fire failed: ${e.message || e}`);
    } finally {
      setFiring(false);
      setTimeout(() => setFireMsg(''), 8000);
    }
  }, [firing, load]);

  // Activity stats over the last 24h / 7d, broken down by source.
  // Once event + Twitter triggers are firing, these numbers tell you
  // how much autonomous activity the system produced in the period —
  // a signal of "how much is the team actually doing" without scrolling.
  const stats = mUseMemo(() => {
    const now = Date.now();
    const day = 24 * 60 * 60 * 1000;
    const cutoff24 = now - day;
    const cutoff7 = now - 7 * day;
    const out = {
      d1: { total: 0, bySource: {} },
      d7: { total: 0, bySource: {} },
    };
    for (const m of memos) {
      const t = new Date(m.ts).getTime();
      const src = m.source || 'cron';
      if (t >= cutoff24) {
        out.d1.total++;
        out.d1.bySource[src] = (out.d1.bySource[src] || 0) + 1;
      }
      if (t >= cutoff7) {
        out.d7.total++;
        out.d7.bySource[src] = (out.d7.bySource[src] || 0) + 1;
      }
    }
    return out;
  }, [memos]);

  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: '60px 20px',
    }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', maxWidth: 920, maxHeight: 'calc(100vh - 120px)', overflowY: 'auto',
        background: 'var(--bg)', border: '1px solid var(--line-2)',
        borderRadius: 6, padding: 24, fontFamily: 'var(--font-body)',
      }}>
        {/* Header */}
        <div style={{
          display: 'flex', alignItems: 'baseline', gap: 12,
          paddingBottom: 14, marginBottom: 14, borderBottom: '1px solid var(--line)',
        }}>
          <h1 style={{
            fontFamily: 'Instrument Serif, serif', fontStyle: 'italic',
            fontSize: 24, margin: 0, color: 'var(--text)',
          }}>Persona memos</h1>
          <span style={{
            fontSize: 10, letterSpacing: '0.18em', textTransform: 'uppercase',
            color: 'var(--text-dim)',
          }}>
            timeline · {total} total · {filteredMemos.length} shown
          </span>
          <button onClick={load} disabled={loading} style={{
            marginLeft: 'auto',
            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: 12, marginBottom: 14,
            background: 'rgba(217,106,106,0.12)', color: '#d96a6a',
            border: '1px solid rgba(217,106,106,0.4)', borderRadius: 4,
            fontSize: 11,
          }}>
            {err}
          </div>
        )}

        {/* Filters */}
        <div style={{
          display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center',
          marginBottom: 14,
        }}>
          <input
            type="text"
            value={filter.search}
            onChange={(e) => setFilter(f => ({ ...f, search: e.target.value }))}
            placeholder="Search topic / body (3+ chars searches full body)…"
            style={{
              flex: 1, minWidth: 220, padding: '6px 10px', fontSize: 12,
              background: 'var(--bg-2)', color: 'var(--text)',
              border: '1px solid var(--line-2)', borderRadius: 3,
              fontFamily: 'var(--font-body)',
            }}
          />
          <input
            type="text"
            value={filter.ticker}
            onChange={(e) => setFilter(f => ({ ...f, ticker: e.target.value.toUpperCase() }))}
            placeholder="Ticker"
            maxLength={6}
            style={{
              width: 90, padding: '6px 10px', fontSize: 12,
              background: 'var(--bg-2)', color: 'var(--text)',
              border: '1px solid var(--line-2)', borderRadius: 3,
              fontFamily: "'JetBrains Mono', monospace",
              textTransform: 'uppercase',
            }}
          />
          <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,
              fontFamily: 'var(--font-body)',
            }}
          >
            <option value="">all sources</option>
            <option value="cron">cron</option>
            <option value="event">event</option>
            <option value="alert">alert</option>
            <option value="twitter">twitter</option>
            <option value="team">team</option>
            <option value="solo">solo</option>
          </select>
          {filter.role && (
            <>
              <button onClick={() => setFilter(f => ({ ...f, role: '' }))} style={clearBtnStyle}>
                ✕ {ROLE_META[filter.role]?.label || filter.role}
              </button>
              <button
                onClick={() => fireRole(filter.role)}
                disabled={firing}
                title={`Manually fire ${ROLE_META[filter.role]?.label || filter.role} now — memo lands in 20-60s`}
                style={{
                  padding: '4px 10px', fontSize: 10, fontFamily: "'JetBrains Mono', monospace",
                  background: firing ? 'transparent' : 'rgba(95, 179, 124, 0.12)',
                  color: firing ? 'var(--text-dim)' : '#5fb37c',
                  border: '1px solid ' + (firing ? 'var(--line)' : 'rgba(95, 179, 124, 0.4)'),
                  borderRadius: 3,
                  cursor: firing ? 'wait' : 'pointer',
                }}
              >
                {firing ? '… firing' : '↻ Fire now'}
              </button>
            </>
          )}
        </div>
        {fireMsg && (
          <div style={{
            marginBottom: 10, padding: '6px 10px',
            fontSize: 10, fontFamily: "'JetBrains Mono', monospace",
            color: fireMsg.startsWith('fire failed') ? '#d96a6a' : '#5fb37c',
            background: fireMsg.startsWith('fire failed')
              ? 'rgba(217,106,106,0.10)' : 'rgba(95,179,124,0.08)',
            border: '1px solid ' + (fireMsg.startsWith('fire failed')
              ? 'rgba(217,106,106,0.30)' : 'rgba(95,179,124,0.30)'),
            borderRadius: 3,
          }}>
            {fireMsg}
          </div>
        )}

        {/* Role pills — click to filter */}
        {rolesPresent.length > 0 && (
          <div style={{
            display: 'flex', gap: 4, flexWrap: 'wrap', marginBottom: 14,
          }}>
            {rolesPresent.map(([role, count]) => {
              const meta = ROLE_META[role] || { label: role, color: 'var(--text)' };
              const active = filter.role === role;
              return (
                <button
                  key={role}
                  onClick={() => setFilter(f => ({ ...f, role: active ? '' : role }))}
                  style={{
                    padding: '4px 10px', fontSize: 10,
                    fontFamily: "'JetBrains Mono', monospace",
                    background: active ? meta.color + '22' : 'transparent',
                    color: active ? meta.color : 'var(--text-dim)',
                    border: `1px solid ${active ? meta.color : 'var(--line)'}`,
                    borderRadius: 3,
                    cursor: 'pointer',
                  }}
                >
                  {meta.label} · {count}
                </button>
              );
            })}
          </div>
        )}

        {/* Activity strip — last 24h / 7d activity broken down by source.
            Becomes the "how much autonomous activity happened" pulse
            once events + alerts + twitter are firing. */}
        {(stats.d1.total > 0 || stats.d7.total > 0) && (
          <div style={{
            display: 'flex', gap: 14, padding: '8px 12px', marginBottom: 14,
            background: 'var(--bg-3)', border: '1px solid var(--line)',
            borderRadius: 3, alignItems: 'center',
            fontSize: 11, fontFamily: "'JetBrains Mono', monospace",
          }}>
            <span style={{ color: 'var(--text-dim)', letterSpacing: '0.10em' }}>activity</span>
            <span style={{ color: 'var(--text-dim)' }}>
              <b style={{ color: 'var(--text)' }}>{stats.d1.total}</b> in last 24h
              {stats.d1.total > 0 && (
                <span style={{ marginLeft: 6 }}>
                  ({Object.entries(stats.d1.bySource).map(([s, c], i) => {
                    const meta = SOURCE_META[s] || SOURCE_META.cron;
                    return (
                      <span key={s} style={{ color: meta.color }}>
                        {i > 0 && <span style={{ color: 'var(--text-dim)' }}> · </span>}
                        {c} {s}
                      </span>
                    );
                  })})
                </span>
              )}
            </span>
            <span style={{ color: 'var(--text-dim)', marginLeft: 'auto' }}>
              <b style={{ color: 'var(--text)' }}>{stats.d7.total}</b> in last 7d
            </span>
          </div>
        )}

        {/* Memo list */}
        {loading && memos.length === 0 && (
          <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-dim)' }}>
            Loading memos…
          </div>
        )}
        {!loading && filteredMemos.length === 0 && (
          <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-dim)' }}>
            No memos match these filters.
          </div>
        )}
        {filteredMemos.map(m => {
          const roleMeta = ROLE_META[m.role] || { label: m.role, color: 'var(--text)' };
          const sourceMeta = SOURCE_META[m.source] || SOURCE_META.cron;
          const isOpen = !!expanded[m.path];
          const body = bodies[m.path];
          const ts = new Date(m.ts);
          const tsStr = ts.toLocaleString(undefined, {
            month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit',
          });
          return (
            <div key={m.path} style={{
              marginBottom: 8,
              padding: '10px 12px',
              background: 'var(--bg-2)',
              border: '1px solid var(--line)',
              borderLeft: `3px solid ${roleMeta.color}`,
              borderRadius: 3,
              cursor: 'pointer',
            }}
            onClick={() => expandMemo(m.path)}
            >
              <div style={{
                display: 'flex', gap: 10, alignItems: 'baseline',
                marginBottom: 4,
              }}>
                <span style={{
                  fontSize: 9, letterSpacing: '0.16em',
                  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>
                <span style={{
                  fontSize: 10, color: 'var(--text-dim)',
                  fontFamily: "'JetBrains Mono', monospace",
                  marginLeft: 'auto',
                }}>{tsStr}</span>
              </div>
              <div style={{
                fontSize: 12, color: 'var(--text)',
                marginBottom: 4, lineHeight: 1.4,
              }}>
                {m.topic || '(no topic)'}
              </div>
              {!isOpen && m.excerpt && (
                <div style={{
                  fontSize: 11, color: 'var(--text-dim)',
                  lineHeight: 1.5,
                  display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical',
                  overflow: 'hidden',
                }}>
                  {m.excerpt}
                </div>
              )}
              {isOpen && (
                <div style={{
                  marginTop: 8, paddingTop: 8,
                  borderTop: '1px dashed var(--line)',
                  fontSize: 12, color: 'var(--text-2)',
                  lineHeight: 1.6,
                }}
                onClick={e => e.stopPropagation()}
                >
                  {body == null ? (
                    <span style={{ color: 'var(--text-dim)' }}>Loading…</span>
                  ) : (
                    <div dangerouslySetInnerHTML={{
                      __html: window.marked ? window.marked.parse(body) : body,
                    }}/>
                  )}
                  <div style={{
                    marginTop: 8, fontSize: 9, color: 'var(--text-dim)',
                    fontFamily: "'JetBrains Mono', monospace",
                  }}>
                    {m.path}
                    {m.parent_run_id && ` · run ${m.parent_run_id.slice(0, 8)}`}
                  </div>
                </div>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
}

const clearBtnStyle = {
  padding: '4px 10px', fontSize: 10, fontFamily: "'JetBrains Mono', monospace",
  background: 'rgba(217, 168, 95, 0.12)', color: '#d9a85f',
  border: '1px solid rgba(217, 168, 95, 0.4)', borderRadius: 3,
  cursor: 'pointer',
};

window.MemoTimeline = MemoTimeline;
