/* ============================================================
   hud.jsx — Phase 1 full-screen HUD overlay
   ============================================================
   - Cmd+H / Ctrl+H opens overlay with default 3 widgets
   - Esc closes
   - Widgets self-fetch their data
   - Exposed via window.HUD, window.useHUD, window.HUD_WIDGETS
*/

const { useState: _hudUseState, useEffect: _hudUseEffect, useCallback: _hudUseCallback } = React;

/* Format minutes as hours+minutes (196 -> "3h 16m", 45 -> "45m"). */
function _hudFmtMins(m) {
  m = Math.round(m || 0);
  if (m < 60) return `${m}m`;
  const h = Math.floor(m / 60), mm = m % 60;
  return mm ? `${h}h ${mm}m` : `${h}h`;
}

/* ---------- shared HUD state (window-level + custom event) ---------- */
if (typeof window.__hudState__ === 'undefined') {
  window.__hudState__ = { open: false, widgets: null, title: null };
}

function _setHudState(next) {
  window.__hudState__ = { ...window.__hudState__, ...next };
  window.dispatchEvent(new CustomEvent('hud-state-change', { detail: window.__hudState__ }));
}

/* Robust, hook-free global opener. Any surface (topbar grid button, keybind,
   bot, deep-link) can call this to open the overlay — it always sets the
   shared window state AND dispatches 'hud-state-change' so the mounted HUD
   component re-renders. Safe to call before HUD mounts: a cold mount reads
   window.__hudState__ in its initializer. DEFAULT_WIDGETS is assigned below
   (module load order) so we resolve it lazily via window.HUD_DEFAULT_WIDGETS. */
window.openHUD = function (widgets, title) {
  _setHudState({
    open: true,
    widgets: widgets || window.HUD_DEFAULT_WIDGETS || null,
    title: title || 'Tactical Overview',
  });
};
window.closeHUD = function () { _setHudState({ open: false }); };

function useHUD() {
  const [state, setState] = _hudUseState(() => window.__hudState__);
  _hudUseEffect(() => {
    const h = (e) => setState({ ...e.detail });
    window.addEventListener('hud-state-change', h);
    return () => window.removeEventListener('hud-state-change', h);
  }, []);
  const openHUD = _hudUseCallback((widgets, title) => {
    _setHudState({ open: true, widgets: widgets || null, title: title || null });
  }, []);
  const closeHUD = _hudUseCallback(() => {
    _setHudState({ open: false });
  }, []);
  return { open: state.open, widgets: state.widgets, title: state.title, openHUD, closeHUD };
}

/* ---------- individual widget render functions ---------- */

// Active HUD date range — set by the voice/chat HUD router (window.__hudRange__)
// or via deep-link URL params (?from&to&range). null = today only.
function _hudActiveRange() {
  try {
    const r = window.__hudRange__;
    if (r && r.from && r.to && r.label && r.label !== 'today') return r;
  } catch (_) {}
  try {
    const p = new URLSearchParams(window.location.search);
    const from = p.get('from'), to = p.get('to'), label = p.get('range');
    if (from && to && label && label !== 'today') return { from, to, label };
  } catch (_) {}
  return null;
}
function _hudAttEndpoint(range) {
  return range
    ? `/api/attendance/range?from=${encodeURIComponent(range.from)}&to=${encodeURIComponent(range.to)}`
    : '/api/attendance';
}

function _HudTodayRoster() {
  const range = _hudActiveRange();
  const [rows, setRows] = _hudUseState(null);
  const [err, setErr] = _hudUseState(null);
  _hudUseEffect(() => {
    let cancelled = false;
    fetch(_hudAttEndpoint(range), { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => { if (!cancelled) setRows(Array.isArray(d) ? d : []); })
      .catch(e => { if (!cancelled) setErr(String(e)); });
    return () => { cancelled = true; };
  }, []);
  if (err) return <div style={{ color: 'var(--warn)', fontSize: 12 }}>{(window.appT||(s=>s))("Error")}: {err}</div>;
  if (rows === null) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("Loading roster…")}</div>;
  if (!rows.length) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("No attendance data.")}</div>;
  // Range mode: per-person summary over the window (present / late / hours).
  if (range) {
    const sorted = [...rows].sort((a, b) => (b.days_present || 0) - (a.days_present || 0));
    return (
      <div style={{ maxHeight: 360, overflowY: 'auto' }}>
        <div style={{ color: 'var(--text-3)', fontSize: 11, marginBottom: 6 }}>{(window.appT||(s=>s))("Attendance summary")} · {range.label}</div>
        <table className="t" style={{ width: '100%', fontSize: 12 }}>
          <thead><tr>
            <th style={{ textAlign: 'left' }}>{(window.appT||(s=>s))("Person")}</th>
            <th>{(window.appT||(s=>s))("Present")}</th>
            <th>{(window.appT||(s=>s))("Late")}</th>
            <th>{(window.appT||(s=>s))("Absent")}</th>
            <th>{(window.appT||(s=>s))("Hours")}</th>
          </tr></thead>
          <tbody>
            {sorted.map((r, i) => (
              <tr key={r.user_id || i}>
                <td style={{ textAlign: 'left' }}>{r.display_name || r.user_id || '—'}</td>
                <td style={{ fontFamily: 'var(--font-mono)', color: 'var(--ok, #16a34a)' }}>{r.days_present ?? 0}</td>
                <td style={{ fontFamily: 'var(--font-mono)', color: 'var(--warn, #d97706)' }}>{r.days_late ?? 0}</td>
                <td style={{ fontFamily: 'var(--font-mono)', color: 'var(--text-3)' }}>{r.days_absent ?? 0}</td>
                <td style={{ fontFamily: 'var(--font-mono)' }}>{typeof r.hours_worked === 'number' ? r.hours_worked.toFixed(1) : '—'}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  }
  const fmt = (iso) => {
    if (!iso) return '—';
    const d = new Date(iso);
    if (isNaN(d)) return '—';
    // 12-hour AM/PM in the BROWSER's local timezone (matches the rest of
    // the dashboard now that ack DMs and EditableCells are 12h too).
    return d.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit', hour12: true });
  };
  const statusOf = (a) => {
    if (a.signed_off) return 'Off';
    if (a.signed_in)  return a.was_late ? 'Late' : 'In';
    return 'Out';
  };
  const colorFor = (s) => s === 'In' ? 'var(--ok, #16a34a)' : s === 'Late' ? 'var(--warn, #d97706)' : s === 'Off' ? 'var(--text-3, #94a3b8)' : 'var(--text-3)';
  return (
    <div style={{ maxHeight: 360, overflowY: 'auto' }}>
      <table className="t" style={{ width: '100%', fontSize: 12 }}>
        <thead>
          <tr>
            <th style={{ textAlign: 'left' }}>{(window.appT||(s=>s))("Person")}</th>
            <th>{(window.appT||(s=>s))("Status")}</th>
            <th>{(window.appT||(s=>s))("In")}</th>
            <th>{(window.appT||(s=>s))("Off")}</th>
            <th>{(window.appT||(s=>s))("Hours")}</th>
          </tr>
        </thead>
        <tbody>
          {rows.map((r, i) => {
            const s = statusOf(r);
            return (
              <tr key={r.user_id || i}>
                <td style={{ textAlign: 'left' }}>{r.display_name || r.user_id || '—'}</td>
                <td><span style={{ color: colorFor(s), fontFamily: 'var(--font-mono)', fontWeight: 600 }}>{(window.appT||(s=>s))(s)}</span></td>
                <td style={{ fontFamily: 'var(--font-mono)' }}>{fmt(r.sign_in_at)}</td>
                <td style={{ fontFamily: 'var(--font-mono)' }}>{fmt(r.sign_off_at)}</td>
                <td style={{ fontFamily: 'var(--font-mono)' }}>{typeof r.hours_worked === 'number' ? r.hours_worked.toFixed(2) : '—'}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

function _HudHoursBar() {
  const range = _hudActiveRange();
  const [rows, setRows] = _hudUseState(null);
  _hudUseEffect(() => {
    let cancelled = false;
    fetch(_hudAttEndpoint(range), { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : [])
      .then(d => { if (!cancelled) setRows(Array.isArray(d) ? d : []); })
      .catch(() => { if (!cancelled) setRows([]); });
    return () => { cancelled = true; };
  }, []);
  if (rows === null) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("Loading hours…")}</div>;
  const data = rows
    .filter(r => typeof r.hours_worked === 'number' && r.hours_worked > 0)
    .map(r => ({
      label: (r.display_name || r.user_id || '?').split(/\s+/)[0].slice(0, 8),
      value: +Number(r.hours_worked).toFixed(2),
    }))
    .sort((a, b) => b.value - a.value)
    .slice(0, 14);
  if (!data.length) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))(range ? `No hours logged in ${range.label}.` : "No hours logged yet today.")}</div>;
  const BC = window.BarChart;
  if (!BC) return <div style={{ color: 'var(--warn)', fontSize: 12 }}>{(window.appT||(s=>s))("BarChart not loaded.")}</div>;
  return (
    <div>
      <div style={{ color: 'var(--text-3)', fontSize: 11, marginBottom: 4 }}>{(window.appT||(s=>s))("Total hours per person")} · {range ? range.label : (window.appT||(s=>s))("today")}</div>
      <BC data={data} w={560} h={240} />
    </div>
  );
}

function _HudAttendancePie() {
  const range = _hudActiveRange();
  const [rows, setRows] = _hudUseState(null);
  _hudUseEffect(() => {
    let cancelled = false;
    fetch(_hudAttEndpoint(range), { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : [])
      .then(d => { if (!cancelled) setRows(Array.isArray(d) ? d : []); })
      .catch(() => { if (!cancelled) setRows([]); });
    return () => { cancelled = true; };
  }, []);
  if (rows === null) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("Loading…")}</div>;
  let present = 0, late = 0, absent = 0;
  if (range) {
    // Aggregate person-days across the window: on-time / late / absent days.
    rows.forEach(r => {
      late += (r.days_late || 0);
      present += Math.max(0, (r.days_present || 0) - (r.days_late || 0));
      absent += (r.days_absent || 0);
    });
  } else {
    rows.forEach(r => {
      if (r.signed_in && r.was_late) late += 1;
      else if (r.signed_in) present += 1;
      else absent += 1;
    });
  }
  const total = present + late + absent || 1;
  // Inline SVG pie with brand colors
  const size = 220, r = size / 2 - 10, cx = size / 2, cy = size / 2;
  const c = 2 * Math.PI * r;
  let off = 0;
  const slice = (v, color, label) => {
    if (!(v > 0)) return null;
    const len = (v / total) * c;
    const node = (
      <circle key={label} cx={cx} cy={cy} r={r} fill="none" stroke={color} strokeWidth={20}
        strokeDasharray={`${len} ${c - len}`} strokeDashoffset={-off}>
        <title>{label}: {v}</title>
      </circle>
    );
    off += len;
    return node;
  };
  const Legend = ({ color, label, value }) => (
    <div style={{ display:'flex', alignItems:'center', gap: 8, fontSize: 12 }}>
      <span style={{ width: 10, height: 10, background: color, borderRadius: 2, display:'inline-block' }} />
      <span style={{ color: 'var(--text-2)', minWidth: 70 }}>{label}</span>
      <span style={{ fontFamily: 'var(--font-mono)', color: 'var(--text-1)', fontWeight: 600 }}>{value}</span>
    </div>
  );
  return (
    <div style={{ display:'flex', alignItems:'center', gap: 24, flexWrap:'wrap' }}>
      <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} style={{ transform: 'rotate(-90deg)' }}>
        <circle cx={cx} cy={cy} r={r} fill="none" stroke="var(--line)" strokeWidth={20} />
        {slice(present, 'var(--ok, #16a34a)', (window.appT||(s=>s))('Present'))}
        {slice(late,    'var(--warn, #d97706)', (window.appT||(s=>s))('Late'))}
        {slice(absent,  'var(--text-3, #94a3b8)', (window.appT||(s=>s))('Absent'))}
      </svg>
      <div style={{ display:'flex', flexDirection:'column', gap: 8 }}>
        <Legend color="var(--ok, #16a34a)"     label={(window.appT||(s=>s))("Present")} value={present} />
        <Legend color="var(--warn, #d97706)"   label={(window.appT||(s=>s))("Late")}    value={late} />
        <Legend color="var(--text-3, #94a3b8)" label={(window.appT||(s=>s))("Absent")}  value={absent} />
        <div style={{ marginTop: 6, paddingTop: 6, borderTop: '1px solid var(--line)', fontSize: 11, color: 'var(--text-3)' }}>
          {(window.appT||(s=>s))("Total")}: <span style={{ fontFamily:'var(--font-mono)', color:'var(--text-1)' }}>{present + late + absent}</span>
        </div>
      </div>
    </div>
  );
}

/* ---------- Phase 2 widgets ---------- */

// Tiny error boundary so one widget crash doesn't nuke the whole HUD.
class _HudBoundary extends React.Component {
  constructor(p) { super(p); this.state = { err: null }; }
  static getDerivedStateFromError(e) { return { err: String(e && e.message || e) }; }
  componentDidCatch(_e, _info) { /* swallow */ }
  render() {
    if (this.state.err) {
      return <div style={{ color: 'var(--warn)', fontSize: 12, fontFamily: 'var(--font-mono)' }}>
        {(window.appT||(s=>s))("Render error")}: {this.state.err}
      </div>;
    }
    return this.props.children;
  }
}

function _hudFmt12(iso) {
  if (!iso) return '—';
  const d = new Date(iso);
  if (isNaN(d)) return '—';
  return d.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit', hour12: true });
}

function _hudFmt12Date(iso) {
  if (!iso) return '—';
  const d = new Date(iso);
  if (isNaN(d)) return '—';
  return d.toLocaleString([], { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', hour12: true });
}

/* compliance-table — strikes per user from /api/employees/strikes */
function _HudCompliance() {
  const [rows, setRows] = _hudUseState(null);
  const [err, setErr] = _hudUseState(null);
  _hudUseEffect(() => {
    let cancelled = false;
    fetch('/api/employees/strikes', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => { if (!cancelled) setRows(Array.isArray(d) ? d : []); })
      .catch(e => { if (!cancelled) setErr(String(e)); });
    return () => { cancelled = true; };
  }, []);
  if (err) return <div style={{ color: 'var(--warn)', fontSize: 12 }}>{(window.appT||(s=>s))("Error")}: {err}</div>;
  if (rows === null) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("Loading strikes…")}</div>;
  if (!rows.length) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("No employees.")}</div>;
  const sorted = [...rows].sort((a, b) => (b.strike_count || 0) - (a.strike_count || 0));
  const colorFor = (n) => n >= 3 ? 'var(--err, #dc2626)' : n >= 1 ? 'var(--warn, #d97706)' : 'var(--ok, #16a34a)';
  return (
    <div style={{ maxHeight: 360, overflowY: 'auto' }}>
      <table className="t" style={{ width: '100%', fontSize: 12 }}>
        <thead>
          <tr>
            <th style={{ textAlign: 'left' }}>{(window.appT||(s=>s))("Person")}</th>
            <th>{(window.appT||(s=>s))("Strikes")}</th>
            <th>{(window.appT||(s=>s))("Last warning")}</th>
          </tr>
        </thead>
        <tbody>
          {sorted.map((r, i) => {
            const n = +(r.strike_count || 0);
            return (
              <tr key={r.user_id || i}>
                <td style={{ textAlign: 'left' }}>{r.display_name || r.matrix_id || '—'}</td>
                <td><span style={{ color: colorFor(n), fontFamily: 'var(--font-mono)', fontWeight: 700 }}>{n}</span></td>
                <td style={{ fontFamily: 'var(--font-mono)', color: 'var(--text-2)' }}>{_hudFmt12Date(r.last_warning)}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

/* monthly-hours-grid — per-user week_hours sparkline.
   Reuses /api/today/scoreboard (weekly aggregate) since there's no 30-day
   per-day endpoint — we render one sparkline per user from that user's
   scoreboard's day-level breakdown when present. */
function _HudMonthlyHours() {
  const [rows, setRows] = _hudUseState(null);
  const [err, setErr] = _hudUseState(null);
  _hudUseEffect(() => {
    let cancelled = false;
    fetch('/api/today/scoreboard', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => { if (!cancelled) setRows(Array.isArray(d) ? d : []); })
      .catch(e => { if (!cancelled) setErr(String(e)); });
    return () => { cancelled = true; };
  }, []);
  if (err) return <div style={{ color: 'var(--warn)', fontSize: 12 }}>{(window.appT||(s=>s))("Error")}: {err}</div>;
  if (rows === null) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("Loading hours history…")}</div>;
  if (!rows.length) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("No data.")}</div>;
  const SP = window.Sparkline;
  const sorted = [...rows].sort((a, b) => (b.week_hours || 0) - (a.week_hours || 0)).slice(0, 20);
  return (
    <div style={{ maxHeight: 360, overflowY: 'auto' }}>
      <table className="t" style={{ width: '100%', fontSize: 12 }}>
        <thead>
          <tr>
            <th style={{ textAlign: 'left' }}>{(window.appT||(s=>s))("Person")}</th>
            <th style={{ textAlign: 'left' }}>{(window.appT||(s=>s))("Trend (week)")}</th>
            <th>{(window.appT||(s=>s))("Hours")}</th>
            <th>{(window.appT||(s=>s))("Present")}</th>
          </tr>
        </thead>
        <tbody>
          {sorted.map((r, i) => {
            // Build a small synthetic series from on_time_days/late_days/absent_days
            // when day_breakdown isn't a full per-day series.
            const series = Array.isArray(r.day_breakdown)
              ? r.day_breakdown.map(d => +Number(d.hours_worked || 0).toFixed(1))
              : [
                  +(r.on_time_days || 0),
                  +(r.late_days || 0),
                  +(r.days_present || 0),
                  +Number(r.week_hours || 0).toFixed(1),
                ];
            return (
              <tr key={r.user_id || i}>
                <td style={{ textAlign: 'left' }}>{r.display_name || '—'}</td>
                <td style={{ textAlign: 'left', minWidth: 140 }}>
                  {SP ? <SP data={series} w={140} h={28} /> : <span style={{ color:'var(--text-3)' }}>—</span>}
                </td>
                <td style={{ fontFamily: 'var(--font-mono)' }}>{Number(r.week_hours || 0).toFixed(1)}</td>
                <td style={{ fontFamily: 'var(--font-mono)' }}>{r.days_present || 0}/{r.days_expected || 0}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

/* presence-now — uses the presence_today field on /api/attendance rows. */
function _HudPresence() {
  const [rows, setRows] = _hudUseState(null);
  const [err, setErr] = _hudUseState(null);
  _hudUseEffect(() => {
    let cancelled = false;
    fetch('/api/attendance', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => { if (!cancelled) setRows(Array.isArray(d) ? d : []); })
      .catch(e => { if (!cancelled) setErr(String(e)); });
    return () => { cancelled = true; };
  }, []);
  if (err) return <div style={{ color: 'var(--warn)', fontSize: 12 }}>{(window.appT||(s=>s))("Error")}: {err}</div>;
  if (rows === null) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("Loading presence…")}</div>;
  if (!rows.length) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("No data.")}</div>;
  const enriched = rows.map(r => {
    const p = r.presence_today || {};
    const on = +(p.online_minutes || p.online || 0);
    const aw = +(p.away_minutes   || p.away   || 0);
    const off= +(p.offline_minutes|| p.offline|| 0);
    return { name: r.display_name || r.user_id || '—', on, aw, off, total: on + aw + off };
  }).filter(x => x.total > 0).sort((a, b) => b.on - a.on);
  if (!enriched.length) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("No presence telemetry today.")}</div>;
  const max = Math.max(...enriched.map(x => x.total)) || 1;
  return (
    <div style={{ maxHeight: 360, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 6 }}>
      {enriched.map((x, i) => {
        const w = (n) => `${(n / max) * 100}%`;
        return (
          <div key={i} style={{ display: 'grid', gridTemplateColumns: '120px 1fr 70px', alignItems: 'center', gap: 8, fontSize: 12 }}>
            <div style={{ color: 'var(--text-2)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{x.name}</div>
            <div style={{ display: 'flex', height: 12, background: 'rgba(255,255,255,0.04)', borderRadius: 2, overflow: 'hidden' }}>
              <div title={`${(window.appT||(s=>s))("Online")} ${_hudFmtMins(x.on)}`}  style={{ width: w(x.on),  background: 'var(--ok, #16a34a)' }} />
              <div title={`${(window.appT||(s=>s))("Away")} ${_hudFmtMins(x.aw)}`}    style={{ width: w(x.aw),  background: 'var(--warn, #d97706)' }} />
              <div title={`${(window.appT||(s=>s))("Offline")} ${_hudFmtMins(x.off)}`} style={{ width: w(x.off), background: 'var(--text-3, #94a3b8)' }} />
            </div>
            <div style={{ fontFamily: 'var(--font-mono)', color: 'var(--text-1)', textAlign: 'right' }}>{_hudFmtMins(x.on)}</div>
          </div>
        );
      })}
    </div>
  );
}

/* signin-histogram — hours-of-day distribution from today's /api/attendance.
   (No 14-day historical endpoint is exposed publicly — we render today's
   distribution and label that clearly so we're honest about the source.) */
function _HudSigninHistogram() {
  const range = _hudActiveRange();
  const [rows, setRows] = _hudUseState(null);
  const [err, setErr] = _hudUseState(null);
  _hudUseEffect(() => {
    let cancelled = false;
    fetch(_hudAttEndpoint(range), { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => { if (!cancelled) setRows(Array.isArray(d) ? d : []); })
      .catch(e => { if (!cancelled) setErr(String(e)); });
    return () => { cancelled = true; };
  }, []);
  if (err) return <div style={{ color: 'var(--warn)', fontSize: 12 }}>{(window.appT||(s=>s))("Error")}: {err}</div>;
  if (rows === null) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("Loading…")}</div>;
  // Range rows carry a list of sign-in timestamps (sign_in_ats); today rows
  // carry a single sign_in_at. Flatten either into an hour-of-day histogram.
  const times = range
    ? rows.flatMap(r => Array.isArray(r.sign_in_ats) ? r.sign_in_ats : [])
    : rows.map(r => r.sign_in_at).filter(Boolean);
  const bins = new Array(24).fill(0);
  times.forEach(iso => {
    const d = new Date(iso);
    if (!isNaN(d)) bins[d.getHours()] += 1;
  });
  const data = bins.map((v, h) => {
    const am = h === 0 ? '12a' : h < 12 ? `${h}a` : h === 12 ? '12p' : `${h - 12}p`;
    return { label: am, value: v };
  });
  const nonzero = data.some(x => x.value > 0);
  if (!nonzero) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))(range ? `No sign-ins in ${range.label}.` : "No sign-ins yet today.")}</div>;
  const BC = window.BarChart;
  if (!BC) return <div style={{ color: 'var(--warn)', fontSize: 12 }}>{(window.appT||(s=>s))("BarChart not loaded.")}</div>;
  return (
    <div>
      <div style={{ color: 'var(--text-3)', fontSize: 11, marginBottom: 4 }}>{range ? `${(window.appT||(s=>s))("Sign-ins by hour")} · ${range.label}` : (window.appT||(s=>s))("Today's sign-ins by hour (12-hour)")}</div>
      <BC data={data} w={560} h={200} />
    </div>
  );
}

/* audit-recent — last 20 audit events */
function _HudAuditRecent() {
  const [rows, setRows] = _hudUseState(null);
  const [err, setErr] = _hudUseState(null);
  _hudUseEffect(() => {
    let cancelled = false;
    fetch('/api/audit?limit=20', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => { if (!cancelled) setRows(Array.isArray(d) ? d : []); })
      .catch(e => { if (!cancelled) setErr(String(e)); });
    return () => { cancelled = true; };
  }, []);
  if (err) return <div style={{ color: 'var(--warn)', fontSize: 12 }}>{(window.appT||(s=>s))("Error")}: {err}</div>;
  if (rows === null) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("Loading audit…")}</div>;
  if (!rows.length) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("No audit events.")}</div>;
  return (
    <div style={{ maxHeight: 360, overflowY: 'auto' }}>
      <table className="t" style={{ width: '100%', fontSize: 12 }}>
        <thead>
          <tr>
            <th style={{ textAlign: 'left' }}>{(window.appT||(s=>s))("When")}</th>
            {/* #6/#10 extension: Actor = who DID this (admin name, bot label
                if bot-initiated, derived from API's `actor` field). Subject
                = the employee it concerns (kept as a sub-line). */}
            <th style={{ textAlign: 'left' }}>{(window.appT||(s=>s))("Actor · Subject")}</th>
            <th style={{ textAlign: 'left' }}>{(window.appT||(s=>s))("Action")}</th>
          </tr>
        </thead>
        <tbody>
          {rows.map((r, i) => {
            const actor = r.actor || null;
            const subject = r.user_display_name || null;
            return (
              <tr key={r.id || i}>
                <td style={{ fontFamily: 'var(--font-mono)', color: 'var(--text-2)' }}>{_hudFmt12Date(r.occurred_at)}</td>
                <td>
                  <div style={{ display:'flex', flexDirection:'column', lineHeight: 1.25 }}>
                    <span style={{ color: actor ? 'var(--text)' : 'var(--text-3)' }}>{actor || subject || '—'}</span>
                    {actor && subject && actor !== subject && (
                      <span style={{ fontSize: 10.5, color:'var(--text-3)' }}>→ {subject}</span>
                    )}
                  </div>
                </td>
                {/* Issue #32: humanized label (API provides action_label;
                    local table fallback via window.humanizeActionType). */}
                <td style={{ fontFamily: 'var(--font-mono)', color: 'var(--brand, #4af)' }} title={r.action_type || ''}>{r.action_label || (window.humanizeActionType ? window.humanizeActionType(r.action_type) : r.action_type) || '—'}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

/* pto-calendar — current month, cells colored by PTO count */
function _HudPtoCalendar() {
  const [rows, setRows] = _hudUseState(null);
  const [err, setErr] = _hudUseState(null);
  const now = new Date();
  const year = now.getFullYear(), month = now.getMonth();
  _hudUseEffect(() => {
    let cancelled = false;
    const first = new Date(year, month, 1);
    const last  = new Date(year, month + 1, 0);
    const iso = (d) => d.toISOString().slice(0, 10);
    const url = `/api/pto?from_date=${iso(first)}&to_date=${iso(last)}`;
    fetch(url, { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => { if (!cancelled) setRows(Array.isArray(d) ? d : []); })
      .catch(e => { if (!cancelled) setErr(String(e)); });
    return () => { cancelled = true; };
  }, [year, month]);
  if (err) return <div style={{ color: 'var(--warn)', fontSize: 12 }}>{(window.appT||(s=>s))("Error")}: {err}</div>;
  if (rows === null) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("Loading PTO…")}</div>;
  // Count PTO bodies per day.
  const counts = {};
  rows.forEach(p => {
    const s = new Date(p.start_date), e = new Date(p.end_date);
    for (let d = new Date(s); d <= e; d.setDate(d.getDate() + 1)) {
      if (d.getFullYear() === year && d.getMonth() === month) {
        const k = d.getDate();
        counts[k] = (counts[k] || 0) + 1;
      }
    }
  });
  const daysInMonth = new Date(year, month + 1, 0).getDate();
  const firstDow = new Date(year, month, 1).getDay(); // 0=Sun
  const cells = [];
  for (let i = 0; i < firstDow; i++) cells.push(null);
  for (let d = 1; d <= daysInMonth; d++) cells.push(d);
  while (cells.length % 7 !== 0) cells.push(null);
  const max = Math.max(1, ...Object.values(counts));
  const colorFor = (n) => {
    if (!n) return 'rgba(255,255,255,0.04)';
    const t = Math.min(1, n / max);
    // Blend brand-blue → warn for heat
    const r = Math.round(74 + (217 - 74) * t);
    const g = Math.round(170 + (119 - 170) * t);
    const b = Math.round(255 + (6 - 255) * t);
    return `rgb(${r},${g},${b})`;
  };
  const todayDay = (now.getMonth() === month && now.getFullYear() === year) ? now.getDate() : -1;
  const monthName = now.toLocaleString([], { month: 'long', year: 'numeric' });
  const dows = [
    (window.appT||(s=>s))('Su'),
    (window.appT||(s=>s))('Mo'),
    (window.appT||(s=>s))('Tu'),
    (window.appT||(s=>s))('We'),
    (window.appT||(s=>s))('Th'),
    (window.appT||(s=>s))('Fr'),
    (window.appT||(s=>s))('Sa'),
  ];
  return (
    <div>
      <div style={{ fontSize: 11, color: 'var(--text-2)', letterSpacing: '0.1em', textTransform: 'uppercase', marginBottom: 6 }}>
        {monthName} — {rows.length} {(window.appT||(s=>s))("PTO blocks")}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 4 }}>
        {dows.map((d, i) => (
          <div key={`h${i}`} style={{ fontSize: 10, color: 'var(--text-3)', textAlign: 'center', fontFamily: 'var(--font-mono)' }}>{d}</div>
        ))}
        {cells.map((d, i) => d == null ? (
          <div key={i} />
        ) : (
          <div key={i} title={`${d}: ${counts[d] || 0} ${(window.appT||(s=>s))("on PTO")}`}
               style={{
                 background: colorFor(counts[d] || 0),
                 borderRadius: 3,
                 padding: '6px 4px',
                 textAlign: 'center',
                 fontFamily: 'var(--font-mono)',
                 fontSize: 11,
                 color: counts[d] ? '#0b0f14' : 'var(--text-2)',
                 outline: d === todayDay ? '1px solid var(--brand, #4af)' : 'none',
                 fontWeight: counts[d] ? 700 : 400,
               }}>
            {d}{counts[d] ? <div style={{ fontSize: 10 }}>×{counts[d]}</div> : null}
          </div>
        ))}
      </div>
    </div>
  );
}

/* schema-browser — DB table/column introspection via /api/ai/hud-schema */
function _HudSchemaBrowser() {
  const [tables, setTables] = _hudUseState(null);
  const [err, setErr] = _hudUseState(null);
  const [selected, setSelected] = _hudUseState(null);
  _hudUseEffect(() => {
    let cancelled = false;
    fetch('/api/ai/hud-schema', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => {
        if (!cancelled) {
          const arr = Array.isArray(d) ? d : [];
          setTables(arr);
          if (arr.length) setSelected(arr[0].name);
        }
      })
      .catch(e => { if (!cancelled) setErr(String(e)); });
    return () => { cancelled = true; };
  }, []);
  if (err) return <div style={{ color: 'var(--warn)', fontSize: 12 }}>{(window.appT||(s=>s))("Error")}: {err}</div>;
  if (tables === null) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("Loading schema…")}</div>;
  if (!tables.length) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{(window.appT||(s=>s))("No tables found.")}</div>;
  const tbl = tables.find(t => t.name === selected) || tables[0];
  return (
    <div style={{ display: 'flex', gap: 12, height: 340 }}>
      <div style={{ width: 160, overflowY: 'auto', borderRight: '1px solid rgba(74,170,255,0.2)', paddingRight: 8, flexShrink: 0 }}>
        {tables.map(t => (
          <div key={t.name}
               onClick={() => setSelected(t.name)}
               style={{
                 padding: '3px 6px',
                 borderRadius: 3,
                 cursor: 'pointer',
                 fontSize: 11,
                 fontFamily: 'var(--font-mono)',
                 color: t.name === selected ? 'var(--brand, #4af)' : 'var(--text-2)',
                 background: t.name === selected ? 'rgba(74,170,255,0.12)' : 'transparent',
                 marginBottom: 2,
               }}>
            {t.name}
          </div>
        ))}
      </div>
      <div style={{ flex: 1, overflowY: 'auto' }}>
        {tbl.doc && <div style={{ fontSize: 11, color: 'var(--text-3)', marginBottom: 6, fontStyle: 'italic' }}>{tbl.doc}</div>}
        <table className="t" style={{ width: '100%', fontSize: 11 }}>
          <thead>
            <tr>
              <th style={{ textAlign: 'left' }}>{(window.appT||(s=>s))("Name")}</th>
              <th style={{ textAlign: 'left' }}>{(window.appT||(s=>s))("Type")}</th>
              <th>{(window.appT||(s=>s))("Nullable")}</th>
              <th style={{ textAlign: 'left' }}>{(window.appT||(s=>s))("Default")}</th>
              <th>{(window.appT||(s=>s))("PK")}</th>
            </tr>
          </thead>
          <tbody>
            {(tbl.columns || []).map((c, i) => (
              <tr key={c.name || i}>
                <td style={{ fontFamily: 'var(--font-mono)', color: c.pk ? 'var(--brand, #4af)' : 'var(--text-1)' }}>{c.name}</td>
                <td style={{ fontFamily: 'var(--font-mono)', color: 'var(--text-2)' }}>{c.type}</td>
                <td style={{ textAlign: 'center', color: c.nullable ? 'var(--text-3)' : 'var(--ok, #16a34a)' }}>{c.nullable ? (window.appT||(s=>s))('yes') : (window.appT||(s=>s))('no')}</td>
                <td style={{ fontFamily: 'var(--font-mono)', color: 'var(--text-3)', fontSize: 10 }}>{c.default || '—'}</td>
                <td style={{ textAlign: 'center', color: 'var(--brand, #4af)', fontWeight: 700 }}>{c.pk ? 'PK' : ''}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

/* code-search — debounced grep of app/*.py via /api/ai/hud-codebase */
function _HudCodeSearch() {
  const [query, setQuery] = _hudUseState('');
  const [results, setResults] = _hudUseState(null);
  const [err, setErr] = _hudUseState(null);
  const [loading, setLoading] = _hudUseState(false);
  const timerRef = React.useRef(null);
  _hudUseEffect(() => {
    const q = query.trim();
    if (!q) { setResults(null); setErr(null); return; }
    clearTimeout(timerRef.current);
    timerRef.current = setTimeout(() => {
      setLoading(true);
      setErr(null);
      fetch(`/api/ai/hud-codebase?q=${encodeURIComponent(q)}`, { credentials: 'same-origin' })
        .then(r => r.ok ? r.json() : Promise.reject(r.status))
        .then(d => { setResults(Array.isArray(d) ? d : []); setLoading(false); })
        .catch(e => { setErr(String(e)); setLoading(false); });
    }, 300);
    return () => clearTimeout(timerRef.current);
  }, [query]);
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8, height: 340 }}>
      <input
        type="text"
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder={(window.appT||(s=>s))("Type a search term…")}
        style={{
          background: 'rgba(255,255,255,0.06)',
          border: '1px solid rgba(74,170,255,0.35)',
          borderRadius: 4,
          color: 'var(--text-1)',
          fontFamily: 'var(--font-mono)',
          fontSize: 12,
          padding: '6px 10px',
          outline: 'none',
          width: '100%',
          boxSizing: 'border-box',
        }}
      />
      {loading && <div style={{ color: 'var(--text-3)', fontSize: 11 }}>{(window.appT||(s=>s))("Searching…")}</div>}
      {err && <div style={{ color: 'var(--warn)', fontSize: 11 }}>{(window.appT||(s=>s))("Error")}: {err}</div>}
      {results !== null && !loading && (
        results.length === 0
          ? <div style={{ color: 'var(--text-3)', fontSize: 11 }}>{(window.appT||(s=>s))("No matches.")}</div>
          : <div style={{ overflowY: 'auto', flex: 1, display: 'flex', flexDirection: 'column', gap: 4 }}>
              {results.map((m, i) => (
                <div key={i} style={{ fontSize: 11, lineHeight: 1.5, borderBottom: '1px solid rgba(255,255,255,0.05)', paddingBottom: 4 }}>
                  <span style={{ fontFamily: 'var(--font-mono)', color: 'var(--brand, #4af)' }}>{m.path}</span>
                  <span style={{ color: 'var(--text-3)', margin: '0 4px' }}>:{m.line}</span>
                  <code style={{ color: 'var(--text-2)', wordBreak: 'break-all' }}>{m.snippet}</code>
                </div>
              ))}
            </div>
      )}
      {results === null && !loading && !err && (
        <div style={{ color: 'var(--text-3)', fontSize: 11 }}>{(window.appT||(s=>s))("Type a search term to grep app/*.py")}</div>
      )}
    </div>
  );
}

function _HudEfficiency() {
  const [rows, setRows] = _hudUseState(null);
  const [err, setErr] = _hudUseState(null);
  _hudUseEffect(() => {
    let cancelled = false;
    fetch('/api/efficiency/roster?period=month', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => { if (!cancelled) setRows((d && d.rows) || []); })
      .catch(e => { if (!cancelled) setErr(String(e)); });
    return () => { cancelled = true; };
  }, []);
  const T = (window.appT || (s => s));
  if (err) return <div style={{ color: 'var(--warn)', fontSize: 12 }}>{T("Error")}: {err}</div>;
  if (rows === null) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{T("Loading efficiency…")}</div>;
  if (!rows.length) return <div style={{ color: 'var(--text-3)', fontSize: 12 }}>{T("No data.")}</div>;
  const bandColor = { top: '#1f6feb', standard: '#2e6f40', warn: '#b26a00', exit_risk: '#b3261e' };
  const actLabel = { reward: 'Reward', retain: 'Retain', coach: 'Coach', formal_warning: 'Warn', pip: 'PIP', investigate: 'Investigate', monitor: 'Monitor' };
  return (
    <div style={{ maxHeight: 360, overflowY: 'auto' }}>
      <table className="t" style={{ width: '100%', fontSize: 12 }}>
        <thead><tr>
          <th style={{ textAlign: 'left' }}>{T("Person")}</th><th>{T("Eff")}</th>
          <th>{T("Trend")}</th><th>{T("Action")}</th>
        </tr></thead>
        <tbody>
          {rows.map((r, i) => (
            <tr key={r.user_id || i}>
              <td style={{ textAlign: 'left' }}>{r.display_name}</td>
              <td style={{ fontWeight: 600, color: bandColor[r.band] || 'inherit' }}>{Math.round(r.efficiency)}%</td>
              <td>{r.trend === 'improving' ? '▲' : r.trend === 'declining' ? '▼' : '–'}</td>
              <td>{(r.action && actLabel[r.action.action]) || (r.action && r.action.action) || ''}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

const HUD_WIDGETS = {
  'efficiency-leaderboard': { id: 'efficiency-leaderboard', title: (window.appT||(s=>s))('Efficiency leaderboard'),         render: () => <_HudBoundary><_HudEfficiency /></_HudBoundary> },
  'today-roster':       { id: 'today-roster',       title: (window.appT||(s=>s))('Today — Roster'),                      render: () => <_HudBoundary><_HudTodayRoster /></_HudBoundary> },
  'hours-bar':          { id: 'hours-bar',          title: (window.appT||(s=>s))('Hours Worked'),                        render: () => <_HudBoundary><_HudHoursBar /></_HudBoundary> },
  'attendance-pie':     { id: 'attendance-pie',     title: (window.appT||(s=>s))('Attendance Breakdown'),                render: () => <_HudBoundary><_HudAttendancePie /></_HudBoundary> },
  'compliance-table':   { id: 'compliance-table',   title: (window.appT||(s=>s))('Compliance — strikes per user'),       render: () => <_HudBoundary><_HudCompliance /></_HudBoundary> },
  'monthly-hours-grid': { id: 'monthly-hours-grid', title: (window.appT||(s=>s))('Hours — week trend'),                  render: () => <_HudBoundary><_HudMonthlyHours /></_HudBoundary> },
  'presence-now':       { id: 'presence-now',       title: (window.appT||(s=>s))('Live K9 presence'),                    render: () => <_HudBoundary><_HudPresence /></_HudBoundary> },
  'signin-histogram':   { id: 'signin-histogram',   title: (window.appT||(s=>s))('Sign-in time distribution'),           render: () => <_HudBoundary><_HudSigninHistogram /></_HudBoundary> },
  'audit-recent':       { id: 'audit-recent',       title: (window.appT||(s=>s))('Recent audit events'),                 render: () => <_HudBoundary><_HudAuditRecent /></_HudBoundary> },
  'pto-calendar':       { id: 'pto-calendar',       title: (window.appT||(s=>s))('PTO calendar — this month'),           render: () => <_HudBoundary><_HudPtoCalendar /></_HudBoundary> },
  'schema-browser':     { id: 'schema-browser',     title: (window.appT||(s=>s))('DB Schema Browser'),                   render: () => <_HudBoundary><_HudSchemaBrowser /></_HudBoundary> },
  'code-search':        { id: 'code-search',        title: (window.appT||(s=>s))('Codebase Search'),                     render: () => <_HudBoundary><_HudCodeSearch /></_HudBoundary> },
};

const DEFAULT_WIDGETS = [HUD_WIDGETS['today-roster'], HUD_WIDGETS['hours-bar'], HUD_WIDGETS['attendance-pie']];

/* ---------- main HUD component ---------- */

// Phase 6 — dedupe set persists for the full page session (module scope).
const _hudSeenEventIds = new Set();

function HUD() {
  const { open, widgets, title, openHUD, closeHUD } = useHUD();
  const [proactiveToast, setProactiveToast] = _hudUseState(null); // {phrase, id}
  const [mutedAutoOpen, setMutedAutoOpen] = _hudUseState(
    () => localStorage.getItem('hud_proactive_off') === '1'
  );

  // Phase 6 — SSE connection for proactive HUD events.
  _hudUseEffect(() => {
    let es;
    try {
      es = new EventSource('/api/hud/stream', { withCredentials: true });
    } catch (_) { return; }
    es.addEventListener('hud_proactive', (e) => {
      try {
        const ev = JSON.parse(e.data);
        const evId = ev.id || (ev.kind + ':' + ev.created_at);
        // Dedupe: skip already-seen event ids.
        if (_hudSeenEventIds.has(evId)) return;
        _hudSeenEventIds.add(evId);
        // Cap set size to avoid unbounded growth on long-lived tabs.
        if (_hudSeenEventIds.size > 200) {
          const first = _hudSeenEventIds.values().next().value;
          _hudSeenEventIds.delete(first);
        }
        const widgetList = (ev.widgets || []).map(id => HUD_WIDGETS[id]).filter(Boolean);
        const phrase = ev.phrase || (window.appT||(s=>s))('Proactive update');
        // Read mute preference at receipt time (not just mount time).
        const muted = localStorage.getItem('hud_proactive_off') === '1';
        if (window.__hudState__.open) {
          // HUD already open: surface non-blocking toast.
          setProactiveToast({ phrase, id: evId });
          setTimeout(() => setProactiveToast(t => t && t.id === evId ? null : t), 8000);
        } else if (!muted && widgetList.length) {
          openHUD(widgetList, (window.appT||(s=>s))('Auto: ') + phrase.slice(0, 60));
        }
      } catch (_) { /* parse error — ignore */ }
    });
    return () => { try { es.close(); } catch (_) {} };
  }, [openHUD]);

  // Sync mute toggle to localStorage.
  const toggleMute = _hudUseCallback(() => {
    setMutedAutoOpen(prev => {
      const next = !prev;
      if (next) localStorage.setItem('hud_proactive_off', '1');
      else localStorage.removeItem('hud_proactive_off');
      return next;
    });
  }, []);

  // Global keybind: Cmd+H / Ctrl+H opens with defaults
  _hudUseEffect(() => {
    const onKey = (e) => {
      const isMod = e.metaKey || e.ctrlKey;
      // Primary, non-hijacked binding: Cmd/Ctrl+Shift+H. macOS reserves plain
      // Cmd+H for Hide-Window at the OS level, so Shift+H is the reliable one.
      // We keep plain Cmd+H below as a best-effort for browsers that forward it.
      if (isMod && e.shiftKey && (e.key === 'h' || e.key === 'H')) {
        e.preventDefault();
        if (!window.__hudState__.open) {
          openHUD(DEFAULT_WIDGETS, (window.appT||(s=>s))('Tactical Overview'));
        } else {
          closeHUD();
        }
        return;
      }
      if (isMod && !e.shiftKey && (e.key === 'h' || e.key === 'H')) {
        // Best-effort: Chrome forwards Cmd+H on some platforms, but macOS
        // usually swallows it as Hide-Window — that's why ⌘⇧H exists above.
        e.preventDefault();
        if (!window.__hudState__.open) {
          openHUD(DEFAULT_WIDGETS, (window.appT||(s=>s))('Tactical Overview'));
        } else {
          closeHUD();
        }
      }
      if (e.key === 'Escape' && window.__hudState__.open) {
        e.preventDefault();
        closeHUD();
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [openHUD, closeHUD]);

  // Phase 3 — deep-link `?hud=id1,id2&q=…` opens the HUD on page load with
  // the named widgets. Used by the bot's "Open in new tab" affordance and by
  // shareable HUD URLs. Runs once on mount.
  _hudUseEffect(() => {
    try {
      const params = new URLSearchParams(window.location.search);
      const ids = (params.get('hud') || '').split(',').map(s => s.trim()).filter(Boolean);
      if (!ids.length) return;
      const list = ids.map(i => HUD_WIDGETS[i]).filter(Boolean);
      if (!list.length) return;
      const q = params.get('q') || '';
      openHUD(list, q ? `From: "${q.slice(0, 60)}"` : (window.appT||(s=>s))('Deep-linked'));
      // Strip the params from the URL so a refresh doesn't keep re-opening.
      const url = new URL(window.location.href);
      url.searchParams.delete('hud');
      url.searchParams.delete('q');
      window.history.replaceState({}, '', url.toString());
    } catch (_) { /* no-op */ }
  }, [openHUD]);

  // Phase 6: proactive toast rendered even when HUD is closed
  // (so we can show it as a floating notification without opening the overlay).
  const toastEl = proactiveToast ? (
    <div style={{
      position: 'fixed', bottom: 24, right: 24, zIndex: 10000,
      background: 'rgba(8,12,18,0.95)', border: '1px solid var(--brand, #4af)',
      borderRadius: 6, padding: '10px 14px', maxWidth: 340,
      fontFamily: 'var(--font-mono, ui-monospace, Menlo, monospace)',
      fontSize: 12, color: '#e8eef6',
      boxShadow: '0 0 18px -4px var(--brand, #4af)',
      display: 'flex', alignItems: 'flex-start', gap: 10,
    }}>
      <span style={{ flex: 1 }}>
        <span style={{ color: 'var(--brand, #4af)', fontWeight: 700 }}>{(window.appT||(s=>s))("Proactive")}: </span>
        {proactiveToast.phrase}
      </span>
      <button onClick={() => setProactiveToast(null)} style={{
        background: 'transparent', border: 'none', color: '#9aa6b6',
        cursor: 'pointer', fontSize: 16, lineHeight: 1, padding: 0,
      }}>x</button>
    </div>
  ) : null;

  if (!open) return toastEl;
  // Single declaration only — a prior edit accidentally duplicated this
  // pair, which crashed babel-standalone with "Identifier 'list' has
  // already been declared" and left the whole React dashboard blank.
  const list = (widgets && widgets.length) ? widgets : DEFAULT_WIDGETS;
  const subtitle = title || (window.appT||(s=>s))('Tactical Overview');

  const overlay = {
    position: 'fixed', inset: 0, zIndex: 9999,
    background: 'rgba(0,0,0,0.92)',
    backdropFilter: 'blur(8px)',
    WebkitBackdropFilter: 'blur(8px)',
    color: '#e8eef6',
    display: 'flex', flexDirection: 'column',
    fontFamily: 'inherit',
  };
  const header = {
    display: 'flex', alignItems: 'center', justifyContent: 'space-between',
    padding: '14px 22px',
    borderBottom: '1px solid var(--brand, #4af)',
    boxShadow: '0 0 24px -6px var(--brand, #4af)',
    background: 'linear-gradient(180deg, rgba(0,0,0,0.6), rgba(0,0,0,0.2))',
  };
  const titleStyle = {
    display: 'flex', alignItems: 'baseline', gap: 14,
  };
  const titleMain = {
    fontFamily: 'var(--font-mono, ui-monospace, Menlo, monospace)',
    fontWeight: 700, letterSpacing: '0.18em', fontSize: 16,
    color: 'var(--brand, #4af)', textShadow: '0 0 12px var(--brand, #4af)',
  };
  const titleSub = {
    fontFamily: 'var(--font-mono, ui-monospace, Menlo, monospace)',
    fontSize: 11, letterSpacing: '0.14em', textTransform: 'uppercase',
    color: '#9aa6b6',
  };
  const closeBtn = {
    background: 'transparent', border: '1px solid var(--brand, #4af)',
    color: 'var(--brand, #4af)', borderRadius: 4,
    width: 32, height: 32, cursor: 'pointer', fontSize: 18, lineHeight: '28px',
    display:'flex', alignItems:'center', justifyContent:'center',
  };
  const tabBtn = { ...closeBtn, width: 'auto', padding: '0 10px', fontSize: 11,
                   fontFamily: 'var(--font-mono, ui-monospace, Menlo, monospace)',
                   letterSpacing: '0.12em', textTransform: 'uppercase' };
  // Build a deep-link to re-open this HUD in a new tab (preserves widget set
  // + question subtitle so the bot's voice flow can pop the HUD elsewhere).
  const openNewTab = () => {
    const ids = list.map(w => w.id).join(',');
    const q = (title && title.startsWith('From: '))
      ? title.replace(/^From: \"?/, '').replace(/\"?$/, '')
      : '';
    const url = `${window.location.origin}/?hud=${encodeURIComponent(ids)}` +
                (q ? `&q=${encodeURIComponent(q)}` : '');
    window.open(url, '_blank', 'noopener');
  };
  const grid = {
    flex: 1, overflowY: 'auto',
    display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(380px, 1fr))',
    gap: 16, padding: 22,
  };
  const card = {
    background: 'rgba(8,12,18,0.85)',
    border: '1px solid rgba(74,170,255,0.35)',
    borderRadius: 6,
    padding: 14,
    boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.02), 0 0 18px -8px var(--brand, #4af)',
    display: 'flex', flexDirection: 'column', gap: 10,
    minHeight: 160,
  };
  const cardTitle = {
    fontFamily: 'var(--font-mono, ui-monospace, Menlo, monospace)',
    fontSize: 11, letterSpacing: '0.14em', textTransform: 'uppercase',
    color: 'var(--brand, #4af)',
    borderBottom: '1px dashed rgba(74,170,255,0.3)',
    paddingBottom: 6,
  };

  return (
    <React.Fragment>
      <div style={overlay} onClick={(e) => { if (e.target === e.currentTarget) closeHUD(); }}>
        <div style={header}>
          <div style={titleStyle}>
            <div style={titleMain}>{(window.appT||(s=>s))("HUD")} ⛶</div>
            <div style={titleSub}>{subtitle}</div>
          </div>
          <div style={{ display:'flex', alignItems:'center', gap: 8 }}>
            <button style={tabBtn} onClick={openNewTab} title={(window.appT||(s=>s))("Open this HUD in a new browser tab")}>
              ↗ {(window.appT||(s=>s))("New Tab")}
            </button>
            <button
              style={{ ...tabBtn, opacity: mutedAutoOpen ? 0.5 : 1 }}
              onClick={toggleMute}
              title={mutedAutoOpen ? (window.appT||(s=>s))('Auto-open is muted — click to unmute') : (window.appT||(s=>s))('Mute auto-open (proactive HUD)')}
            >
              {mutedAutoOpen ? '🔇 ' + (window.appT||(s=>s))('Unmute') : '🔔 ' + (window.appT||(s=>s))('Mute')}
            </button>
            <button style={closeBtn} onClick={closeHUD} title={(window.appT||(s=>s))("Close (Esc)")}>×</button>
          </div>
        </div>
        <div style={grid}>
          {list.map(w => (
            <div key={w.id} style={card}>
              <div style={cardTitle}>{w.title}</div>
              <div style={{ flex: 1 }}>{w.render()}</div>
            </div>
          ))}
        </div>
      </div>
      {toastEl}
    </React.Fragment>
  );
}

window.HUD = HUD;
window.useHUD = useHUD;
window.HUD_WIDGETS = HUD_WIDGETS;
window.HUD_DEFAULT_WIDGETS = DEFAULT_WIDGETS;
