/* Mise Stores list — same shell, dense data table */

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 'plus':      return <svg viewBox="0 0 16 16" style={s}><path d="M8 3v10M3 8h10" {...p}/></svg>;
    case 'list':      return <svg viewBox="0 0 16 16" style={s}><g {...p}><path d="M3 4h10M3 8h10M3 12h10"/></g></svg>;
    case 'map':       return <svg viewBox="0 0 16 16" style={s}><g {...p}><path d="M2 4l4-1 4 1 4-1v9l-4 1-4-1-4 1z"/><path d="M6 3v10M10 4v10"/></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', active: true },
    { id: 'alerts', icon: 'alerts', label: 'Alerts', href: '/alerts', badge: openAlertsCount },
    { id: 'ask', icon: 'ask', label: 'Ask Mise', href: '/ask' },
  ];
  const items2 = [
    { id: 'docs', icon: 'docs', label: 'Documents', href: '/documents' },
    { id: 'reports', icon: 'reports', label: 'Reports', href: '/reports' },
    { id: 'settings', icon: 'settings', label: 'Settings', href: '/settings' },
  ];
  const renderItem = (it) => (
    <a key={it.id} href={it.href || '#'} className={`nav-item ${it.active ? 'active' : ''}`}>
      <Ic name={it.icon}/>
      <span>{it.label}</span>
      {it.badge && <span className="badge">{it.badge}</span>}
    </a>
  );
  return (
    <aside className="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>
          <div className="label">Organization</div>
          <div className="name">{org.name}</div>
        </div>
      </div>
      <nav className="nav-list">
        {items.map(renderItem)}
        <div className="nav-section">Workspace</div>
        {items2.map(renderItem)}
      </nav>
      <UserTile/>
    </aside>
  );
}

function Topbar({ storeCount, scanStatus, onRescan, scanning }) {
  return (
    <header className="topbar">
      <div>
        <span className="crumb">Stores</span>
        <span className="crumb"><span className="sub">{storeCount != null ? `— ${storeCount} store${storeCount === 1 ? '' : 's'}` : ''}</span></span>
      </div>
      <div className="top-right">
        <AuditFreshness status={scanStatus} onRescan={onRescan} scanning={scanning}/>
      </div>
    </header>
  );
}

/** Surfaces "Audited Xm ago" plus a manual rescan affordance. The
 *  alert pipeline runs nightly at 05:30 server time; this lets the
 *  operator pull a fresh scan on demand when they've made a change
 *  and don't want to wait for tomorrow's cron. */
function AuditFreshness({ status, onRescan, scanning }) {
  const last = status?.lastScannedAt ? new Date(status.lastScannedAt) : null;
  const label = last ? `Audited ${formatRelative(last)}` : 'Audit not yet run';
  const tone =
    status?.lastStatus === 'error' ? 'err'
    : !last ? 'muted'
    : (Date.now() - last.getTime()) > 36 * 3600 * 1000 ? 'stale'
    : 'ok';
  return (
    <div style={{ display: 'inline-flex', alignItems: 'center', gap: 10 }}>
      <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)',
      }}>
        <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>
      <button onClick={onRescan} disabled={scanning} title="Re-run the AI compliance scan now" style={{
        padding: '4px 10px', borderRadius: 8,
        border: '1px solid var(--hair)', background: 'var(--bg)',
        fontFamily: 'inherit', fontSize: 11.5, color: 'var(--ink)',
        cursor: scanning ? 'not-allowed' : 'pointer', opacity: scanning ? 0.6 : 1,
      }}>
        {scanning ? 'Scanning…' : 'Rescan'}
      </button>
    </div>
  );
}

function formatRelative(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`;
}

function FilterChip({ label, options, value, onChange, active }) {
  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={`fchip ${active ? 'active' : ''}`} onClick={() => setOpen(o => !o)}>
        <span className="lbl">{label}:</span> {current.title}
        <span className="chev" style={{ transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .15s', display: 'inline-flex' }}><Ic name="chev" size={12}/></span>
      </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>
                {o.meta && <div className="dd-meta">{o.meta}</div>}
              </div>
              {o.id === value && <span className="dd-check">✓</span>}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

const STORES_API_BASE = (typeof window !== 'undefined' && window.MISE_API_BASE) || 'http://localhost:8080';

function Filters({ filters, setFilters, view, setView, stores = [], onStoreAdded }) {
  const set = (k) => (v) => setFilters(f => ({ ...f, [k]: v }));
  // Brand chip options come from the actual brand assignments on the
  // current store list — no hardcoded list. Brands appear in
  // alphabetical order, with an "Unbranded" bucket for stores that
  // haven't been assigned yet.
  const brandCounts = React.useMemo(() => {
    const map = new Map();
    let unbranded = 0;
    for (const s of stores) {
      if (s.brand && s.brand.id) {
        const cur = map.get(s.brand.id) || { id: s.brand.id, name: s.brand.name, count: 0 };
        cur.count++;
        map.set(s.brand.id, cur);
      } else {
        unbranded++;
      }
    }
    const sorted = [...map.values()].sort((a, b) => a.name.localeCompare(b.name));
    return { list: sorted, unbranded };
  }, [stores]);
  const counts = {
    all: stores.length,
    ca: stores.filter(s => s.state === 'CA').length,
    nv: stores.filter(s => s.state === 'NV').length,
  };
  const brandOptions = [
    { id: 'all', title: 'All', meta: `${stores.length} store${stores.length === 1 ? '' : 's'}` },
    ...brandCounts.list.map(b => ({ id: b.id, title: b.name, meta: `${b.count} store${b.count === 1 ? '' : 's'}` })),
    ...(brandCounts.unbranded > 0 ? [{ id: '__none', title: 'Unbranded', meta: `${brandCounts.unbranded} store${brandCounts.unbranded === 1 ? '' : 's'}` }] : []),
  ];
  return (
    <div className="filters">
      <div className="filter-chips">
        <FilterChip label="State" active={filters.state !== 'all'} value={filters.state} onChange={set('state')} options={[
          { id: 'all', title: 'All', meta: `${counts.all} stores` },
          { id: 'ca', title: 'California', meta: `${counts.ca} stores` },
          { id: 'nv', title: 'Nevada', meta: `${counts.nv} stores` },
        ]}/>
        <FilterChip label="Brand" active={filters.brand !== 'all'} value={filters.brand} onChange={set('brand')} options={brandOptions}/>
        <FilterChip label="Health" active={filters.health !== 'any'} value={filters.health} onChange={set('health')} options={[
          { id: 'any', title: 'Any', meta: `${counts.all} stores` },
          { id: 'red', title: 'At risk', meta: 'Score < 70' },
          { id: 'amber', title: 'Watch', meta: 'Score 70–79' },
          { id: 'green', title: 'Healthy', meta: 'Score ≥ 80' },
        ]}/>
        <FilterChip label="Sort" active value={filters.sort} onChange={set('sort')} options={[
          { id: 'composite', title: 'Composite (worst first)' },
          { id: 'sales', title: 'Sales (high to low)' },
          { id: 'labor', title: 'Labor % (high to low)' },
          { id: 'alerts', title: 'Open alerts' },
          { id: 'name', title: 'Name (A–Z)' },
        ]}/>
      </div>
      <div style={{ display: 'inline-flex', alignItems: 'center', gap: 10 }}>
        <AddStoreButton onAdded={onStoreAdded}/>
        <div className="view-toggle">
          <button className={view==='list' ? 'active' : ''} onClick={() => setView('list')}><Ic name="list" size={13}/> List</button>
          <button className={view==='map' ? 'active' : ''} onClick={() => setView('map')}><Ic name="map" size={13}/> Map</button>
        </div>
      </div>
    </div>
  );
}

const blankStore = () => ({
  num: '', name: '', city: '', state: '',
  timezone: 'America/Los_Angeles', status: 'active', brandId: null,
});

function AddStoreButton({ onAdded }) {
  const [open, setOpen] = React.useState(false);
  const [form, setForm] = React.useState(blankStore);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);
  const [brandOptions, setBrandOptions] = React.useState([]);
  React.useEffect(() => {
    if (window.MISE && window.MISE.fetchBrandsCached) {
      window.MISE.fetchBrandsCached().then(setBrandOptions);
    } else {
      fetch(`${STORES_API_BASE}/brands`, { cache: 'no-store' })
        .then(r => r.ok ? r.json() : []).then(setBrandOptions).catch(() => setBrandOptions([]));
    }
  }, []);

  const close = () => { if (!busy) { setOpen(false); setErr(null); setForm(blankStore()); } };
  const update = (k) => (e) => setForm(f => ({ ...f, [k]: e.target.value }));

  const onSave = async () => {
    setBusy(true); setErr(null);
    try {
      const res = await fetch(`${STORES_API_BASE}/stores`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(form),
      });
      if (!res.ok) throw new Error(`${res.status}: ${await res.text() || res.statusText}`);
      setOpen(false);
      setForm(blankStore());
      onAdded && onAdded();
    } catch (e) {
      setErr(e.message || String(e));
    } finally { setBusy(false); }
  };

  return (
    <>
      <button onClick={() => setOpen(true)} title="Add a new store" style={addBtnStyle}>
        <Ic name="plus" size={13}/> Add store
      </button>
      {open && (
        <div onClick={close} 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: 480, maxWidth: '92vw',
            boxShadow: '0 20px 60px rgba(11,27,38,.25)', border: '1px solid var(--hair)',
          }}>
            <div style={{ fontSize: 11, color: 'var(--muted)', fontFamily: "'JetBrains Mono', monospace", letterSpacing: '.05em', textTransform: 'uppercase', marginBottom: 4 }}>Add store</div>
            <div style={{ fontSize: 16, fontWeight: 600, color: 'var(--ink)', marginBottom: 14 }}>
              {form.num ? `${form.num.startsWith('#') ? form.num : '#' + form.num} ${form.name || ''}`.trim() : 'New store'}
            </div>

            <div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: 12, marginBottom: 4 }}>
              <Field label="Store #"><input value={form.num} onChange={update('num')} disabled={busy} placeholder="e.g. 14" style={inputStyle}/></Field>
              <Field label="Name"><input value={form.name} onChange={update('name')} disabled={busy} placeholder="Wingstop Anaheim" style={inputStyle}/></Field>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: 12 }}>
              <Field label="City"><input value={form.city} onChange={update('city')} disabled={busy} style={inputStyle}/></Field>
              <Field label="State"><input value={form.state} onChange={update('state')} disabled={busy} placeholder="CA" maxLength={4} style={inputStyle}/></Field>
            </div>
            <Field label="Timezone">
              <select value={form.timezone} onChange={update('timezone')} disabled={busy} style={inputStyle}>
                <option value="America/Los_Angeles">America/Los_Angeles (PT)</option>
                <option value="America/Denver">America/Denver (MT)</option>
                <option value="America/Phoenix">America/Phoenix (Arizona)</option>
                <option value="America/Chicago">America/Chicago (CT)</option>
                <option value="America/New_York">America/New_York (ET)</option>
              </select>
            </Field>

            <Field label="Brand">
              <select
                value={form.brandId || ''}
                onChange={(e) => setForm(f => ({ ...f, brandId: e.target.value || null }))}
                disabled={busy}
                style={inputStyle}
              >
                <option value="">— No brand —</option>
                {brandOptions.map(b => (
                  <option key={b.id} value={b.id}>{b.name}</option>
                ))}
              </select>
              {brandOptions.length === 0 && (
                <div style={{ fontSize: 11.5, color: 'var(--muted)', marginTop: 4 }}>
                  No brands defined yet. Add one from Settings → Brands.
                </div>
              )}
            </Field>

            <div style={{
              margin: '6px 0 14px', padding: '10px 12px', borderRadius: 8,
              background: 'var(--bg-2)', border: '1px solid var(--hair)',
              fontSize: 11.5, color: 'var(--muted)', lineHeight: 1.5,
            }}>
              POS, labor, and food-safety connections turn on automatically once you map this store to a location in Settings → POS.
            </div>

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

            <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
              <button onClick={close} disabled={busy} style={modalGhostStyle(busy)}>Cancel</button>
              <button onClick={onSave}
                disabled={busy || !form.num.trim() || !form.name.trim()}
                style={modalPrimaryStyle(busy || !form.num.trim() || !form.name.trim())}>
                {busy ? 'Adding…' : 'Add store'}
              </button>
            </div>
          </div>
        </div>
      )}
    </>
  );
}

function Field({ label, children }) {
  return (
    <label style={{ display: 'block', marginBottom: 10 }}>
      <div style={{ fontSize: 11.5, color: 'var(--muted)', marginBottom: 4 }}>{label}</div>
      {children}
    </label>
  );
}

const inputStyle = {
  width: '100%', padding: '8px 10px', borderRadius: 8,
  border: '1px solid var(--hair)', background: 'var(--bg)',
  fontFamily: 'inherit', fontSize: 13, color: 'var(--ink)', boxSizing: 'border-box',
};

const addBtnStyle = {
  display: 'inline-flex', alignItems: 'center', gap: 6,
  padding: '6px 12px', height: 30,
  background: 'var(--primary)', color: 'var(--bg)',
  border: '1px solid var(--primary)', borderRadius: 8,
  fontFamily: 'inherit', fontSize: 12.5, fontWeight: 500,
  cursor: 'pointer',
};

const modalPrimaryStyle = (disabled) => ({
  padding: '8px 14px', borderRadius: 8,
  background: 'var(--primary)', color: 'var(--bg)',
  border: '1px solid var(--primary)',
  fontFamily: 'inherit', fontSize: 13, fontWeight: 500,
  cursor: disabled ? 'not-allowed' : 'pointer',
  opacity: disabled ? 0.6 : 1,
});

const modalGhostStyle = (disabled) => ({
  padding: '8px 14px', borderRadius: 8,
  background: 'var(--bg)', color: 'var(--ink)',
  border: '1px solid var(--hair)',
  fontFamily: 'inherit', fontSize: 13,
  cursor: disabled ? 'not-allowed' : 'pointer',
  opacity: disabled ? 0.6 : 1,
});

const parseMoney = (s) => Number((s || '').replace(/[^0-9.-]/g, '')) || 0;
const parsePct   = (s) => Number((s || '').replace(/[^0-9.-]/g, '')) || 0;
function applyStoreFilters(rows, f) {
  let out = rows.filter(s => {
    if (f.state === 'ca' && s.state !== 'CA') return false;
    if (f.state === 'nv' && s.state !== 'NV') return false;
    if (f.brand !== 'all') {
      if (f.brand === '__none') {
        if (s.brand && s.brand.id) return false;
      } else if (!s.brand || s.brand.id !== f.brand) {
        return false;
      }
    }
    // Stores in Pending (no live data → null score) drop out of any
    // health-bucket filter except "any" — they're not red/amber/green
    // until they have evidence.
    if (f.health !== 'any' && (s.score == null)) return false;
    if (f.health === 'red'   && s.score >= 70) return false;
    if (f.health === 'amber' && (s.score < 70 || s.score >= 80)) return false;
    if (f.health === 'green' && s.score < 80) return false;
    return true;
  });
  const cmp = {
    composite: (a, b) => {
      // Pending (null score) sorts after all real scores.
      if (a.score == null && b.score == null) return 0;
      if (a.score == null) return 1;
      if (b.score == null) return -1;
      return a.score - b.score;
    },
    sales:     (a, b) => parseMoney(b.sales) - parseMoney(a.sales),
    labor:     (a, b) => parsePct(b.labor) - parsePct(a.labor),
    alerts:    (a, b) => b.alerts - a.alerts,
    name:      (a, b) => a.name.localeCompare(b.name),
  }[f.sort] || (() => 0);
  return [...out].sort(cmp);
}

const SPARK_DAYS = ['Wed','Thu','Fri','Sat','Sun','Mon','Tue'];

function Spark({ data, color = 'var(--primary)', format = (v) => v }) {
  const W = 60, H = 20;
  const min = Math.min(...data), max = Math.max(...data), r = max - min || 1;
  const xAt = (i) => (i / (data.length - 1)) * W;
  const yAt = (v) => H - ((v - min) / r) * (H - 4) - 2;
  const pts = data.map((v, i) => `${xAt(i)},${yAt(v)}`).join(' ');
  const wrapRef = React.useRef(null);
  const [hoverIdx, setHoverIdx] = React.useState(null);
  const onMove = (e) => {
    e.stopPropagation();
    const rect = wrapRef.current.getBoundingClientRect();
    const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
    setHoverIdx(Math.round(ratio * (data.length - 1)));
  };
  return (
    <span
      ref={wrapRef}
      style={{ position: 'relative', display: 'inline-block', lineHeight: 0 }}
      onMouseMove={onMove}
      onMouseLeave={() => setHoverIdx(null)}
    >
      <svg className="spark" viewBox={`0 0 ${W} ${H}`} aria-hidden="true">
        <polyline points={pts} fill="none" stroke={color} strokeWidth="1.4" strokeLinejoin="round" strokeLinecap="round"/>
        {hoverIdx != null && (
          <>
            <line x1={xAt(hoverIdx)} x2={xAt(hoverIdx)} y1={0} y2={H} stroke={color} strokeDasharray="1.5 1.5" strokeWidth=".7" opacity=".55"/>
            <circle cx={xAt(hoverIdx)} cy={yAt(data[hoverIdx])} r="2" fill={color} stroke="var(--bg)" strokeWidth="1"/>
          </>
        )}
      </svg>
      {hoverIdx != null && (
        <span style={{
          position: 'absolute', bottom: 'calc(100% + 4px)', left: '50%', transform: 'translateX(-50%)',
          background: 'var(--ink)', color: 'var(--bg)', padding: '4px 7px', borderRadius: 5,
          fontSize: 10.5, lineHeight: 1.3, whiteSpace: 'nowrap',
          fontFamily: "'JetBrains Mono', monospace", pointerEvents: 'none', zIndex: 20,
          boxShadow: '0 3px 10px rgba(11,27,38,.20)',
        }}>
          {SPARK_DAYS[(SPARK_DAYS.length - data.length + hoverIdx + SPARK_DAYS.length) % SPARK_DAYS.length]} · {format(data[hoverIdx])}
        </span>
      )}
    </span>
  );
}

function ScoreBar({ score }) {
  // 10 segments; color by tier
  const segs = [];
  const filled = Math.round(score / 10);
  for (let i = 0; i < 10; i++) {
    if (i >= filled) segs.push('empty');
    else if (i < 3) segs.push(score < 70 ? 'r' : score < 80 ? 'a' : 'g');
    else if (i < 6) segs.push(score < 70 ? 'r' : score < 80 ? 'a' : 'g');
    else if (i < 8) segs.push(score < 80 ? 'a' : 'g');
    else segs.push('g');
  }
  return <div className="score-bar">{segs.map((s, i) => <i key={i} className={s}/>)}</div>;
}

const STATE_COLORS = window.MISE.stateColors;

/** Mock-fixture geo lookup — used only as a fallback for stores the
 *  backend hasn't geocoded yet. Backend coords (b.lat / b.lng) come
 *  from GeocodingService and are always preferred when present. */
const STORE_GEO = Object.fromEntries(
  window.MISE.stores.map(s => [s.num, { lat: s.lat, lng: s.lng }])
);

/** Convert a /stores/list row into the shape the table + map expect.
 *  Stores without synced data get null-ish placeholders the renderer
 *  formats as "—" rather than zero, so the UI is honest about what
 *  isn't connected yet. */
function rowFromBackend(b) {
  const geo = (b.lat != null && b.lng != null)
    ? { lat: b.lat, lng: b.lng }
    : (STORE_GEO[b.num] || { lat: null, lng: null });
  // Sales card formatting.
  const sales = b.hasSalesData
    ? `$${Math.round(b.sales.thisWeekDollars).toLocaleString()}`
    : '—';
  const sparkD = b.sales.sparkDaily.length > 0 ? b.sales.sparkDaily : [0,0,0,0,0,0,0];
  // Sales delta is week-over-week. Backend returns null when no
  // prior-week baseline exists (new store / first sync) — render "—"
  // rather than "+0.0%" which misreads as "no change".
  const salesDelta = !b.hasSalesData || b.sales.deltaPct == null
    ? { txt: '—', tone: 'gray' }
    : (b.sales.deltaPct >= 0
        ? { txt: `+${b.sales.deltaPct.toFixed(1)}%`, tone: 'green' }
        : { txt: `${b.sales.deltaPct.toFixed(1)}%`, tone: 'red' });
  // Labor card — use variance % since we don't have wage data for true
  // labor cost %. Frontend label says "Labor %" today; the meaning is
  // |actual − scheduled| / scheduled. Same null-baseline handling as
  // sales delta.
  const labor = b.hasLaborData ? `${b.labor.thisWeekVarianceAbsPct.toFixed(1)}%` : '—';
  const laborDelta = !b.hasLaborData || b.labor.deltaPct == null
    ? { txt: '—', tone: 'gray' }
    : (b.labor.deltaPct <= 0
        ? { txt: `${b.labor.deltaPct.toFixed(1)}pt`, tone: 'green' }
        : { txt: `+${b.labor.deltaPct.toFixed(1)}pt`, tone: 'red' });
  // Alert summary.
  const alertSev = b.criticalAlertCount > 0 ? 'red'
    : b.openAlertCount > 0 ? 'amber'
    : null;
  // Food-safety integration not wired yet — leave placeholder.
  const tempLog = { time: '—', sub: 'no log integration connected' };
  // Audit grade pill.
  const audit = b.audit
    ? { date: '—', grade: b.audit.grade, tone: b.audit.tone }
    : { date: '—', grade: '—', tone: 'a' };
  return {
    num: b.num,
    name: b.name,
    city: b.city || '',
    state: b.state || '',
    lat: geo.lat,
    lng: geo.lng,
    brand: b.brand || null,
    gm: b.gm || null,
    score: b.composite,
    grade: b.grade,
    pendingSetup: b.pendingSetup === true,
    sales,
    sparkD,
    salesDelta,
    labor,
    laborDelta,
    alerts: b.openAlertCount,
    alertSev,
    tempLog,
    audit,
    // Raw numbers for the expand panel; the formatted versions above
    // are tuned for the table cells.
    salesThisWeek:  b.hasSalesData ? (b.sales?.thisWeekDollars  ?? 0) : null,
    salesPriorWeek: b.hasSalesData ? (b.sales?.priorWeekDollars ?? 0) : null,
    laborVarPct:    b.hasLaborData ? (b.labor?.thisWeekVarianceAbsPct ?? null) : null,
    foodSafetyPct:  typeof b.foodSafetyCompliancePct === 'number' ? b.foodSafetyCompliancePct : null,
    auditGrade:     b.auditGrade || null,
    hasSalesData:   !!b.hasSalesData,
    hasLaborData:   !!b.hasLaborData,
  };
}

function ExpandPanel({ row }) {
  // Live data only. Previous version showed hardcoded $11,640 / 29.1% /
  // 99.1% / 94/100 / "Priya Shah" — those numbers were demo content
  // that read as live performance. Each panel below either renders
  // real numbers from /stores/list or surfaces an honest empty state.
  const fmtMoney = (v) => `$${Math.round(v).toLocaleString()}`;
  const sales = row.salesThisWeek;
  const prior = row.salesPriorWeek;
  const salesDeltaPct = (prior && prior > 0) ? ((sales - prior) / prior) * 100 : null;
  return (
    <div className="expand-panel">
      <div className="expand-grid">
        <div className="mini-chart">
          <div className="lbl">Sales · 7 days</div>
          {row.hasSalesData ? (
            <>
              <div className="val">{fmtMoney(sales || 0)}</div>
              <div className="sub">
                {prior && prior > 0
                  ? <>prior {fmtMoney(prior)} · trend {salesDeltaPct >= 0 ? '+' : ''}{salesDeltaPct.toFixed(1)}%</>
                  : 'no prior comparison'}
              </div>
              <div className="chart">
                <Spark data={row.sparkD} color={salesDeltaPct == null || salesDeltaPct >= 0 ? 'var(--green)' : 'var(--red)'} format={v => `$${Math.round(v).toLocaleString()}`}/>
              </div>
            </>
          ) : (
            <>
              <div className="val" style={{ color: 'var(--muted-2)' }}>—</div>
              <div className="sub">no POS sync</div>
            </>
          )}
        </div>

        <div className="mini-chart">
          <div className="lbl">Labor variance · 7 days</div>
          {row.hasLaborData && row.laborVarPct != null ? (
            <>
              <div className="val">{row.laborVarPct.toFixed(1)}<span style={{ fontSize: 14, color: 'var(--muted)' }}>%</span></div>
              <div className="sub">|actual − scheduled| ÷ scheduled</div>
            </>
          ) : (
            <>
              <div className="val" style={{ color: 'var(--muted-2)' }}>—</div>
              <div className="sub">no labor sync</div>
            </>
          )}
        </div>

        <div className="mini-chart">
          <div className="lbl">Food safety</div>
          {row.foodSafetyPct != null ? (
            <>
              <div className="val">{row.foodSafetyPct.toFixed(1)}<span style={{ fontSize: 14, color: 'var(--muted)' }}>%</span></div>
              <div className="sub">most recent log review</div>
            </>
          ) : (
            <>
              <div className="val" style={{ color: 'var(--muted-2)' }}>—</div>
              <div className="sub">not recorded yet</div>
            </>
          )}
        </div>

        <div className="mini-chart">
          <div className="lbl">Audit grade</div>
          {row.auditGrade ? (
            <>
              <div className="val">{row.auditGrade}</div>
              <div className="sub">most recent recorded</div>
            </>
          ) : (
            <>
              <div className="val" style={{ color: 'var(--muted-2)' }}>—</div>
              <div className="sub">not recorded yet</div>
            </>
          )}
        </div>
      </div>
      <div className="expand-actions">
        <div className="expand-meta">
          <span><strong>GM:</strong> {row.gm?.name || <span style={{ color: 'var(--muted-2)' }}>—</span>}</span>
          {row.brand && <span><strong>Brand:</strong> {row.brand.name}</span>}
        </div>
        <a className="open-detail" href={`/stores/${row.num.replace('#', '')}`}>Open store detail →</a>
      </div>
    </div>
  );
}

function StoresTable({ filters, stores = [], loaded = true }) {
  const rows = React.useMemo(() => applyStoreFilters(stores, filters), [stores, filters]);
  const initialExpanded = rows.findIndex(s => s.expanded);
  const [expanded, setExpanded] = React.useState(initialExpanded);
  React.useEffect(() => { setExpanded(rows.findIndex(s => s.expanded)); }, [filters]);
  if (!loaded) {
    return (
      <div className="table-wrap" style={{ padding: 40, textAlign: 'center', color: 'var(--muted)' }}>
        Loading stores…
      </div>
    );
  }
  if (rows.length === 0) {
    return (
      <div className="table-wrap" style={{ padding: 40, textAlign: 'center', color: 'var(--muted)' }}>
        No stores match the current filters.
      </div>
    );
  }
  return (
    <div className="table-wrap">
      <table className="ttable">
        <thead>
          <tr>
            <th>Store</th>
            <th>State</th>
            <th className="sortable">Composite <span className="sort">↑</span></th>
            <th>Sales · today</th>
            <th>Labor %</th>
            <th>Open alerts</th>
            <th>Last food-safety log</th>
            <th>Last audit</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          {rows.map((s, i) => (
            <React.Fragment key={s.num}>
              <tr
                className={expanded === i ? 'expanded' : ''}
                onClick={() => setExpanded(expanded === i ? -1 : i)}
              >
                <td>
                  <div className="store-cell">
                    <span className="store-num">{s.num}</span>
                    <div className="store-info">
                      <div className="nm">{s.name}</div>
                      <div className="ct">{s.city}</div>
                    </div>
                  </div>
                </td>
                <td>
                  <span className="state"><span className="dot" style={{ background: STATE_COLORS[s.state] }}/>{s.state}</span>
                </td>
                <td>
                  {s.pendingSetup || s.score == null ? (
                    <span title="No live operational data yet — connect a POS or run an alert scan to start scoring." style={{
                      fontSize: 10.5, color: 'var(--muted)',
                      fontFamily: "'JetBrains Mono', monospace",
                      letterSpacing: '.04em', textTransform: 'uppercase',
                      padding: '3px 8px', borderRadius: 4,
                      background: 'var(--bg-2)', border: '1px solid var(--hair)',
                    }}>Pending</span>
                  ) : (
                    <div className="score-cell">
                      <ScoreBar score={s.score}/>
                      <span className={`score-num ${s.score < 70 ? 'r' : s.score < 80 ? 'a' : 'g'}`}>{s.score}</span>
                      {s.grade && (
                        <span className={`score-grade ${s.score < 70 ? 'r' : s.score < 80 ? 'a' : 'g'}`}>{s.grade}</span>
                      )}
                    </div>
                  )}
                </td>
                <td>
                  <div className="sales-cell">
                    <span className="sales-amt">{s.sales}</span>
                    <Spark data={s.sparkD} color={s.salesDelta.tone === 'green' ? 'var(--green)' : 'var(--red)'} format={v => `$${v.toLocaleString()}`}/>
                    <span className={`delta ${s.salesDelta.tone}`}>{s.salesDelta.txt}</span>
                  </div>
                </td>
                <td>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    <span className="sales-amt">{s.labor}</span>
                    <span className={`delta ${s.laborDelta.tone}`}>{s.laborDelta.txt}</span>
                  </div>
                </td>
                <td>
                  <div className="alert-cell">
                    {s.alertSev && <span className={`sev-dot ${s.alertSev}`}/>}
                    <span className={`alert-count ${s.alerts === 0 ? 'zero' : ''}`}>{s.alerts}</span>
                    {s.alerts === 0 && <span style={{ fontSize: 11, color: 'var(--muted)' }}>none</span>}
                  </div>
                </td>
                <td>
                  <div className={`ts ${s.tempLog.overdue ? 'overdue' : ''}`}>
                    {s.tempLog.time}
                    <span className="sub">{s.tempLog.sub}</span>
                  </div>
                </td>
                <td>
                  <div className="audit-cell">
                    <div className="date">{s.audit.date}</div>
                    <span className={`grade ${s.audit.tone}`}>Grade {s.audit.grade}</span>
                  </div>
                </td>
                <td className="arrow-cell">→</td>
              </tr>
              {expanded === i && (
                <tr className="expand-row">
                  <td colSpan={9}><ExpandPanel row={s}/></td>
                </tr>
              )}
            </React.Fragment>
          ))}
        </tbody>
      </table>
      <div className="table-foot">
        <div className="pag">
          <span><strong>{rows.length}</strong> of <strong>{stores.length}</strong> stores</span>
          <span style={{ opacity: .6 }}>·</span>
          <span>{rows.length === stores.length ? 'showing all' : 'filtered'}</span>
        </div>
        <div className="links">
          <a>Export CSV</a>
          <a>Saved views</a>
        </div>
      </div>
    </div>
  );
}

function MapView({ filters, stores = [] }) {
  const rows = React.useMemo(() => applyStoreFilters(stores, filters), [stores, filters]);
  const containerRef = React.useRef(null);
  const mapRef = React.useRef(null);
  const layerRef = React.useRef(null);

  React.useEffect(() => {
    if (typeof window === 'undefined' || !window.L || !containerRef.current) return;
    if (mapRef.current) return; // already initialized
    const map = window.L.map(containerRef.current, {
      zoomControl: true,
      attributionControl: true,
      preferCanvas: false,
    });
    window.L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      maxZoom: 18,
      attribution: '© OpenStreetMap contributors',
    }).addTo(map);
    mapRef.current = map;
    return () => {
      map.remove();
      mapRef.current = null;
      layerRef.current = null;
    };
  }, []);

  React.useEffect(() => {
    const map = mapRef.current;
    if (!map || !window.L) return;
    if (layerRef.current) {
      layerRef.current.remove();
      layerRef.current = null;
    }
    if (rows.length === 0) return;

    const group = window.L.layerGroup().addTo(map);
    const bounds = window.L.latLngBounds([]);

    rows.forEach((s) => {
      if (s.lat == null || s.lng == null) return;
      // Pending stores (no score yet) get a neutral marker tone.
      const tone = s.score == null ? 'p'
        : s.score < 70 ? 'r' : s.score < 80 ? 'a' : 'g';
      const numLabel = s.num.replace('#', '');
      const icon = window.L.divIcon({
        html: `<div class="map-marker ${tone}">${numLabel}</div>`,
        className: '',
        iconSize: [28, 28],
        iconAnchor: [14, 14],
        popupAnchor: [0, -14],
      });
      const m = window.L.marker([s.lat, s.lng], { icon }).addTo(group);
      const detailHref = `/stores/${numLabel}`;
      m.bindPopup(`
        <div class="map-pop-title">${s.num} · ${s.name}</div>
        <div class="map-pop-meta">${s.city}</div>
        <div class="map-pop-row"><span class="lbl">Composite</span><span>${s.score == null ? 'Pending' : s.score}</span></div>
        <div class="map-pop-row"><span class="lbl">Sales today</span><span>${s.sales}</span></div>
        <div class="map-pop-row"><span class="lbl">Labor</span><span>${s.labor}</span></div>
        <div class="map-pop-row"><span class="lbl">Open alerts</span><span>${s.alerts}</span></div>
        <a class="map-pop-link" href="${detailHref}">Open store detail →</a>
      `);
      bounds.extend([s.lat, s.lng]);
    });

    layerRef.current = group;
    if (bounds.isValid()) {
      map.fitBounds(bounds, { padding: [40, 40], maxZoom: 11 });
    }
  }, [rows]);

  return (
    <div className="map-wrap">
      <div ref={containerRef} className="map-canvas"/>
      {rows.length === 0 && (
        <div className="map-empty">No stores match the current filters.</div>
      )}
    </div>
  );
}

function App() {
  const [filters, setFilters] = React.useState({ state: 'all', brand: 'all', health: 'any', sort: 'composite' });
  const [view, setView] = React.useState('list');
  // Single fetch shared by Filters (chip counts), StoresTable, and MapView.
  const [stores, setStores] = React.useState([]);
  const [loaded, setLoaded] = React.useState(false);
  const [scanStatus, setScanStatus] = React.useState(null);
  const [scanning, setScanning] = React.useState(false);

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

  const refresh = React.useCallback(() => {
    return fetch(`${STORES_API_BASE}/stores/list`, { cache: 'no-store' })
      .then(r => r.ok ? r.json() : [])
      .then(rows => Array.isArray(rows) ? rows.map(rowFromBackend) : [])
      .then(rows => { setStores(rows); setLoaded(true); })
      .catch(() => { setStores([]); setLoaded(true); });
  }, []);

  const onRescan = React.useCallback(async () => {
    setScanning(true);
    try {
      const r = await fetch(`${STORES_API_BASE}/alerts/scan?refresh=true`, { method: 'POST' });
      if (!r.ok) throw new Error(String(r.status));
      // Fresh scan changes alert counts → composite scores → store rows.
      // Re-pull both /stores/list and /alerts/scan/status.
      await Promise.all([refresh(), refreshScanStatus()]);
      // Notify other tabs/components that subscribe to alert changes.
      if (window.MISE && window.MISE.notifyAlerts) window.MISE.notifyAlerts();
    } catch (e) {
      // Surface the failure status the next time scanStatus refreshes.
      await refreshScanStatus();
    } finally {
      setScanning(false);
    }
  }, [refresh, refreshScanStatus]);

  React.useEffect(() => {
    refresh();
    refreshScanStatus();
    if (window.MISE && window.MISE.subscribeAlerts) {
      return window.MISE.subscribeAlerts(() => { refresh(); refreshScanStatus(); });
    }
  }, [refresh, refreshScanStatus]);

  return (
    <div className="shell">
      <Sidebar/>
      <div>
        <Topbar storeCount={stores.length} scanStatus={scanStatus} onRescan={onRescan} scanning={scanning}/>
        <Filters filters={filters} setFilters={setFilters} view={view} setView={setView} stores={stores} onStoreAdded={refresh}/>
        <main className="main">
          {view === 'map'
            ? <MapView filters={filters} stores={stores}/>
            : <StoresTable filters={filters} stores={stores} loaded={loaded}/>}
        </main>
      </div>
    </div>
  );
}

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