// Historical incident view — heatmap (day-of-week × hour) + worst-offenders
// + recent incidents timeline. Data comes from SQLite on the backend via
// /api/history/{heatmap,node-stats,incidents}.
//
// Rules-of-Hooks: every useState/useEffect/useMemo is declared BEFORE any
// early return. See components/admin.jsx:180-210 for the pattern that
// has bitten us before.

const DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const WINDOWS = [
  { k: 7,   l: '7d' },
  { k: 30,  l: '30d' },
  { k: 90,  l: '90d' },
];

function _fmtDur(ms) {
  const s = Math.max(0, Math.floor((ms || 0) / 1000));
  if (s < 60)    return `${s}s`;
  if (s < 3600)  return `${Math.floor(s/60)}m`;
  if (s < 86400) return `${Math.floor(s/3600)}h ${Math.floor((s%3600)/60)}m`;
  return `${Math.floor(s/86400)}d ${Math.floor((s%86400)/3600)}h`;
}

function _fmtTsLocal(ts) {
  if (!ts) return '—';
  const d = new Date(ts);
  const dd = d.toLocaleDateString(undefined, { month: 'short', day: '2-digit' });
  const tt = d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
  return `${dd} ${tt}`;
}

function HeatmapGrid({ grid, nodeLabel }) {
  // `grid` is 7×24. Color intensity scales with per-cell `ms` relative to the max.
  const peak = React.useMemo(() => {
    let m = 0;
    for (const row of grid || []) for (const c of row) if (c.ms > m) m = c.ms;
    return m;
  }, [grid]);

  if (!grid || !grid.length) return null;

  return (
    <div className="card" style={{padding: 0}}>
      <div className="card-head">
        <h3>Incidents · day × hour</h3>
        <span className="sub">{nodeLabel}</span>
        <span className="spacer" style={{flex:1}} />
        <span style={{fontSize: 10, color:'var(--text-mute)'}}>
          darker red = more downtime · hover for detail
        </span>
      </div>
      <div className="card-body" style={{overflow: 'auto'}}>
        <div style={{display:'grid', gridTemplateColumns: 'auto repeat(24, 1fr)', gap: 2, minWidth: 620}}>
          {/* header row — hours */}
          <div></div>
          {Array.from({length: 24}).map((_, h) => (
            <div key={h} style={{fontSize: 9, color: 'var(--text-mute)', textAlign: 'center',
                                   fontFamily: 'var(--mono)'}}>
              {String(h).padStart(2,'0')}
            </div>
          ))}
          {/* rows */}
          {grid.map((row, di) => (
            <React.Fragment key={di}>
              <div style={{fontSize: 10, color: 'var(--text-faint)',
                             paddingRight: 6, alignSelf: 'center',
                             letterSpacing: '0.08em', textTransform: 'uppercase'}}>
                {DAYS[di]}
              </div>
              {row.map((cell, h) => {
                const frac = peak > 0 ? cell.ms / peak : 0;
                const bg = cell.count === 0
                  ? 'var(--surface-3)'
                  // Blend accent (cool) → crit (hot) as frac grows.
                  : (frac < 0.25 ? `oklch(0.72 0.14 195 / ${0.25 + frac})`
                     : frac < 0.75 ? `oklch(0.72 0.14 85 / ${0.55 + frac * 0.25})`
                                   : `oklch(0.66 0.22 27 / ${0.65 + frac * 0.3})`);
                const title = cell.count
                  ? `${DAYS[di]} ${String(h).padStart(2,'0')}:00 · ${cell.count} incident${cell.count===1?'':'s'} · ${_fmtDur(cell.ms)}`
                  : `${DAYS[di]} ${String(h).padStart(2,'0')}:00 · no incidents`;
                return (
                  <div key={h} title={title}
                       style={{aspectRatio: '1 / 1.2', borderRadius: 1, background: bg,
                               minHeight: 14, border: '1px solid transparent'}} />
                );
              })}
            </React.Fragment>
          ))}
        </div>
      </div>
    </div>
  );
}

function WorstOffenders({ stats, onSelectNode, selectedNodeId }) {
  if (!stats) return null;
  if (stats.length === 0) {
    return (
      <div className="card" style={{padding: 0}}>
        <div className="card-head">
          <h3>Worst offenders</h3>
          <span className="sub">no incidents in window</span>
        </div>
      </div>
    );
  }
  const maxMs = Math.max(...stats.map(s => s.ms), 1);
  return (
    <div className="card" style={{padding: 0}}>
      <div className="card-head">
        <h3>Worst offenders</h3>
        <span className="sub">{stats.length} node{stats.length === 1 ? '' : 's'}</span>
        <span className="spacer" style={{flex:1}} />
        <span style={{fontSize: 10, color: 'var(--text-mute)'}}>click a row to filter heatmap</span>
      </div>
      <div style={{padding: 0, maxHeight: 320, overflow: 'auto'}}>
        {stats.map(s => {
          const pct = Math.round((s.ms / maxMs) * 100);
          const sel = selectedNodeId === s.nodeId;
          return (
            <button key={s.nodeId}
              onClick={() => onSelectNode(sel ? null : s.nodeId)}
              style={{
                width: '100%', textAlign: 'left',
                padding: '7px 12px',
                borderBottom: '1px solid var(--border)',
                display: 'grid', gridTemplateColumns: '1.2fr 60px 1fr 80px', gap: 10, alignItems: 'center',
                background: sel ? 'var(--accent-bg)' : 'transparent',
                color: sel ? 'var(--accent)' : 'var(--text)',
                fontSize: 12, fontFamily: 'var(--mono)',
              }}>
              <span style={{fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'}}>
                {s.displayName || s.hostname || s.nodeId}
              </span>
              <span className="chip" style={{background: 'var(--surface-3)', color: 'var(--text-dim)', fontSize: 9}}>
                {s.group || '—'}
              </span>
              <div style={{height: 6, background: 'var(--surface-3)', borderRadius: 1, overflow: 'hidden'}}>
                <div style={{height: '100%', width: `${pct}%`, background: 'var(--crit)'}} />
              </div>
              <span className="num mute" style={{textAlign: 'right'}}>
                {s.count}× · {_fmtDur(s.ms)}
              </span>
            </button>
          );
        })}
      </div>
    </div>
  );
}

function IncidentTimeline({ incidents }) {
  if (!incidents) return null;
  if (incidents.length === 0) {
    return (
      <div className="card" style={{padding: 0}}>
        <div className="card-head">
          <h3>Timeline</h3><span className="sub">no incidents yet</span>
        </div>
        <div className="card-body" style={{color:'var(--text-mute)', fontSize: 12}}>
          History fills up as incidents happen. Trigger one with Tweaks → simulate incident to see it here.
        </div>
      </div>
    );
  }
  return (
    <div className="card" style={{padding: 0}}>
      <div className="card-head">
        <h3>Timeline</h3>
        <span className="sub">{incidents.length} recent · newest first</span>
      </div>
      <div style={{overflow: 'auto', maxHeight: 400}}>
        <table className="tbl">
          <thead>
            <tr>
              <th>node</th><th>group</th><th>when</th><th>duration</th>
              <th>status</th><th style={{textAlign:'right'}}>notified</th>
            </tr>
          </thead>
          <tbody>
            {incidents.map(i => (
              <tr key={i.id}>
                <td style={{fontWeight: 500}}>{i.displayName || i.hostname || i.node_id}</td>
                <td className="mute">{i.group || '—'}</td>
                <td className="mute">{_fmtTsLocal(i.started_at)}</td>
                <td className="num mute">
                  {i.ended_at ? _fmtDur(i.durationMs) : <span style={{color:'var(--crit)'}}>still down · {_fmtDur(i.durationMs)}</span>}
                </td>
                <td>
                  <span className={`chip ${i.max_status}`}>{i.max_status}</span>
                </td>
                <td className="num mute" style={{textAlign: 'right'}}>
                  {i.notifications_sent > 0
                    ? <span className="chip accent" style={{fontSize: 9}}>{i.notifications_sent}×</span>
                    : <span style={{color: 'var(--text-mute)'}}>—</span>}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function History({ runtimeNodes }) {
  // ALL hooks above early returns.
  const [days, setDays] = React.useState(30);
  const [nodeId, setNodeId] = React.useState(null);
  const [data, setData] = React.useState(null);  // { grid, stats, incidents }
  const [loading, setLoading] = React.useState(false);
  const [err, setErr] = React.useState(null);
  const tzOffsetMinutes = -new Date().getTimezoneOffset();

  const selectedNode = React.useMemo(() => {
    if (!nodeId || !runtimeNodes) return null;
    return runtimeNodes.find(n => n.id === nodeId) || null;
  }, [nodeId, runtimeNodes]);

  async function load() {
    setLoading(true); setErr(null);
    try {
      const q = (p) => {
        const sp = new URLSearchParams({ days: String(days), tzOffsetMinutes: String(tzOffsetMinutes) });
        if (nodeId) sp.set('nodeId', nodeId);
        return `${p}?${sp.toString()}`;
      };
      const [heatmap, stats, incidents] = await Promise.all([
        window.apiFetch(q('/api/history/heatmap')),
        window.apiFetch(`/api/history/node-stats?days=${days}`),
        window.apiFetch(q('/api/history/incidents')),
      ]);
      setData({
        grid: heatmap.grid,
        stats: stats.stats || [],
        incidents: incidents.incidents || [],
        total: incidents.total,
      });
    } catch (e) {
      if (e.message !== 'unauthorized') setErr(e.message);
    } finally { setLoading(false); }
  }

  React.useEffect(() => { load(); }, [days, nodeId]);
  // Also auto-refresh every 60s so a brand-new incident shows up without a manual reload.
  React.useEffect(() => {
    const t = setInterval(load, 60_000);
    return () => clearInterval(t);
  }, [days, nodeId]);

  const nodeLabel = selectedNode
    ? `${selectedNode.displayName || selectedNode.hostname} · ${selectedNode.group}`
    : 'all nodes';

  return (
    <div style={{padding: 12, display: 'flex', flexDirection: 'column', gap: 12}}>
      <div className="card" style={{padding: 0}}>
        <div className="card-head" style={{gap: 10}}>
          <h3>History</h3>
          <span className="sub">sqlite · bucketed in local timezone</span>
          <span className="spacer" style={{flex: 1}} />
          <span className="topbar-label">Window</span>
          <div className="seg">
            {WINDOWS.map(w => (
              <button key={w.k} className={days === w.k ? 'on' : ''}
                onClick={() => setDays(w.k)}>{w.l}</button>
            ))}
          </div>
          <span className="topbar-label" style={{marginLeft: 8}}>Node</span>
          <select value={nodeId || ''}
            onChange={e => setNodeId(e.target.value || null)}
            style={{background: 'var(--surface)', border: '1px solid var(--border-2)',
                    borderRadius: 2, color: 'var(--text)', padding: '4px 8px',
                    fontFamily: 'var(--mono)', fontSize: 11}}>
            <option value="">all</option>
            {(runtimeNodes || []).slice().sort((a,b) => (a.displayName||a.hostname||'').localeCompare(b.displayName||b.hostname||'')).map(n => (
              <option key={n.id} value={n.id}>{n.displayName || n.hostname} · {n.group}</option>
            ))}
          </select>
          <button className="btn" onClick={load} disabled={loading}>
            {loading ? '…' : 'reload'}
          </button>
        </div>
      </div>

      {err && <div className="card" style={{padding: 12, color: 'var(--crit)'}}>Failed to load: {err}</div>}

      <HeatmapGrid grid={data?.grid} nodeLabel={nodeLabel} />

      <div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12}}>
        <WorstOffenders stats={data?.stats} onSelectNode={setNodeId} selectedNodeId={nodeId} />
        <IncidentTimeline incidents={data?.incidents} />
      </div>
    </div>
  );
}

Object.assign(window, { History });
