// Logs — live tail with filters, severity colors, JSON detail expanders.
// Wired to GET /api/v1/logs (polling). Query params: limit, level, kind.
// Backend LogEntry shape: {time, level, message, attrs:{kind?, account?, …}}.
const { Card, Pill, fmtTs } = window.UI;

// adapt one backend LogEntry to the Mock-shape this page already renders.
function adaptEntry(e) {
  const attrs = e.attrs || {};
  const kind = attrs.kind != null ? String(attrs.kind) : '';
  const account = attrs.account != null ? String(attrs.account) : '';
  // Strip well-known fields out of the data blob shown in the expander.
  const data = {};
  for (const [k, v] of Object.entries(attrs)) {
    if (k === 'kind' || k === 'account') continue;
    data[k] = v;
  }
  const tsMs = e.time ? Date.parse(e.time) : Date.now();
  const sev = (e.level || 'info').toLowerCase();
  return {
    ts: tsMs,
    sev: sev === 'warning' ? 'warn' : sev,
    kind,
    msg: e.message || '',
    account,
    data,
  };
}

function PageLogs({ app }) {
  const [logs, setLogs] = React.useState([]);
  const [tail, setTail] = React.useState(true);
  const [filters, setFilters] = React.useState({ sev: 'all', kind: '', account: 'all' });
  const [expanded, setExpanded] = React.useState(new Set());
  const [error, setError] = React.useState('');
  const scrollerRef = React.useRef();

  // Poll /api/v1/logs every 2s when tailing; once on mount otherwise.
  React.useEffect(() => {
    let stopped = false;
    const fetchOnce = async () => {
      try {
        const params = { limit: 500 };
        if (filters.sev !== 'all') params.level = filters.sev;
        // server-side filter by attr kind only when text matches a known
        // bucket; otherwise leave kind blank and filter client-side.
        const raw = await window.API.get('/api/v1/logs', params);
        if (stopped) return;
        const adapted = (Array.isArray(raw) ? raw : []).map(adaptEntry);
        // Newest-last for the tail UX.
        adapted.reverse();
        setLogs(adapted);
        setError('');
      } catch (e) {
        if (!stopped) setError(e.message || 'logs fetch failed');
      }
    };
    fetchOnce();
    if (!tail) return () => { stopped = true; };
    const id = setInterval(fetchOnce, 2000);
    return () => { stopped = true; clearInterval(id); };
  }, [tail, filters.sev]);

  React.useEffect(() => {
    if (tail && scrollerRef.current) {
      scrollerRef.current.scrollTop = scrollerRef.current.scrollHeight;
    }
  }, [logs, tail]);

  // Account list comes from app state — empty until the boot fetch
  // hydrates. The dropdown shows only configured accounts.
  const accountOptions = React.useMemo(() => {
    return ((app && app.accounts) || []).map(a => a.id);
  }, [app && app.accounts]);

  const filtered = logs.filter(l => {
    // Severity is enforced server-side too, but client-side filter still
    // narrows when the server already returned mixed levels.
    if (filters.sev !== 'all' && l.sev !== filters.sev) return false;
    if (filters.kind) {
      const q = filters.kind.toLowerCase();
      if (!(l.kind || '').toLowerCase().includes(q) && !(l.msg || '').toLowerCase().includes(q)) {
        return false;
      }
    }
    if (filters.account !== 'all' && l.account !== filters.account) return false;
    return true;
  });

  const sevColor = { debug: 'text-ink-3', info: 'text-inf-0', warn: 'text-amb-0', error: 'text-neg-0' };
  const sevBg = { debug: 'bg-ink-3', info: 'bg-inf-0', warn: 'bg-amb-0', error: 'bg-neg-0' };

  const toggle = (i) => setExpanded(s => { const c = new Set(s); c.has(i) ? c.delete(i) : c.add(i); return c; });

  return (
    <div className="p-4 flex flex-col gap-4 h-[calc(100vh-3rem)]">
      <div className="flex items-end justify-between">
        <div>
          <h1 className="text-[16px] font-medium text-ink-0">logs</h1>
          <p className="mono text-[11px] text-ink-3 mt-0.5">structured JSON · last 500 lines · {filtered.length} match</p>
        </div>
      </div>

      {error && <div className="bg-neg-2/30 border border-neg-1/40 px-3 py-2 mono text-[11px] text-neg-0">{error}</div>}

      <Card padded={false}>
        <div className="px-3 py-2 grid grid-cols-12 gap-3 items-center border-b border-line-0">
          <div className="col-span-2">
            <select value={filters.sev} onChange={e => setFilters(f => ({...f, sev: e.target.value}))}
              className="w-full bg-bg-2 border border-line-1 mono text-[11.5px] h-7 px-2 text-ink-1 outline-none">
              <option value="all">all severities</option>
              <option value="debug">debug</option><option value="info">info</option>
              <option value="warn">warn</option><option value="error">error</option>
            </select>
          </div>
          <div className="col-span-4 relative">
            <Icon.Search size={12} className="absolute left-2 top-2 text-ink-3"/>
            <input value={filters.kind} onChange={e => setFilters(f => ({...f, kind: e.target.value}))}
              placeholder="filter by kind or message…"
              className="w-full bg-bg-2 border border-line-1 mono text-[11.5px] h-7 pl-7 pr-2 text-ink-1 outline-none focus:border-line-2"/>
          </div>
          <div className="col-span-3">
            <select value={filters.account} onChange={e => setFilters(f => ({...f, account: e.target.value}))}
              className="w-full bg-bg-2 border border-line-1 mono text-[11.5px] h-7 px-2 text-ink-1 outline-none">
              <option value="all">all accounts</option>
              {accountOptions.map(id => <option key={id}>{id}</option>)}
            </select>
          </div>
          <div className="col-span-3 flex items-center justify-end gap-2">
            <button onClick={() => setTail(t => !t)}
              className={`h-7 px-2.5 mono text-[11px] uppercase tracking-[0.12em] border inline-flex items-center gap-1.5 ${tail ? 'border-pos-1/40 bg-pos-2/20 text-pos-0' : 'border-line-1 text-ink-2 hover:text-ink-0'}`}>
              <span className={`w-1.5 h-1.5 ${tail ? 'bg-pos-0 pulse-dot' : 'bg-ink-3'}`}></span>
              {tail ? 'tailing' : 'paused'}
            </button>
            <button onClick={() => { setLogs([]); setExpanded(new Set()); }}
              className="h-7 px-2.5 mono text-[11px] uppercase tracking-[0.12em] border border-line-1 text-ink-2 hover:text-ink-0">clear</button>
          </div>
        </div>

        <div ref={scrollerRef} className="overflow-auto scrollbar font-mono" style={{ height: 'calc(100vh - 220px)' }}>
          {filtered.length === 0 && (
            <div className="p-6 text-center mono text-[11.5px] text-ink-3">no log entries match · {tail ? 'waiting for new lines…' : 'tail paused'}</div>
          )}
          {filtered.map((l, i) => {
            const last = tail && i === filtered.length - 1;
            const isOpen = expanded.has(i);
            return (
              <div key={i} className={`group border-b border-line-0/50 ${last ? 'bg-bg-2/60' : ''}`}>
                <button onClick={() => toggle(i)} className="w-full flex items-start gap-2 px-3 py-1 text-left hover:bg-bg-2/40">
                  <span className="mono text-[10.5px] text-ink-4 w-[88px] shrink-0 mt-0.5">{fmtTs(l.ts, { short: true })}</span>
                  <span className={`w-1 h-3.5 mt-0.5 ${sevBg[l.sev] || 'bg-ink-3'} shrink-0`}></span>
                  <span className={`mono text-[10.5px] uppercase tracking-[0.08em] w-12 shrink-0 mt-0.5 ${sevColor[l.sev] || 'text-ink-2'}`}>{l.sev}</span>
                  <span className="mono text-[10.5px] text-ink-3 w-20 shrink-0 mt-0.5">{l.kind || '—'}</span>
                  <span className="mono text-[10.5px] text-ink-3 w-28 shrink-0 mt-0.5 truncate">{l.account || '—'}</span>
                  <span className="mono text-[11.5px] text-ink-1 flex-1 truncate mt-0.5">{l.msg}</span>
                  <Icon.ChevronRight size={11} className={`text-ink-3 mt-1 transition-transform ${isOpen ? 'rotate-90' : ''}`}/>
                </button>
                {isOpen && (
                  <pre className="px-3 pb-2 pl-[120px] mono text-[11px] text-ink-2 leading-[1.55] whitespace-pre-wrap">
{JSON.stringify({ ts: l.ts, sev: l.sev, kind: l.kind, account: l.account, msg: l.msg, ...l.data }, null, 2)}
                  </pre>
                )}
              </div>
            );
          })}
        </div>
      </Card>
    </div>
  );
}

window.PageLogs = PageLogs;
