/* Mise — Alerts triage screen */

const Ic = ({ name, size = 16 }) => {
  const s = { width: size, height: size, display: 'block' };
  const p = { fill: 'none', stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round' };
  switch (name) {
    case 'dashboard': return <svg viewBox="0 0 16 16" style={s}><g {...p}><rect x="2" y="2" width="5" height="5" rx="1"/><rect x="9" y="2" width="5" height="5" rx="1"/><rect x="2" y="9" width="5" height="5" rx="1"/><rect x="9" y="9" width="5" height="5" rx="1"/></g></svg>;
    case 'stores':    return <svg viewBox="0 0 16 16" style={s}><g {...p}><path d="M2 6h12l-1-3H3z"/><path d="M3 6v8h10V6"/><path d="M6 14V9h4v5"/></g></svg>;
    case 'alerts':    return <svg viewBox="0 0 16 16" style={s}><g {...p}><path d="M3 12V7a5 5 0 0 1 10 0v5l1 2H2z"/><path d="M6 14a2 2 0 0 0 4 0"/></g></svg>;
    case 'ask':       return <svg viewBox="0 0 16 16" style={s}><g {...p}><path d="M2 4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H8l-3 2v-2H4a2 2 0 0 1-2-2z"/></g></svg>;
    case 'docs':      return <svg viewBox="0 0 16 16" style={s}><g {...p}><path d="M3 2h7l3 3v9H3z"/><path d="M10 2v3h3"/><path d="M5 8h6M5 11h4"/></g></svg>;
    case 'reports':   return <svg viewBox="0 0 16 16" style={s}><g {...p}><path d="M2 14h12"/><rect x="4" y="6" width="2" height="6"/><rect x="7" y="3" width="2" height="9"/><rect x="10" y="8" width="2" height="4"/></g></svg>;
    case 'settings':  return <svg viewBox="0 0 16 16" style={s}><g {...p}><circle cx="8" cy="8" r="2"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2M3 3l1.5 1.5M11.5 11.5L13 13M3 13l1.5-1.5M11.5 4.5L13 3"/></g></svg>;
    case 'chev':      return <svg viewBox="0 0 16 16" style={s}><path d="M5 6l3 3 3-3" {...p}/></svg>;
    case 'chevR':     return <svg viewBox="0 0 16 16" style={s}><path d="M6 4l3 4-3 4" {...p}/></svg>;
    case 'plus':      return <svg viewBox="0 0 16 16" style={s}><path d="M8 3v10M3 8h10" {...p}/></svg>;
    case 'search':    return <svg viewBox="0 0 16 16" style={s}><g {...p}><circle cx="7" cy="7" r="4.5"/><path d="M10.5 10.5L14 14"/></g></svg>;
    case 'bell':      return <svg viewBox="0 0 16 16" style={s}><g {...p}><path d="M3 12V7a5 5 0 0 1 10 0v5l1 2H2z"/><path d="M6 14a2 2 0 0 0 4 0"/></g></svg>;
    case 'bellSlash': return <svg viewBox="0 0 16 16" style={s}><g {...p}><path d="M3 12V7a5 5 0 0 1 8.5-3.6"/><path d="M13 7v5l1 2H4"/><path d="M6 14a2 2 0 0 0 4 0"/><path d="M2 2l12 12"/></g></svg>;
    case 'dots':      return <svg viewBox="0 0 16 16" style={s}><g fill="currentColor"><circle cx="3" cy="8" r="1.3"/><circle cx="8" cy="8" r="1.3"/><circle cx="13" cy="8" r="1.3"/></g></svg>;
    case 'check':     return <svg viewBox="0 0 16 16" style={s}><path d="M3 8l3 3 7-7" {...p}/></svg>;
    case 'x':         return <svg viewBox="0 0 16 16" style={s}><path d="M4 4l8 8M12 4l-8 8" {...p}/></svg>;
    case 'down':      return <svg viewBox="0 0 16 16" style={s}><path d="M8 3v9M5 9l3 3 3-3" {...p}/></svg>;
    case 'list':      return <svg viewBox="0 0 16 16" style={s}><g {...p}><path d="M2 4h12M2 8h12M2 12h12"/></g></svg>;
    case 'group':     return <svg viewBox="0 0 16 16" style={s}><g {...p}><rect x="2" y="3" width="12" height="3" rx="1"/><rect x="2" y="10" width="12" height="3" rx="1"/></g></svg>;
    case 'eye':       return <svg viewBox="0 0 16 16" style={s}><g {...p}><path d="M1.5 8s2.5-4.5 6.5-4.5S14.5 8 14.5 8 12 12.5 8 12.5 1.5 8 1.5 8z"/><circle cx="8" cy="8" r="2"/></g></svg>;
    case 'fire':      return <svg viewBox="0 0 16 16" style={s}><path d="M8 14a4 4 0 0 1-4-4c0-2 1-3 2-4 0 1 1 1.5 1.5 1 .5-.5 0-2 1-3.5.5 2 4 3 4 6.5a4 4 0 0 1-4.5 4z" {...p}/></svg>;
    case 'send':      return <svg viewBox="0 0 16 16" style={s}><g {...p}><path d="M14 2L7 9"/><path d="M14 2l-4.5 12-2.5-5-5-2.5z"/></g></svg>;
    default: return null;
  }
};

const ORGS = window.MISE.orgs;

function Sidebar() {
  const UserTile = window.MISE.SidebarUserTile;
  const [openAlertsCount, setOpenAlertsCount] = React.useState(null);
  React.useEffect(() => {
    let alive = true;
    const refresh = () => window.MISE.fetchAlertsCached().then(d => {
      if (alive) setOpenAlertsCount(d.filter(a => a.status === "open").length);
    });
    refresh();
    const unsub = window.MISE.subscribeAlerts(refresh);
    return () => { alive = false; unsub && unsub(); };
  }, []);
  const [open, setOpen] = React.useState(false);
  const [orgId, setOrgId] = React.useState('acme-wingstop');
  const org = ORGS.find(o => o.id === orgId) || ORGS[0];
  const items = [
    { id: 'dash',    icon: 'dashboard', label: 'Dashboard',  href: '/dashboard' },
    { id: 'stores',  icon: 'stores',    label: 'Stores',     href: '/stores' },
    { id: 'alerts',  icon: 'alerts',    label: 'Alerts',     active: true, badge: openAlertsCount },
    { id: 'ask',     icon: 'ask',       label: 'Ask Mise',   href: '/ask' },
    { id: 'docs',    icon: 'docs',      label: 'Documents',  href: '/documents' },
    { id: 'reports', icon: 'reports',   label: 'Reports',    href: '/reports' },
    { id: 'settings',icon: 'settings',  label: 'Settings',   href: '/settings' },
  ];
  return (
    <aside className="sidebar" data-screen-label="Sidebar">
      <a href="/" className="sb-brand">
        <span className="glyph">
          <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
            <path d="M3 11 V3 L6 8 L8 3 V11" fill="none" stroke="#0F2A3F" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/>
            <circle cx="11" cy="9" r="1.2" fill="#C8553D"/>
          </svg>
        </span>
        <span className="word">Mise</span>
      </a>
      <div className="org-switch" style={{ cursor: 'default' }}>
        <div style={{ minWidth: 0 }}>
          <div className="label">Organization</div>
          <div className="name">{org.name}</div>
        </div>
      </div>
      <nav className="nav-list">
        {items.slice(0, 4).map(it => (
          <a key={it.id} href={it.href || '#'} className={`nav-item ${it.active ? 'active' : ''}`}>
            <Ic name={it.icon}/>
            <span>{it.label}</span>
            {it.id === 'alerts' && <span className="al-nav-dot" aria-hidden/>}
            {it.badge && <span className="badge">{it.badge}</span>}
          </a>
        ))}
        <div className="nav-section">Workspace</div>
        {items.slice(4).map(it => (
          <a key={it.id} href={it.href || '#'} className={`nav-item ${it.active ? 'active' : ''}`}>
            <Ic name={it.icon}/>
            <span>{it.label}</span>
          </a>
        ))}
      </nav>
      <UserTile/>
    </aside>
  );
}

function Topbar({ selectedCount, onBulk }) {
  const [open, setOpen] = React.useState(false);
  const close = () => setOpen(false);
  const apply = (status) => { close(); onBulk(status); };
  return (
    <header className="topbar" data-screen-label="Top bar">
      <div>
        <span className="crumb serif">Alerts</span>
      </div>
      <div className="search">
        <span className="sicon"><Ic name="search" size={14}/></span>
        <input type="text" placeholder="Search alerts, stores, citations…" />
        <span className="kbd">⌘K</span>
      </div>
      <div className="top-right" style={{ gap: 10 }}>
        <div className="dd-wrap" style={{ display: 'inline-block' }}>
          {open && <div className="dd-backdrop" onClick={close}/>}
          <button
            className="d-btn d-btn-out"
            disabled={selectedCount === 0}
            onClick={() => setOpen(o => !o)}
          >
            Bulk actions {selectedCount > 0 && <span className="mono" style={{fontSize:10.5,color:'var(--accent)'}}>· {selectedCount}</span>}
            <Ic name="chev" size={12}/>
          </button>
          {open && selectedCount > 0 && (
            <div className="dd-menu right">
              <div className="dd-head">Apply to {selectedCount} selected</div>
              <div className="dd-item" onClick={() => apply('acked')}>
                <div className="dd-text"><div className="dd-title">Acknowledge</div></div>
              </div>
              <div className="dd-item" onClick={() => apply('snoozed')}>
                <div className="dd-text"><div className="dd-title">Snooze 4h</div></div>
              </div>
              <div className="dd-item" onClick={() => apply('open')}>
                <div className="dd-text"><div className="dd-title">Reopen</div></div>
              </div>
              <div className="dd-item" onClick={() => apply('resolved')}>
                <div className="dd-text"><div className="dd-title">Mark resolved</div></div>
              </div>
            </div>
          )}
        </div>
        <button className="icon-btn" aria-label="Mute alerts" title="Mute alerts">
          <Ic name="bellSlash" size={16}/>
        </button>
      </div>
    </header>
  );
}

/* ——— Summary strip ——— */
function SummaryStrip({ alerts, resolvedThisSession }) {
  const open      = alerts.filter(a => a.status === 'open');
  const newRecent = alerts.filter(a => ageHours(a.age) <= 24);
  const acked     = alerts.filter(a => a.status === 'acked');
  const totalStores    = (window.MISE && window.MISE.stores) ? window.MISE.stores.length : 0;
  const storesWithOpen = new Set(open.map(a => a.num)).size;
  const worstSev = open.some(a => a.sev === 'red')   ? 'red'
                 : open.some(a => a.sev === 'amber') ? 'amber'
                 : 'green';
  return (
    <section className="sum-strip" data-screen-label="Summary">
      <div className="sum-card">
        <div className="sum-lbl">Active alerts</div>
        <div className="sum-val-row">
          <div className="sum-val">{open.length}</div>
          <span className={`al-dot ${worstSev}`}/>
        </div>
      </div>
      <div className="sum-card">
        <div className="sum-lbl">New since yesterday</div>
        <div className="sum-val-row">
          <div className="sum-val">{newRecent.length}</div>
          {newRecent.length > 0 && <span className="sum-pip amber">+{newRecent.length}</span>}
        </div>
      </div>
      <div className="sum-card">
        <div className="sum-lbl">Acknowledged</div>
        <div className="sum-val-row">
          <div className="sum-val">{acked.length}</div>
          <span className="sum-pip neutral">session</span>
        </div>
      </div>
      <div className="sum-card">
        <div className="sum-lbl">Resolved this session</div>
        <div className="sum-val-row">
          <div className="sum-val">{resolvedThisSession}</div>
          <span className="sum-pip neutral">{resolvedThisSession > 0 ? '✓' : '—'}</span>
        </div>
      </div>
      <div className="sum-card">
        <div className="sum-lbl">Stores w/ open alerts</div>
        <div className="sum-val-row">
          <div className="sum-val">{storesWithOpen}<span className="sub"> / {totalStores}</span></div>
        </div>
        <div className="sum-bar"><i style={{ width: `${totalStores ? (storesWithOpen/totalStores)*100 : 0}%` }}/></div>
      </div>
    </section>
  );
}

/* ——— Filter / view bar ——— */
const TABS = [
  { id: 'all',  label: 'All',         count: 8 },
  { id: 'crit', label: 'Critical',    count: 4 },
  { id: 'food', label: 'Food Safety', count: 3 },
  { id: 'lab',  label: 'Labor',       count: 3 },
  { id: 'aud',  label: 'Audits',      count: 2 },
  { id: 'sales',label: 'Sales',       count: 1 },
];

function FilterPill({ label, value, options, onChange }) {
  const [open, setOpen] = React.useState(false);
  const current = options.find(o => o.id === value) || options[0];
  return (
    <div className="dd-wrap" style={{ display: 'inline-block' }}>
      {open && <div className="dd-backdrop" onClick={() => setOpen(false)}/>}
      <button className="fpill" onClick={() => setOpen(o => !o)}>
        <span className="fpk">{label}:</span>
        <span className="fpv">{current.title}</span>
        <Ic name="chev" size={11}/>
      </button>
      {open && (
        <div className="dd-menu left">
          <div className="dd-head">{label}</div>
          {options.map(o => (
            <div key={o.id} className={`dd-item ${o.id === value ? 'active' : ''}`} onClick={() => { onChange(o.id); setOpen(false); }}>
              <div className="dd-text"><div className="dd-title">{o.title}</div></div>
              {o.id === value && <span className="dd-check">✓</span>}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function FilterBar({ alerts, tab, setTab, view, setView, filters, setFilters, search, setSearch, tabCounts, onRefresh, refreshing, scanStatus }) {
  const set = (k) => (v) => setFilters(f => ({ ...f, [k]: v }));
  const storeOptions = [
    { id: 'any', title: 'Any store' },
    ...Array.from(new Set(alerts.map(a => a.num))).map(num => {
      const s = alerts.find(a => a.num === num);
      return { id: num, title: `${num} ${s.store}` };
    }),
  ];
  return (
    <div className="filter-bar-wrap" data-screen-label="Filters">
      <div className="filter-tabs-row">
        <div className="filter-tabs">
          {TABS.map(t => (
            <button key={t.id} className={`ft ${t.id === tab ? 'active' : ''}`} onClick={() => setTab(t.id)}>
              {t.label}
              <span className="ft-c">{tabCounts[t.id] ?? 0}</span>
            </button>
          ))}
        </div>
        <div className="view-toggle">
          <button className={view === 'list' ? 'active' : ''} onClick={() => setView('list')}><Ic name="list" size={12}/> List</button>
          <button className={view === 'group' ? 'active' : ''} onClick={() => setView('group')}><Ic name="group" size={12}/> Grouped by store</button>
        </div>
      </div>
      <div className="filter-pills-row">
        <div className="filter-pills">
          <FilterPill label="Severity" value={filters.severity} onChange={set('severity')} options={[
            { id: 'any', title: 'any' },
            { id: 'red', title: 'Critical' },
            { id: 'amber', title: 'Warning' },
            { id: 'green', title: 'Info' },
          ]}/>
          <FilterPill label="Store" value={filters.store} onChange={set('store')} options={storeOptions}/>
          <FilterPill label="Status" value={filters.status} onChange={set('status')} options={[
            { id: 'any', title: 'any' },
            { id: 'open', title: 'open' },
            { id: 'acked', title: 'acknowledged' },
            { id: 'snoozed', title: 'snoozed' },
          ]}/>
          <FilterPill label="Time" value={filters.time} onChange={set('time')} options={[
            { id: 'any', title: 'all time' },
            { id: '1h', title: 'last hour' },
            { id: '24h', title: 'last 24 hours' },
            { id: '7d', title: 'last 7 days' },
          ]}/>
        </div>
        <div className="al-search">
          <span className="sicon"><Ic name="search" size={12}/></span>
          <input placeholder="Search alerts…" value={search} onChange={(e) => setSearch(e.target.value)}/>
        </div>
        <AuditFreshnessPill status={scanStatus}/>
        <button className="ack-btn" onClick={onRefresh} disabled={refreshing} style={{ marginLeft: 8 }}>
          {refreshing ? 'Re-scanning…' : 'Re-scan'}
        </button>
      </div>
    </div>
  );
}

function ageHours(s) {
  const m = String(s || '').match(/(\d+)\s*(m|h|d)/);
  if (!m) return 0;
  const n = Number(m[1]);
  if (m[2] === 'm') return n / 60;
  if (m[2] === 'h') return n;
  if (m[2] === 'd') return n * 24;
  return 0;
}

function applyAlertFilters(rows, { tab, filters, search }) {
  const q = search.trim().toLowerCase();
  const tabMap = { crit: 'red', food: 'Food Safety', lab: 'Labor', aud: 'Audit', sales: 'Sales' };
  return rows.filter(a => {
    if (tab === 'crit' && a.sev !== 'red') return false;
    if (tab === 'food' && a.type !== 'Food Safety') return false;
    if (tab === 'lab'  && a.type !== 'Labor') return false;
    if (tab === 'aud'  && a.type !== 'Audit') return false;
    if (tab === 'sales'&& a.type !== 'Sales') return false;
    if (filters.severity !== 'any' && a.sev !== filters.severity) return false;
    if (filters.store !== 'any' && a.num !== filters.store) return false;
    if (filters.status !== 'any' && a.status !== filters.status) return false;
    if (filters.time !== 'any') {
      const h = ageHours(a.age);
      const cap = filters.time === '1h' ? 1 : filters.time === '24h' ? 24 : 24 * 7;
      if (h > cap) return false;
    }
    if (q && !(a.title.toLowerCase().includes(q) || a.store.toLowerCase().includes(q) || a.type.toLowerCase().includes(q))) return false;
    return true;
  });
}

/* ——— Alerts list (live, AI-generated from POST /alerts/scan) ——— */
const API_BASE = (typeof window !== 'undefined' && window.MISE_API_BASE) || 'http://localhost:8080';

/** Compact "Audited Xm ago" pill for the FilterBar. Shares semantics
 *  with the equivalent component on the Stores page so freshness
 *  reads the same everywhere — green within ~36h, amber for staler,
 *  red on the last scan having errored. */
function AuditFreshnessPill({ status }) {
  const last = status?.lastScannedAt ? new Date(status.lastScannedAt) : null;
  const label = last ? `Audited ${formatRelativeAlerts(last)}` : 'Audit not yet run';
  const tone =
    status?.lastStatus === 'error' ? 'err'
    : !last ? 'muted'
    : (Date.now() - last.getTime()) > 36 * 3600 * 1000 ? 'stale'
    : 'ok';
  return (
    <span title={status?.lastError || (last ? last.toLocaleString() : 'No scan has run yet')} style={{
      display: 'inline-flex', alignItems: 'center', gap: 6,
      fontSize: 11.5,
      color: tone === 'err' ? 'var(--red)' : tone === 'stale' ? 'var(--amber)' : 'var(--muted)',
      padding: '4px 10px', borderRadius: 999,
      background: 'var(--bg-2)', border: '1px solid var(--hair)',
      marginLeft: 8,
    }}>
      <span style={{
        width: 6, height: 6, borderRadius: '50%',
        background: tone === 'err' ? 'var(--red)'
          : tone === 'stale' ? 'var(--amber)'
          : tone === 'ok' ? 'var(--green)' : 'var(--muted-2)',
      }}/>
      {label}
    </span>
  );
}

function formatRelativeAlerts(date) {
  const ms = Date.now() - date.getTime();
  if (ms < 60_000) return 'just now';
  const m = Math.floor(ms / 60_000);
  if (m < 60) return `${m}m ago`;
  const h = Math.floor(m / 60);
  if (h < 48) return `${h}h ago`;
  const d = Math.floor(h / 24);
  return `${d}d ago`;
}

async function fetchAlerts(refresh) {
  const res = await fetch(`${API_BASE}/alerts/scan${refresh ? '?refresh=true' : ''}`, {
    method: 'POST',
    cache: 'no-store',
  });
  if (!res.ok) {
    const text = await res.text();
    throw new Error(`${res.status}: ${text || res.statusText}`);
  }
  return res.json();
}

async function setAlertStatusBackend(id, status) {
  const res = await fetch(`${API_BASE}/alerts/${encodeURIComponent(id)}/status`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ status }),
  });
  if (!res.ok) {
    throw new Error(`${res.status}: ${await res.text() || res.statusText}`);
  }
}

function draftSlackMessage(alert) {
  const emoji = alert.sev === 'red' ? '🚨' : alert.sev === 'amber' ? '⚠️' : 'ℹ️';
  const cite = (alert.citations && alert.citations[0]);
  const citeLine = cite
    ? `\n_Source: ${cite.documentTitle}${cite.sectionPath ? ` — ${cite.sectionPath}` : ''}_`
    : '';
  return `${emoji} *${alert.sevLbl} · ${alert.num} ${alert.store}*\n` +
    `${alert.title}\n\n` +
    (alert.explanation ? `${alert.explanation}\n\n` : '') +
    (alert.recommendedAction ? `*Recommended:* ${alert.recommendedAction}` : '') +
    citeLine;
}

function SlackSendModal({ alert, onClose, onSent }) {
  const [status, setStatus] = React.useState(null);
  const [channels, setChannels] = React.useState([]);
  const [channelId, setChannelId] = React.useState('');
  const [text, setText] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    if (!alert) return;
    setText(draftSlackMessage(alert));
    setError(null);
    setBusy(true);
    fetch(`${API_BASE}/slack/workspace`, { cache: 'no-store' })
      .then(r => r.ok ? r.json() : null)
      .then(s => {
        setStatus(s);
        if (s?.connected) {
          setChannelId(s.defaultChannelId || '');
          fetch(`${API_BASE}/slack/channels`, { cache: 'no-store' })
            .then(r => r.ok ? r.json() : [])
            .then(setChannels)
            .catch(() => setChannels([]));
        }
      })
      .catch(() => setStatus(null))
      .finally(() => setBusy(false));
  }, [alert]);

  if (!alert) return null;

  const send = async () => {
    if (!channelId || !text.trim()) return;
    setBusy(true);
    setError(null);
    try {
      const res = await fetch(`${API_BASE}/slack/post`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ channel: channelId, text: text.trim() }),
      });
      if (!res.ok) throw new Error(`${res.status}: ${await res.text() || res.statusText}`);
      const channelName = channels.find(c => c.id === channelId)?.name || channelId;
      onSent && onSent(channelName);
      onClose();
    } catch (e) {
      setError(e.message || String(e));
    } finally { setBusy(false); }
  };

  const copy = async () => {
    try { await navigator.clipboard.writeText(text); onSent && onSent('clipboard'); onClose(); }
    catch (e) { setError('Clipboard blocked: ' + e.message); }
  };

  return (
    <div onClick={busy ? null : onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(11,27,38,.45)', display: 'flex',
      alignItems: 'center', justifyContent: 'center', zIndex: 1000,
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: 'var(--bg)', borderRadius: 12, padding: 24, width: 540, maxWidth: '92vw',
        maxHeight: '85vh', display: 'flex', flexDirection: 'column',
        boxShadow: '0 20px 60px rgba(11,27,38,.25)', border: '1px solid var(--hair)',
      }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 14 }}>
          <div>
            <div style={{ fontSize: 11, color: 'var(--muted)', fontFamily: "'JetBrains Mono', monospace", letterSpacing: '.05em', textTransform: 'uppercase' }}>Send to Slack</div>
            <div style={{ fontSize: 16, fontWeight: 600, marginTop: 2 }}>{alert.num} {alert.store}</div>
          </div>
          <button onClick={onClose} className="dt-actions" aria-label="Close"><Ic name="x" size={14}/></button>
        </div>

        {!status?.configured && (
          <div style={{ padding: 16, background: 'rgba(178,58,42,.06)', border: '1px solid rgba(178,58,42,.30)', borderRadius: 8, fontSize: 13, color: 'var(--ink)' }}>
            Slack credentials aren't configured on the server. An admin needs to add them before "Send to Slack" can work.
          </div>
        )}
        {status?.configured && !status.connected && (
          <div style={{ padding: 16, background: 'var(--bg-2)', border: '1px solid var(--hair)', borderRadius: 8, fontSize: 13, color: 'var(--ink)' }}>
            No Slack workspace connected for this org. <a href="/settings" style={{ color: 'var(--accent)', textDecoration: 'underline' }}>Connect one in Settings →</a>
          </div>
        )}

        {status?.connected && (
          <>
            <label style={{ display: 'block', fontSize: 11, color: 'var(--muted)', fontFamily: "'JetBrains Mono', monospace", letterSpacing: '.05em', textTransform: 'uppercase', marginBottom: 6 }}>Channel</label>
            <select
              value={channelId}
              onChange={(e) => setChannelId(e.target.value)}
              disabled={busy || channels.length === 0}
              style={{ width: '100%', padding: '9px 10px', borderRadius: 8, border: '1px solid var(--hair)', background: 'var(--bg)', fontFamily: 'inherit', fontSize: 13, marginBottom: 14 }}
            >
              <option value="" disabled>Pick a channel…</option>
              {channels.map(c => (
                <option key={c.id} value={c.id}>{c.isPrivate ? '🔒' : '#'} {c.name}</option>
              ))}
            </select>

            <label style={{ display: 'block', fontSize: 11, color: 'var(--muted)', fontFamily: "'JetBrains Mono', monospace", letterSpacing: '.05em', textTransform: 'uppercase', marginBottom: 6 }}>Message</label>
            <textarea
              value={text}
              onChange={(e) => setText(e.target.value)}
              disabled={busy}
              rows={9}
              style={{ width: '100%', padding: '10px 12px', borderRadius: 8, border: '1px solid var(--hair)', background: 'var(--bg)', fontFamily: "'JetBrains Mono', monospace", fontSize: 12.5, lineHeight: 1.5, resize: 'vertical', minHeight: 140 }}
            />
            <div style={{ fontSize: 11, color: 'var(--muted)', marginTop: 4 }}>Slack-flavored markdown: *bold*, _italic_, `code`.</div>
          </>
        )}

        {error && (
          <div style={{ marginTop: 12, padding: 10, background: 'rgba(178,58,42,.08)', border: '1px solid rgba(178,58,42,.30)', borderRadius: 8, fontSize: 12.5, color: 'var(--red)' }}>
            {error}
          </div>
        )}

        <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end', marginTop: 18 }}>
          <button onClick={copy} disabled={busy} className="d-btn d-btn-out">Copy</button>
          <button onClick={onClose} disabled={busy} className="d-btn d-btn-out">Cancel</button>
          <button
            onClick={send}
            disabled={busy || !status?.connected || !channelId}
            className="d-btn d-btn-pri"
          >{busy ? 'Sending…' : 'Send to Slack'}</button>
        </div>
      </div>
    </div>
  );
}

async function bulkSetAlertStatusBackend(ids, status) {
  const res = await fetch(`${API_BASE}/alerts/status/bulk`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ids: Array.from(ids), status }),
  });
  if (!res.ok) {
    throw new Error(`${res.status}: ${await res.text() || res.statusText}`);
  }
}

async function fetchScenarios() {
  const res = await fetch(`${API_BASE}/alerts/scenarios`, { cache: 'no-store' });
  if (!res.ok) throw new Error(`${res.status}: ${await res.text() || res.statusText}`);
  return res.json();
}

function RowMenu({ alert, onStatus, onViewScenario }) {
  const [open, setOpen] = React.useState(false);
  const close = () => setOpen(false);
  const pick = (fn) => (e) => { e.stopPropagation(); close(); fn(); };
  const copyId = async () => {
    try { await navigator.clipboard.writeText(alert.id); }
    catch { /* clipboard blocked, ignore */ }
  };
  return (
    <div className="dd-wrap" style={{ position: 'relative', justifySelf: 'end' }}>
      {open && <div className="dd-backdrop" onClick={(e) => { e.stopPropagation(); close(); }}/>}
      <button
        className="dt-actions"
        onClick={(e) => { e.stopPropagation(); setOpen(o => !o); }}
        aria-label="More"
      ><Ic name="dots" size={14}/></button>
      {open && (
        <div className="dd-menu right" style={{ minWidth: 200 }} onClick={(e) => e.stopPropagation()}>
          {/* Reopen / Snooze are mutually exclusive — show whichever
              moves the row to a new state from where it currently is. */}
          {alert.status === 'snoozed' ? (
            <div className="dd-item" onClick={pick(() => onStatus(alert.id, 'open'))}>
              <div className="dd-text"><div className="dd-title">Unsnooze</div></div>
            </div>
          ) : alert.status === 'acked' ? (
            <div className="dd-item" onClick={pick(() => onStatus(alert.id, 'open'))}>
              <div className="dd-text"><div className="dd-title">Reopen</div></div>
            </div>
          ) : (
            <div className="dd-item" onClick={pick(() => onStatus(alert.id, 'snoozed'))}>
              <div className="dd-text"><div className="dd-title">Snooze 4h</div></div>
            </div>
          )}
          <div className="dd-item" onClick={pick(() => onStatus(alert.id, 'resolved'))}>
            <div className="dd-text"><div className="dd-title">Mark resolved</div></div>
          </div>
          <div className="dd-item" onClick={pick(() => onViewScenario(alert.scenarioId))}>
            <div className="dd-text"><div className="dd-title">View original scenario</div></div>
          </div>
          <div className="dd-item" onClick={pick(copyId)}>
            <div className="dd-text"><div className="dd-title">Copy alert ID</div></div>
          </div>
        </div>
      )}
    </div>
  );
}

function ScenarioModal({ scenario, onClose }) {
  if (!scenario) return null;
  return (
    <div
      onClick={onClose}
      style={{
        position: 'fixed', inset: 0, background: 'rgba(11,27,38,.45)',
        display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000,
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          background: 'var(--bg)', borderRadius: 12, padding: 24, maxWidth: 720, width: '90vw',
          maxHeight: '80vh', overflow: 'auto', boxShadow: '0 20px 60px rgba(11,27,38,.25)',
          border: '1px solid var(--hair)',
        }}
      >
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 8 }}>
          <div>
            <div style={{ fontSize: 11, color: 'var(--muted)', fontFamily: "'JetBrains Mono', monospace", letterSpacing: '.05em', textTransform: 'uppercase' }}>Original scenario · fed to /analyze</div>
            <div style={{ fontSize: 16, fontWeight: 600, marginTop: 2 }}>
              <span className="num" style={{ marginRight: 8 }}>{scenario.storeNum}</span>
              {scenario.storeName} · {scenario.type}
            </div>
          </div>
          <button onClick={onClose} className="dt-actions" aria-label="Close"><Ic name="x" size={14}/></button>
        </div>
        <div style={{
          marginTop: 14, padding: 16, background: 'var(--bg-2)', borderRadius: 8,
          fontFamily: "'JetBrains Mono', ui-monospace, monospace", fontSize: 12.5,
          whiteSpace: 'pre-wrap', lineHeight: 1.55, color: 'var(--ink)',
        }}>{scenario.scenario}</div>
      </div>
    </div>
  );
}

function AlertRow({ a, selected, checked, onClick, onCheck, onAck, onStatus, onViewScenario }) {
  return (
    <div className={`al-row ${selected ? 'selected' : ''}`} onClick={onClick}>
      <div
        className={`al-check ${checked ? 'checked' : ''}`}
        onClick={(e) => { e.stopPropagation(); onCheck(); }}
        role="checkbox"
        aria-checked={checked}
      >
        {checked && <Ic name="check" size={10}/>}
      </div>
      <div className={`al-dot ${a.sev}`}/>
      <span className={`sev-chip ${a.sev}`}>{a.sevLbl}</span>
      <span className="type-chip">{a.type}</span>
      <span className="al-store"><span className="num">{a.num}</span>{a.store}</span>
      <span className="al-title">{a.title}</span>
      <span className="al-time">{a.age}</span>
      <span className="al-status-cell">
        <span className={`status-pill ${a.status}`}>{a.status === 'acked' ? 'Acknowledged' : a.status[0].toUpperCase() + a.status.slice(1)}</span>
        {a.status === 'open' && (
          <button
            className="ack-btn"
            onClick={(e) => { e.stopPropagation(); onAck(); }}
          >Ack</button>
        )}
      </span>
      <RowMenu alert={a} onStatus={onStatus} onViewScenario={onViewScenario}/>
    </div>
  );
}

function AlertsList({ allAlerts, rows, selectedId, setSelectedId, checked, setChecked, loading, error, onAck, onStatus, onViewScenario, onSetStatusFilter, currentStatusFilter }) {
  const totals = allAlerts.reduce((acc, a) => { acc[a.status] = (acc[a.status] || 0) + 1; return acc; }, {});
  if (loading && allAlerts.length === 0) {
    return (
      <div className="al-list">
        <div className="al-empty" style={{ padding: '24px 32px', textAlign: 'left' }}>
          <div style={{ fontSize: 13, color: 'var(--ink)', fontWeight: 500 }}>
            Running compliance auditor against mock scenarios…
          </div>
          <div style={{ fontSize: 12, color: 'var(--muted)', marginTop: 4 }}>
            First scan takes 30–60s. Subsequent loads are cached and instant.
          </div>
        </div>
        {[0,1,2,3].map(i => (
          <div key={i} className="al-row" style={{ pointerEvents: 'none' }}>
            <div className="al-check"/>
            <div className="al-dot" style={{ background: 'var(--hair)' }}/>
            <span className="sev-chip" style={{ background: 'var(--bg-2)', color: 'transparent', minWidth: 56 }}>⏳</span>
            <span className="type-chip" style={{ background: 'var(--bg-2)', color: 'transparent', minWidth: 60 }}>…</span>
            <span className="al-store" style={{ color: 'transparent' }}><span className="num" style={{ background: 'var(--bg-2)', color: 'transparent' }}>···</span></span>
            <span className="al-title" style={{ background: 'var(--bg-2)', borderRadius: 4, color: 'transparent', height: 14 }}>{'\u00A0'}</span>
            <span className="al-time" style={{ color: 'transparent' }}>—</span>
            <span className="al-status-cell">
              <span className="status-pill" style={{ background: 'var(--bg-2)', color: 'transparent', borderColor: 'var(--bg-2)' }}>—</span>
            </span>
            <span/>
          </div>
        ))}
      </div>
    );
  }
  if (error) {
    return (
      <div className="al-list">
        <div className="al-empty" style={{ padding: 32, textAlign: 'center', color: 'var(--red)' }}>
          Failed to load alerts: {error}
          <div style={{ marginTop: 6, fontSize: 11, color: 'var(--muted)' }}>
            Make sure the backend is running on localhost:8080.
          </div>
        </div>
      </div>
    );
  }
  return (
    <div className="al-list">
      {rows.length === 0 && (
        <div className="al-empty" style={{ padding: 32, textAlign: 'center' }}>No alerts match the current filters.</div>
      )}
      {rows.map(a => (
        <AlertRow
          key={a.id}
          a={a}
          selected={selectedId === a.id}
          checked={checked.has(a.id)}
          onClick={() => setSelectedId(a.id)}
          onCheck={() => {
            const next = new Set(checked);
            if (next.has(a.id)) next.delete(a.id); else next.add(a.id);
            setChecked(next);
          }}
          onAck={() => onAck(a.id, 'acked')}
          onStatus={onStatus}
          onViewScenario={onViewScenario}
        />
      ))}
      {rows.length > 0 && (
        <div className="al-empty">
          Showing {rows.length} of {allAlerts.length} alerts ·{' '}
          <FooterFilterLink count={totals.open || 0} label="open" filterId="open" current={currentStatusFilter} onClick={onSetStatusFilter}/> ·{' '}
          <FooterFilterLink count={totals.acked || 0} label="acknowledged" filterId="acked" current={currentStatusFilter} onClick={onSetStatusFilter}/> ·{' '}
          <FooterFilterLink count={totals.snoozed || 0} label="snoozed" filterId="snoozed" current={currentStatusFilter} onClick={onSetStatusFilter}/>
        </div>
      )}
    </div>
  );
}

function FooterFilterLink({ count, label, filterId, current, onClick }) {
  if (count === 0) {
    return <span style={{ color: 'var(--muted-2)' }}>{count} {label}</span>;
  }
  const active = current === filterId;
  return (
    <button
      onClick={() => onClick(filterId)}
      style={{
        background: 'none', border: 0, padding: 0, cursor: 'pointer',
        color: active ? 'var(--ink)' : 'var(--accent)',
        font: 'inherit', textDecoration: 'underline', textUnderlineOffset: 2,
        textDecorationColor: active ? 'var(--ink)' : 'rgba(200,85,61,.4)',
      }}
      title={active ? `Currently filtered to ${label}` : `Show only ${label} alerts`}
    >{count} {label}</button>
  );
}

const SEV_RANK = { red: 0, amber: 1, green: 2 };

function GroupedAlertsList({ allAlerts, rows, selectedId, setSelectedId, checked, setChecked, loading, error, onAck, onStatus, onViewScenario, onSetStatusFilter, currentStatusFilter }) {
  if (loading || error) {
    // Reuse the flat list's loading/error UI.
    return (
      <AlertsList
        allAlerts={allAlerts} rows={rows}
        selectedId={selectedId} setSelectedId={setSelectedId}
        checked={checked} setChecked={setChecked}
        loading={loading} error={error}
        onAck={onAck} onStatus={onStatus} onViewScenario={onViewScenario}
        onSetStatusFilter={onSetStatusFilter} currentStatusFilter={currentStatusFilter}
      />
    );
  }
  if (rows.length === 0) {
    return (
      <div className="al-list">
        <div className="al-empty" style={{ padding: 32, textAlign: 'center' }}>No alerts match the current filters.</div>
      </div>
    );
  }

  // Bucket rows by store, sorted by worst severity in the group.
  const byStore = new Map();
  for (const a of rows) {
    if (!byStore.has(a.num)) byStore.set(a.num, { num: a.num, store: a.store, alerts: [] });
    byStore.get(a.num).alerts.push(a);
  }
  const groups = Array.from(byStore.values())
    .map(g => ({
      ...g,
      worstSev: g.alerts.reduce((w, a) => (SEV_RANK[a.sev] ?? 9) < (SEV_RANK[w] ?? 9) ? a.sev : w, 'green'),
      open: g.alerts.filter(a => a.status === 'open').length,
    }))
    .sort((a, b) => (SEV_RANK[a.worstSev] - SEV_RANK[b.worstSev]) || (b.alerts.length - a.alerts.length));

  const totals = allAlerts.reduce((acc, a) => { acc[a.status] = (acc[a.status] || 0) + 1; return acc; }, {});

  return (
    <div className="al-list">
      {groups.map(g => (
        <div key={g.num} className="al-group">
          <div style={{
            display: 'flex', alignItems: 'center', gap: 10,
            padding: '14px 16px 10px', borderTop: '1px solid var(--hair-2)',
            background: 'var(--bg-2)', position: 'sticky', top: 0, zIndex: 1,
          }}>
            <span className={`al-dot ${g.worstSev}`} style={{ flex: 'none' }}/>
            <a
              className="num"
              href={`/stores/${g.num.replace('#', '')}`}
              onClick={(e) => e.stopPropagation()}
            >{g.num}</a>
            <span style={{ fontWeight: 500, fontSize: 13 }}>{g.store}</span>
            <span style={{ marginLeft: 'auto', fontSize: 11, color: 'var(--muted)', fontFamily: "'JetBrains Mono', monospace" }}>
              {g.alerts.length} {g.alerts.length === 1 ? 'alert' : 'alerts'}
              {g.open > 0 && ` · ${g.open} open`}
            </span>
          </div>
          {g.alerts.map(a => (
            <AlertRow
              key={a.id}
              a={a}
              selected={selectedId === a.id}
              checked={checked.has(a.id)}
              onClick={() => setSelectedId(a.id)}
              onCheck={() => {
                const next = new Set(checked);
                if (next.has(a.id)) next.delete(a.id); else next.add(a.id);
                setChecked(next);
              }}
              onAck={() => onAck(a.id, 'acked')}
              onStatus={onStatus}
              onViewScenario={onViewScenario}
            />
          ))}
        </div>
      ))}
      <div className="al-empty">
        {groups.length} {groups.length === 1 ? 'store' : 'stores'} · {rows.length} of {allAlerts.length} alerts ·{' '}
        <FooterFilterLink count={totals.open || 0} label="open" filterId="open" current={currentStatusFilter} onClick={onSetStatusFilter}/> ·{' '}
        <FooterFilterLink count={totals.acked || 0} label="acknowledged" filterId="acked" current={currentStatusFilter} onClick={onSetStatusFilter}/> ·{' '}
        <FooterFilterLink count={totals.snoozed || 0} label="snoozed" filterId="snoozed" current={currentStatusFilter} onClick={onSetStatusFilter}/>
      </div>
    </div>
  );
}

/* ——— Drawer: temp chart ——— */
function TempChart() {
  // 6 hours, hourly readings (°F) plus the recent spike
  const readings = [
    { t: '6pm',    v: 38.4 },
    { t: '7pm',    v: 38.8 },
    { t: '8pm',    v: 39.1 },
    { t: '9pm',    v: 39.6 },
    { t: '10pm',   v: 40.4 },
    { t: '11pm',   v: 41.6 },
    { t: '11:30',  v: 44.2 },
    { t: '11:45',  v: 44.8 },
    { t: '12am',   v: 43.9 },
    { t: '12:30',  v: 40.9 },
  ];
  const W = 400, H = 90, PAD_L = 28, PAD_R = 8, PAD_T = 8, PAD_B = 18;
  const innerW = W - PAD_L - PAD_R, innerH = H - PAD_T - PAD_B;
  const minV = 36, maxV = 48;
  const x = i => PAD_L + (i / (readings.length - 1)) * innerW;
  const y = v => PAD_T + (1 - (v - minV) / (maxV - minV)) * innerH;
  const thresholdY = y(41);
  const linePts = readings.map((r, i) => `${x(i)},${y(r.v)}`).join(' ');

  // build the "out of range" shaded area: vertices follow the line where v > 41, rest sits on threshold
  const aboveSegments = [];
  let seg = null;
  readings.forEach((r, i) => {
    if (r.v >= 41) {
      if (!seg) seg = [];
      seg.push(i);
    } else if (seg) {
      aboveSegments.push(seg);
      seg = null;
    }
  });
  if (seg) aboveSegments.push(seg);

  return (
    <div className="temp-chart">
      <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none" aria-label="Cooler temperature, last 6 hours">
        {/* y-axis labels */}
        <g fontFamily="JetBrains Mono, monospace" fontSize="9" fill="#8A98A4">
          <text x="2" y={y(48) + 3}>48°</text>
          <text x="2" y={y(41) + 3}>41°</text>
          <text x="2" y={y(36) + 3}>36°</text>
        </g>
        {/* threshold line */}
        <line x1={PAD_L} y1={thresholdY} x2={W - PAD_R} y2={thresholdY} stroke="#B07A19" strokeWidth="1" strokeDasharray="3 3"/>
        {/* shaded out-of-range under-line */}
        {aboveSegments.map((s, k) => {
          // expand segment by one neighbor on each side so the polygon hugs the crossing of the threshold
          const start = Math.max(0, s[0] - 1);
          const end = Math.min(readings.length - 1, s[s.length - 1] + 1);
          const pts = [];
          for (let i = start; i <= end; i++) {
            const yv = readings[i].v >= 41 ? y(readings[i].v) : thresholdY;
            pts.push(`${x(i)},${yv}`);
          }
          // close along threshold
          pts.push(`${x(end)},${thresholdY}`);
          pts.push(`${x(start)},${thresholdY}`);
          return <polygon key={k} points={pts.join(' ')} fill="#C8553D" opacity="0.18"/>;
        })}
        {/* line */}
        <polyline points={linePts} fill="none" stroke="#0F2A3F" strokeWidth="1.8" strokeLinejoin="round" strokeLinecap="round"/>
        {/* points */}
        {readings.map((r, i) => (
          <circle key={i} cx={x(i)} cy={y(r.v)} r={r.v >= 41 ? 2.6 : 1.8} fill={r.v >= 41 ? '#C8553D' : '#0F2A3F'}/>
        ))}
        {/* annotation */}
        <g>
          <line x1={x(6)} y1={y(44.2)} x2={x(6)} y2={PAD_T + 2} stroke="#C8553D" strokeWidth=".8" strokeDasharray="2 2"/>
          <text x={x(6) + 3} y={PAD_T + 8} fontFamily="JetBrains Mono, monospace" fontSize="9" fill="#B14228" fontWeight="600">44.2°F</text>
        </g>
      </svg>
      <div className="temp-chart-foot">
        <span>6pm</span>
        <span>9pm</span>
        <span>12am</span>
      </div>
      <div className="temp-chart-leg">
        <span><span className="sw" style={{ background: '#0F2A3F' }}/>Reading</span>
        <span><span className="sw" style={{ borderTop: '1px dashed #B07A19', height: 0, marginTop: 4 }}/>Threshold 41°F</span>
        <span><span className="sw" style={{ background: 'rgba(200,85,61,.4)' }}/>Out of range</span>
      </div>
    </div>
  );
}

/** Avatar palette: stable color per user id so chips don't flicker
 *  between renders. Hashes the id into the palette index. */
const ASSIGN_PALETTE = ['#163A55', '#2F7D5B', '#8C5F0F', '#7A3E5C', '#3D6E8C', '#B07A19', '#5A6B78', '#C8553D'];
function avatarColor(userId) {
  if (!userId) return '#5A6B78';
  let h = 0;
  for (let i = 0; i < userId.length; i++) h = (h * 31 + userId.charCodeAt(i)) >>> 0;
  return ASSIGN_PALETTE[h % ASSIGN_PALETTE.length];
}
function avatarInitials(name) {
  if (!name) return '—';
  return name.split(/\s+/).slice(0, 2).map(p => p[0] || '').join('').toUpperCase();
}

/** "Assign to…" block in the alert drawer. Replaces the hardcoded
 *  RT/JL avatars with real users pulled from /users, persists each
 *  add/remove via /alerts/{id}/assign, and lets the parent sync local
 *  state via onChanged so the chip pile updates without a re-scan. */
function AssignSection({ alert, onChanged }) {
  const [users, setUsers] = React.useState([]);
  const [pickerOpen, setPickerOpen] = React.useState(false);
  const [busy, setBusy] = React.useState(false);

  React.useEffect(() => {
    if (window.MISE && window.MISE.fetchUsersCached) {
      window.MISE.fetchUsersCached().then(setUsers);
      return window.MISE.subscribeUsers
        ? window.MISE.subscribeUsers(() => window.MISE.fetchUsersCached().then(setUsers))
        : undefined;
    }
  }, []);

  const assignedIds = alert.assignedUserIds || [];
  const userById = React.useMemo(() => {
    const m = new Map();
    for (const u of users) m.set(u.id, u);
    return m;
  }, [users]);
  const assignedUsers = assignedIds.map(id => userById.get(id)).filter(Boolean);
  const candidates = users.filter(u => !assignedIds.includes(u.id));

  const add = async (user) => {
    setBusy(true);
    setPickerOpen(false);
    const next = [...assignedIds, user.id];
    onChanged && onChanged(alert.id, next);
    try {
      const res = await fetch(`${API_BASE}/alerts/${encodeURIComponent(alert.id)}/assign`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ userId: user.id }),
      });
      if (!res.ok && res.status !== 204) throw new Error(String(res.status));
    } catch (e) {
      console.warn('alert.assign failed', e);
      onChanged && onChanged(alert.id, assignedIds);  // revert
    } finally { setBusy(false); }
  };

  const remove = async (user) => {
    setBusy(true);
    const next = assignedIds.filter(id => id !== user.id);
    onChanged && onChanged(alert.id, next);
    try {
      const res = await fetch(`${API_BASE}/alerts/${encodeURIComponent(alert.id)}/assign/${user.id}`, {
        method: 'DELETE',
      });
      if (!res.ok && res.status !== 204) throw new Error(String(res.status));
    } catch (e) {
      console.warn('alert.unassign failed', e);
      onChanged && onChanged(alert.id, assignedIds);  // revert
    } finally { setBusy(false); }
  };

  // Anchor the picker dropdown on the whole .dr-assign row instead of
  // the small avs span — the avs span's right edge sits well to the
  // left of the drawer's right border, so a `right: 0` + min-width
  // dropdown clipped off the left side of the drawer column.
  return (
    <div className="dr-assign" style={{ position: 'relative' }}>
      <span className="dr-assign-l">Assign to…</span>
      <span className="dr-assign-avs">
        {assignedUsers.length === 0 && (
          <span style={{ fontSize: 11.5, color: 'var(--muted)' }}>nobody</span>
        )}
        {assignedUsers.map(u => (
          <span
            key={u.id}
            title={`${u.name}${u.role ? ' · ' + u.role : ''} (click to unassign)`}
            onClick={() => !busy && remove(u)}
            className="av-mini"
            style={{
              width: 22, height: 22, fontSize: 10, background: avatarColor(u.id),
              cursor: busy ? 'wait' : 'pointer',
            }}
          >{avatarInitials(u.name)}</span>
        ))}
        <button
          className="dr-assign-add"
          aria-label="Add assignee"
          onClick={() => setPickerOpen(o => !o)}
          disabled={busy}
        ><Ic name="plus" size={11}/></button>
      </span>
      {pickerOpen && (
        <>
          {/* Backdrop swallows outside-clicks; below the picker but
              above page content so click-through can't reach
              underlying drawer elements. */}
          <div onClick={() => setPickerOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 998 }}/>
          {/* Picker uses a high z-index AND isolation:isolate so it
              creates its own stacking context. This keeps it above
              any later drawer section that has its own positioned
              elements (e.g. .act-ic with z-index:1 in the activity
              feed below) regardless of DOM order. */}
          <div style={{
            position: 'absolute', top: 'calc(100% + 6px)', left: 0, right: 0, zIndex: 999,
            maxHeight: 300, overflowY: 'auto', isolation: 'isolate',
            background: '#FAF9F6', border: '1px solid var(--hair)', borderRadius: 9,
            boxShadow: '0 12px 32px rgba(11,27,38,.18)',
          }}>
            <div style={{
              padding: '10px 14px', fontSize: 9.5, letterSpacing: '.08em',
              textTransform: 'uppercase', fontFamily: "'JetBrains Mono', monospace",
              color: 'var(--muted)', background: 'var(--bg-2)',
              borderBottom: '1px solid var(--hair-2)', fontWeight: 500,
            }}>
              {candidates.length === 0 ? 'Everyone is assigned' : 'Add assignee'}
            </div>
            {candidates.length === 0 && users.length === 0 && (
              <a href="/settings?section=users" style={{
                display: 'block', padding: '12px 14px', fontSize: 12.5,
                color: 'var(--primary)', textDecoration: 'none',
                background: '#FAF9F6',
              }}>No users yet — add one in Settings → Users &amp; Roles</a>
            )}
            {candidates.map(u => (
              <div
                key={u.id}
                onClick={() => add(u)}
                style={{
                  display: 'flex', alignItems: 'center', gap: 10,
                  padding: '10px 14px', cursor: 'pointer',
                  borderBottom: '1px solid var(--hair-2)',
                  background: '#FAF9F6',
                }}
              >
                <span className="av-mini" style={{
                  width: 22, height: 22, fontSize: 10, background: avatarColor(u.id),
                }}>{avatarInitials(u.name)}</span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13, fontWeight: 500, color: 'var(--ink)' }}>{u.name}</div>
                  <div style={{ fontSize: 11, color: 'var(--muted)' }}>
                    {u.role}{u.assignedStoreNum ? ' · ' + u.assignedStoreNum : ''}
                  </div>
                </div>
              </div>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

/* ——— Drawer ——— */
function Drawer({ alert, onClose, onStatus, onSlackSend, onAssignmentsChanged }) {
  const [openCite, setOpenCite] = React.useState('cite-1');
  if (!alert) return null;

  return (
    <aside className="al-drawer" data-screen-label="Alert detail">
      <div className="dr-head">
        <button className="dr-close" onClick={onClose} aria-label="Close"><Ic name="x" size={14}/></button>
        <div className="dr-chips">
          <span className={`sev-chip ${alert.sev}`}>{alert.sevLbl}</span>
          <span className="type-chip">{alert.type}</span>
        </div>
        <h2 className="dr-title">{alert.title}.</h2>
        <div className="dr-sub">
          <span><span className="num">{alert.num}</span> {alert.store}</span>
          <span className="dot">·</span>
          <span>Fired {alert.age}</span>
          <span className="dot">·</span>
          <span className={`status-pill ${alert.status}`} style={{ height: 18, padding: '0 7px', fontSize: 10.5 }}>
            {alert.status === 'acked' ? 'Acknowledged' : alert.status[0].toUpperCase() + alert.status.slice(1)}
          </span>
        </div>
      </div>

      <div className="dr-section">
        <h4>What the auditor found</h4>
        <p>{alert.explanation || 'No explanation returned by the auditor.'}</p>
      </div>

      <div className="dr-section">
        <h4>Citations from indexed SOPs</h4>
        {(!alert.citations || alert.citations.length === 0) ? (
          <div className="cite-meta">No SOP excerpts cited for this finding.</div>
        ) : (
          <div className="cite-list">
            {alert.citations.map((c, i) => {
              const id = `cite-${i}`;
              const open = openCite === id;
              const pages = c.pageStart != null
                ? (c.pageEnd && c.pageEnd !== c.pageStart ? `pp. ${c.pageStart}–${c.pageEnd}` : `p. ${c.pageStart}`)
                : null;
              return (
                <div key={id} className={`cite-row ${open ? 'open' : ''}`} onClick={() => setOpenCite(open ? null : id)}>
                  <div className="cite-head">
                    <span className="cite-emoji">📘</span>
                    <span className="cite-name">{c.documentTitle}{c.sectionPath ? ` — ${c.sectionPath}` : ''}</span>
                    <span className="cite-arrow"><Ic name="chevR" size={11}/></span>
                  </div>
                  <div className="cite-meta">{pages || 'page unknown'}</div>
                  {open && (
                    <div className="cite-body" onClick={e => e.stopPropagation()}>
                      {c.snippet}
                    </div>
                  )}
                </div>
              );
            })}
          </div>
        )}
      </div>

      <div className="dr-section">
        <h4>Recommended next step</h4>
        <div className="reco">
          <div className="reco-h"><Ic name="fire" size={11}/> Recommended action</div>
          <p>{alert.recommendedAction || 'No recommendation returned.'}</p>
        </div>
      </div>

      <div className="dr-actions">
        <div className="dr-actions-row">
          <button
            className="d-btn d-btn-warm"
            onClick={() => onSlackSend(alert)}
          ><Ic name="send" size={13}/> Send to Slack</button>
        </div>
        <div className="dr-actions-row">
          <button
            className="d-btn d-btn-out"
            disabled={alert.status === 'acked'}
            onClick={() => onStatus(alert.id, 'acked')}
          >{alert.status === 'acked' ? '✓ Acknowledged' : 'Acknowledge'}</button>
          {alert.status === 'snoozed' ? (
            <button
              className="d-btn d-btn-out"
              onClick={() => onStatus(alert.id, 'open')}
              title="Bring this alert back to the open queue"
            >Unsnooze</button>
          ) : (
            <button
              className="d-btn d-btn-out"
              onClick={() => onStatus(alert.id, 'snoozed')}
            >Snooze 4h</button>
          )}
          <button
            className="d-btn d-btn-text"
            onClick={() => onStatus(alert.id, 'resolved')}
          >Mark resolved</button>
        </div>
        <AssignSection alert={alert} onChanged={onAssignmentsChanged}/>
      </div>

      <div className="dr-section" style={{ borderBottom: 0 }}>
        <h4>Activity</h4>
        <div className="activity">
          <div className="act-row">
            <span className="act-ic fired"><span style={{width:6,height:6,borderRadius:'50%',background:'#fff'}}/></span>
            <div className="act-text">Alert fired automatically <span className="meta">— cooler reading 44.2°F</span></div>
            <span className="act-time">11:32pm</span>
          </div>
          <div className="act-row">
            <span className="act-ic viewed"><Ic name="eye" size={9}/></span>
            <div className="act-text"><span className="who">Max Newberry</span> opened the alert</div>
            <span className="act-time">11:44pm</span>
          </div>
          <div className="act-row">
            <span className="act-ic now"><span style={{width:5,height:5,borderRadius:'50%',background:'#fff'}}/></span>
            <div className="act-text"><span className="who">You</span> are viewing now</div>
            <span className="act-time">just now</span>
          </div>
        </div>
      </div>
    </aside>
  );
}

function App() {
  const [tab, setTab] = React.useState('all');
  const [view, setView] = React.useState('list');
  const [selectedId, setSelectedId] = React.useState(() => {
    if (typeof window === 'undefined') return null;
    return new URLSearchParams(window.location.search).get('id') || null;
  });
  const [checked, setChecked] = React.useState(new Set());
  const [filters, setFilters] = React.useState({ severity: 'any', store: 'any', status: 'open', time: '7d' });
  const [search, setSearch] = React.useState('');
  const [alerts, setAlerts] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [refreshing, setRefreshing] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [scenarios, setScenarios] = React.useState([]);
  const [viewedScenarioId, setViewedScenarioId] = React.useState(null);
  const [resolvedThisSession, setResolvedThisSession] = React.useState(0);
  const [slackAlert, setSlackAlert] = React.useState(null);
  const [slackSentNotice, setSlackSentNotice] = React.useState(null);
  const [scanStatus, setScanStatus] = React.useState(null);

  const refreshScanStatus = React.useCallback(() => {
    return fetch(`${API_BASE}/alerts/scan/status`, { cache: 'no-store' })
      .then(r => r.ok ? r.json() : null)
      .then(setScanStatus)
      .catch(() => setScanStatus(null));
  }, []);

  React.useEffect(() => {
    if (!slackSentNotice) return;
    const t = setTimeout(() => setSlackSentNotice(null), 3500);
    return () => clearTimeout(t);
  }, [slackSentNotice]);

  React.useEffect(() => {
    fetchScenarios().then(setScenarios).catch(e => console.warn('scenarios fetch failed', e));
  }, []);

  const runScan = React.useCallback(async (refresh) => {
    if (refresh) setRefreshing(true); else setLoading(true);
    setError(null);
    try {
      const data = await fetchAlerts(refresh);
      setAlerts(data);
      setSelectedId(prev => {
        if (prev && data.some(a => a.id === prev)) return prev;
        return data[0]?.id ?? null;
      });
    } catch (e) {
      setError(e.message || String(e));
    } finally {
      setLoading(false);
      setRefreshing(false);
      // Always refresh freshness status — captures both success and the
      // error path so the pill reflects what actually happened.
      refreshScanStatus();
    }
  }, [refreshScanStatus]);

  React.useEffect(() => { runScan(false); }, [runScan]);

  // Optimistic local update of an alert's assignedUserIds. Used by the
  // Drawer's AssignSection so adding/removing a user reflects
  // immediately without round-tripping a /alerts/scan.
  const onAssignmentsChanged = React.useCallback((alertId, nextIds) => {
    setAlerts(curr => curr.map(a => a.id === alertId ? { ...a, assignedUserIds: nextIds } : a));
  }, []);

  const updateStatus = React.useCallback(async (id, status) => {
    // Optimistic local update — drop "resolved" alerts from the list,
    // otherwise patch the row's status. Reverts on backend failure.
    const previous = alerts;
    setAlerts(curr => status === 'resolved'
      ? curr.filter(a => a.id !== id)
      : curr.map(a => a.id === id ? { ...a, status } : a));
    if (status === 'resolved') setResolvedThisSession(n => n + 1);
    try {
      await setAlertStatusBackend(id, status);
      window.MISE.notifyAlertsChanged();
    } catch (e) {
      console.warn('alert status update failed', e);
      setAlerts(previous);
      if (status === 'resolved') setResolvedThisSession(n => Math.max(0, n - 1));
    }
  }, [alerts]);

  const bulkUpdateStatus = React.useCallback(async (status) => {
    const ids = Array.from(checked);
    if (ids.length === 0) return;
    const previous = alerts;
    setAlerts(curr => status === 'resolved'
      ? curr.filter(a => !checked.has(a.id))
      : curr.map(a => checked.has(a.id) ? { ...a, status } : a));
    setChecked(new Set());
    if (status === 'resolved') setResolvedThisSession(n => n + ids.length);
    try {
      await bulkSetAlertStatusBackend(ids, status);
      window.MISE.notifyAlertsChanged();
    } catch (e) {
      console.warn('bulk alert status update failed', e);
      setAlerts(previous);
      if (status === 'resolved') setResolvedThisSession(n => Math.max(0, n - ids.length));
    }
  }, [alerts, checked]);

  const filteredRows = React.useMemo(
    () => applyAlertFilters(alerts, { tab, filters, search }),
    [alerts, tab, filters, search]
  );

  const tabCounts = React.useMemo(() => {
    const counts = {};
    for (const t of TABS) {
      counts[t.id] = applyAlertFilters(alerts, { tab: t.id, filters, search }).length;
    }
    return counts;
  }, [alerts, filters, search]);

  const selected = alerts.find(a => a.id === selectedId);

  // Keyboard shortcuts shown in the .kbd-hint footer:
  //   ↑ / ↓  : move selection within the current filtered list
  //   Enter  : open scenario detail for the selected alert
  //   A      : acknowledge selected
  //   S      : snooze selected (4h)
  // Skip when the user is typing in any input/textarea.
  React.useEffect(() => {
    const handler = (e) => {
      const t = e.target;
      if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return;
      if (e.metaKey || e.ctrlKey || e.altKey) return;
      if (filteredRows.length === 0) return;
      const idx = filteredRows.findIndex(a => a.id === selectedId);
      if (e.key === 'ArrowDown') {
        e.preventDefault();
        const next = idx < 0 ? 0 : Math.min(filteredRows.length - 1, idx + 1);
        setSelectedId(filteredRows[next].id);
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        const next = idx <= 0 ? 0 : idx - 1;
        setSelectedId(filteredRows[next].id);
      } else if (e.key === 'Enter') {
        if (selected) {
          e.preventDefault();
          setViewedScenarioId(selected.scenarioId);
        }
      } else if (e.key === 'a' || e.key === 'A') {
        if (selected) {
          e.preventDefault();
          // Toggle: ack → reopen, anything else → ack.
          updateStatus(selected.id, selected.status === 'acked' ? 'open' : 'acked');
        }
      } else if (e.key === 's' || e.key === 'S') {
        if (selected) {
          e.preventDefault();
          // Toggle: snoozed → unsnooze, anything else → snooze.
          updateStatus(selected.id, selected.status === 'snoozed' ? 'open' : 'snoozed');
        }
      }
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, [filteredRows, selectedId, selected, updateStatus]);

  return (
    <div className="shell">
      <Sidebar/>
      <div>
        <Topbar selectedCount={checked.size} onBulk={bulkUpdateStatus}/>
        <div className={`al-shell ${selected ? '' : 'no-drawer'}`}>
          <main className="al-main">
            <SummaryStrip alerts={alerts} resolvedThisSession={resolvedThisSession}/>
            <FilterBar
              alerts={alerts}
              tab={tab} setTab={setTab}
              view={view} setView={setView}
              filters={filters} setFilters={setFilters}
              search={search} setSearch={setSearch}
              tabCounts={tabCounts}
              onRefresh={() => runScan(true)}
              refreshing={refreshing}
              scanStatus={scanStatus}
            />
            {view === 'group' ? (
              <GroupedAlertsList
                allAlerts={alerts}
                rows={filteredRows}
                selectedId={selectedId}
                setSelectedId={setSelectedId}
                checked={checked}
                setChecked={setChecked}
                loading={loading}
                error={error}
                onAck={updateStatus}
                onStatus={updateStatus}
                onViewScenario={setViewedScenarioId}
                onSetStatusFilter={(status) => setFilters(f => ({ ...f, status }))}
                currentStatusFilter={filters.status}
              />
            ) : (
              <AlertsList
                allAlerts={alerts}
                rows={filteredRows}
                selectedId={selectedId}
                setSelectedId={setSelectedId}
                checked={checked}
                setChecked={setChecked}
                loading={loading}
                error={error}
                onAck={updateStatus}
                onStatus={updateStatus}
                onViewScenario={setViewedScenarioId}
                onSetStatusFilter={(status) => setFilters(f => ({ ...f, status }))}
                currentStatusFilter={filters.status}
              />
            )}
            {selected && (
              <div className="kbd-hint">
                <span><kbd>↑</kbd><kbd>↓</kbd> navigate</span>
                <span className="sep">·</span>
                <span><kbd>↵</kbd> open</span>
                <span className="sep">·</span>
                <span><kbd>A</kbd> ack</span>
                <span className="sep">·</span>
                <span><kbd>S</kbd> snooze</span>
              </div>
            )}
          </main>
          <Drawer alert={selected} onClose={() => setSelectedId(null)} onStatus={updateStatus} onSlackSend={setSlackAlert} onAssignmentsChanged={onAssignmentsChanged}/>
        </div>
      </div>
      <ScenarioModal
        scenario={viewedScenarioId ? scenarios.find(s => s.id === viewedScenarioId) : null}
        onClose={() => setViewedScenarioId(null)}
      />
      <SlackSendModal
        alert={slackAlert}
        onClose={() => setSlackAlert(null)}
        onSent={(channelName) => setSlackSentNotice(`Sent to #${channelName}`)}
      />
      {slackSentNotice && (
        <div style={{
          position: 'fixed', bottom: 28, left: '50%', transform: 'translateX(-50%)',
          background: 'var(--green)', color: 'var(--bg)', padding: '10px 18px',
          borderRadius: 999, fontSize: 13, fontWeight: 500, zIndex: 1100,
          boxShadow: '0 8px 20px rgba(11,27,38,.25)',
        }}>{slackSentNotice}</div>
      )}
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App/>);
