// ═══════════════════════════════════════════════════════════════
// inventory.jsx — the Stock Room screens (Inventory Step 2)
// ═══════════════════════════════════════════════════════════════
//
// WHAT THIS IS (in plain English):
// Everything you see under the "Stock Room" item in the sidebar lives in
// this file: the product catalog, preference cards, and the inventory
// settings (procedures with their meds/equipment lists, manufacturers,
// suppliers, shelves, units, kits, IOL families, CSV import).
//
// It is a second script next to public/index.html, compiled in the browser
// by the same Babel that compiles the main app, and it reuses the main
// app's styles (cards, buttons, tables) so it feels like one program.
// index.html loads this file BEFORE its own script, then renders
// <StockRoomView/> when the sidebar item is chosen.
//
// Where the data lives: the server's inventory drawers (see inventory.js).
// Each drawer is loaded with a version number and saved back with that
// number; if someone else saved first, the server refuses (409) and we
// reload their copy and say so, instead of overwriting them.
//
// Seeding: the very first time anyone opens the Stock Room on a fresh
// server, the catalog is empty. We then load /inventory-seed.json —
// Heather's master lists extracted from her app (deploy/extract-seed.js)
// — into every drawer, once. After that it is live data.
//
// Quantities (stock on hand, receiving, adjustments, alerts, reports) live in
// inventory-stock.jsx (Step 3). The patient chart's Dispensary is Step 4; the
// chart's own med/equipment lists are untouched until then.
// ═══════════════════════════════════════════════════════════════

const InvReact = React;
const { useState: invUseState, useEffect: invUseEffect, useMemo: invUseMemo, useRef: invUseRef, useCallback: invUseCallback } = React;

// ─── Talking to the server ─────────────────────────────────────
const InvApi = {
  headers() { return { 'Content-Type': 'application/json', 'X-HIPAA-Session': 'active' }; },
  async get(store) {
    const r = await fetch(`/api/inventory/store/${store}`, { headers: this.headers() });
    if (!r.ok) throw new Error(`Could not load ${store} (${r.status})`);
    return r.json(); // { data, version, updatedAt, updatedBy }
  },
  // Returns { ok:true, version } or { ok:false, conflict:true, current } or throws.
  async save(store, data, version) {
    const r = await fetch(`/api/inventory/store/${store}`, { method: 'PUT', headers: this.headers(), body: JSON.stringify({ data, version }) });
    const body = await r.json().catch(() => ({}));
    if (r.status === 409) return { ok: false, conflict: true, current: body.current };
    if (!r.ok) throw new Error(body.error || `Could not save ${store} (${r.status})`);
    return { ok: true, version: body.version };
  },
  audit(action, detail) { try { if (window.HIPAA && HIPAA.auditLog) HIPAA.auditLog(action, detail); } catch {} },
};

// One drawer, held in React state with its version. save(next) writes it back;
// on a conflict the other person's copy replaces ours and the caller is told.
function useInvStore(name, fallback) {
  const [state, setState] = invUseState({ data: null, version: 0, loading: true, error: null });
  const load = invUseCallback(async () => {
    try { const d = await InvApi.get(name); setState({ data: d.data == null ? fallback : d.data, version: d.version, loading: false, error: null, updatedAt: d.updatedAt, updatedBy: d.updatedBy }); }
    catch (e) { setState(s => ({ ...s, loading: false, error: e.message })); }
  }, [name]);
  invUseEffect(() => { load(); }, [load]);
  const save = invUseCallback(async (next) => {
    const r = await InvApi.save(name, next, state.version);
    if (r.ok) { setState(s => ({ ...s, data: next, version: r.version })); return { ok: true }; }
    if (r.conflict) { setState(s => ({ ...s, data: r.current.data == null ? fallback : r.current.data, version: r.current.version })); return { ok: false, conflict: true, by: r.current.updatedBy }; }
    return { ok: false };
  }, [name, state.version]);
  return { ...state, save, reload: load };
}

const invId = (prefix) => prefix + Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
const invNorm = (s) => String(s == null ? '' : s).toLowerCase().replace(/\s+/g, ' ').trim();
const invMoney = (n) => (n == null || n === '' || isNaN(n)) ? '—' : '$' + Number(n).toFixed(2);
const INV_GROUPS = ['IOL', 'Surgery Supplies', 'Refractive supplies', 'Clinic Supplies', 'Crash Cart', 'Cleaning', 'Other'];
const INV_PROC_KEYS = [['cataract', 'Cataract'], ['rle', 'RLE'], ['icl', 'ICL'], ['lasik', 'LASIK'], ['smile', 'SMILE'], ['prk', 'PRK']];

// ─── Barcode brain (GS1) ───────────────────────────────────────
// A lens box or drug box barcode packs several facts into one string:
// (01) the product code, (17) expiry YYMMDD, (10) lot, (21) serial. Scanners
// "type" the whole thing at once. This pulls the pieces apart. Ported from
// Heather's parseGS1 so the same boxes scan the same way.
function invParseGS1(raw) {
  let b = String(raw || '').replace(/[\u200b\u200c\u200d\ufeff]/g, '').replace(/[\r\n\t ]/g, '');
  b = b.replace(/<GS>/gi, '\x1d').replace(/\u241d/g, '\x1d').replace(/^\][A-Za-z]\d/, '');
  const GS = '\x1d';
  const fixed = { '00': 18, '01': 14, '02': 14, '11': 6, '12': 6, '13': 6, '15': 6, '16': 6, '17': 6, '20': 2 };
  const variable = new Set(['10', '21', '22', '30', '37', '90', '91', '92', '240', '241', '250', '251', '400', '8004']);
  const out = { raw: String(raw || ''), gtin: '', lot: '', serial: '', expiry: '', other: {} };
  if (/^\d{8,14}$/.test(b)) { out.gtin = b; return out; } // a plain UPC/EAN
  let i = 0, found = false;
  const yymmdd = (v) => { if (!/^\d{6}$/.test(v)) return ''; const dd = v.slice(4, 6) === '00' ? '01' : v.slice(4, 6); return `20${v.slice(0, 2)}-${v.slice(2, 4)}-${dd}`; };
  while (i < b.length) {
    if (b[i] === GS) { i++; continue; }
    let ai = null;
    for (const len of [4, 3, 2]) { const c = b.slice(i, i + len); if (c.length === len && (fixed[c] !== undefined || variable.has(c))) { ai = c; break; } }
    if (!ai) break;
    i += ai.length;
    let val;
    if (fixed[ai] !== undefined) { val = b.slice(i, i + fixed[ai]); i += fixed[ai]; }
    else { const e = b.indexOf(GS, i); val = e < 0 ? b.slice(i) : b.slice(i, e); i = e < 0 ? b.length : e; }
    found = true;
    if (ai === '01' || ai === '02') out.gtin = val;
    else if (ai === '10') out.lot = val;
    else if (ai === '21') out.serial = val;
    else if (ai === '17') out.expiry = yymmdd(val);
    else out.other[ai] = val;
  }
  if (!found) out.gtin = b.replace(/\D/g, '');
  return out;
}
// Match a scan to a product: by GTIN/UPC (ignoring leading zeros), then by manufacturer code.
function invFindByScan(products, raw) {
  const s = invParseGS1(raw);
  const digits = (v) => String(v || '').replace(/\D/g, '').replace(/^0+/, '');
  const g = digits(s.gtin);
  let hit = null;
  if (g) hit = products.find(p => digits(p.upc) && digits(p.upc) === g) || products.find(p => digits(p.upc) && (g.endsWith(digits(p.upc)) || digits(p.upc).endsWith(g)) && Math.min(g.length, digits(p.upc).length) >= 11);
  if (!hit) { const code = invNorm(raw); hit = products.find(p => invNorm(p.manufacturerCode) === code || invNorm(p.upc) === code); }
  return { product: hit || null, scan: s };
}

// ─── Small shared pieces ───────────────────────────────────────
function InvModal({ title, onClose, children, wide }) {
  return (
    <div className="modal-overlay" onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="modal" style={{ maxWidth: wide ? 900 : 560, maxHeight: '90vh', overflow: 'auto' }}>
        <h3 style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>{title}<button className="btn btn-outline btn-sm" onClick={onClose}>✕</button></h3>
        {children}
      </div>
    </div>
  );
}
function InvField({ label, children, span }) {
  return <div className="field-group" style={span ? { gridColumn: `span ${span}` } : undefined}><label className="field-label">{label}</label>{children}</div>;
}
const invCell = { padding: '8px 8px', borderBottom: '1px solid var(--gray-200)', verticalAlign: 'middle', fontSize: '0.84rem' };
const invTh = { ...invCell, fontSize: '0.7rem', color: 'var(--gray-500)', textTransform: 'uppercase', textAlign: 'left', background: 'var(--gray-50)', position: 'sticky', top: 0 };
function InvTag({ children, color }) {
  const c = color || 'var(--gray-500)';
  return <span style={{ display: 'inline-block', fontSize: '0.66rem', fontWeight: 700, padding: '2px 6px', borderRadius: 6, border: `1px solid ${c}`, color: c, marginRight: 4, whiteSpace: 'nowrap' }}>{children}</span>;
}
function InvTabs({ tabs, value, onChange }) {
  return (
    <div style={{ display: 'flex', gap: 4, borderBottom: '2px solid var(--gray-200)', marginBottom: 14, flexWrap: 'wrap' }}>
      {tabs.map(t => (
        <button key={t.id} onClick={() => onChange(t.id)} style={{ background: 'none', border: 'none', borderBottom: value === t.id ? '3px solid var(--teal)' : '3px solid transparent', marginBottom: -2, padding: '8px 14px', fontFamily: 'inherit', fontSize: '0.86rem', fontWeight: 600, color: value === t.id ? 'var(--navy)' : 'var(--gray-500)', cursor: 'pointer' }}>
          {t.label}{t.badge ? <span className="nav-badge" style={{ marginLeft: 6 }}>{t.badge}</span> : null}
        </button>
      ))}
    </div>
  );
}
// Type-to-search over the catalog; onPick(product). Also accepts a scanned barcode.
function InvProductPicker({ products, onPick, placeholder, autoFocus, iolOnly }) {
  const [q, setQ] = invUseState('');
  const list = invUseMemo(() => {
    const n = invNorm(q); if (n.length < 2) return [];
    const words = n.split(' ');
    return products.filter(p => p.active !== false && (!iolOnly || p.isIol) && words.every(w => invNorm(p.description).includes(w) || invNorm(p.manufacturerCode).includes(w) || invNorm(p.upc).includes(w))).slice(0, 12);
  }, [q, products, iolOnly]);
  const pick = (p, scan) => { onPick(p, scan || null); setQ(''); };
  const onKey = (e) => {
    if (e.key === 'Enter') { e.preventDefault(); const f = invFindByScan(products, q); if (f.product) pick(f.product, f.scan); else if (list.length === 1) pick(list[0]); }
  };
  return (
    <div style={{ position: 'relative' }}>
      <input type="text" value={q} autoFocus={autoFocus} onChange={e => setQ(e.target.value)} onKeyDown={onKey} placeholder={placeholder || 'Search products or scan a barcode…'} />
      {list.length > 0 && (
        <div style={{ position: 'absolute', zIndex: 20, left: 0, right: 0, background: 'white', border: '1px solid var(--gray-300)', borderRadius: 8, boxShadow: '0 8px 20px rgba(0,0,0,.12)', maxHeight: 280, overflow: 'auto' }}>
          {list.map(p => (
            <div key={p.id} onMouseDown={() => pick(p)} style={{ padding: '8px 10px', cursor: 'pointer', borderBottom: '1px solid var(--gray-100)', fontSize: '0.84rem' }}>
              <div style={{ fontWeight: 600 }}>{p.description}</div>
              <div style={{ color: 'var(--gray-500)', fontSize: '0.74rem' }}>{p.manufacturerCode}{p.manufacturer ? ' · ' + p.manufacturer : ''} · {p.dispenseUom}{p.upc ? ' · UPC ' + p.upc : ''}</div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// ─── Seeding from Heather's master lists ───────────────────────
async function invSeedFromFile(onProgress) {
  const r = await fetch('/inventory-seed.json', { cache: 'no-store' });
  if (!r.ok) throw new Error('inventory-seed.json is missing on the server');
  const seed = await r.json();
  const writes = [
    ['products', seed.products],
    ['manufacturers', seed.manufacturers],
    ['suppliers', seed.suppliers],
    ['shelves', seed.shelves],
    ['uoms', seed.uoms],
    ['procedures', { list: seed.procedures, config: seed.procedureConfig, nonCardEquipment: seed.nonCardEquipment }],
    ['prefcards', seed.prefcards],
    ['kits', seed.kits || []],
    ['settings', { ...seed.settings, seedVersion: seed.seedVersion, seedSource: seed.source, seededAt: new Date().toISOString() }],
  ];
  for (const [store, data] of writes) {
    onProgress && onProgress(store);
    const cur = await InvApi.get(store);
    if (cur.version && cur.data && (Array.isArray(cur.data) ? cur.data.length : Object.keys(cur.data).length)) continue; // never overwrite live data
    const res = await InvApi.save(store, data, cur.version || 0);
    if (!res.ok) throw new Error(`Could not seed ${store}`);
  }
  InvApi.audit('INV_SEED', `Loaded master lists from ${seed.source}: ${seed.products.length} products, ${seed.prefcards.length} cards`);
  return seed;
}

// ═══════════════════════════════════════════════════════════════
// THE STOCK ROOM
// ═══════════════════════════════════════════════════════════════
function StockRoomView({ me, onToast }) {
  const [tab, setTab] = invUseState(() => { try { return localStorage.getItem('inv_tab') || 'inventory'; } catch { return 'inventory'; } });
  invUseEffect(() => { try { localStorage.setItem('inv_tab', tab); } catch {} }, [tab]);
  const toast = (m, kind) => onToast && onToast(m, kind || 'success');

  const products = useInvStore('products', []);
  const manufacturers = useInvStore('manufacturers', []);
  const suppliers = useInvStore('suppliers', []);
  const shelves = useInvStore('shelves', []);
  const uoms = useInvStore('uoms', []);
  const procedures = useInvStore('procedures', { list: [], config: {}, nonCardEquipment: [] });
  const prefcards = useInvStore('prefcards', []);
  const kits = useInvStore('kits', []);
  const settings = useInvStore('settings', {});
  const purchase_orders = useInvStore('purchase_orders', []);
  const stores = { products, manufacturers, suppliers, shelves, uoms, procedures, prefcards, kits, settings, purchase_orders };
  const onHand = useInvOnHand();
  const { alerts, reload: refreshAlerts } = useInvAlerts();
  const refreshAll = () => { onHand.reload(); refreshAlerts(); if (window.__invBadgeRefresh) window.__invBadgeRefresh(); };
  window.__invSettings = settings.data || {};
  const anyLoading = Object.values(stores).some(s => s.loading);
  const firstError = Object.values(stores).map(s => s.error).find(Boolean);

  // Empty catalog on a fresh server → offer the one-time seed.
  const [seeding, setSeeding] = invUseState(null);
  const needsSeed = !anyLoading && !firstError && products.version === 0 && (!products.data || products.data.length === 0);
  const runSeed = async () => {
    setSeeding('Starting…');
    try {
      await invSeedFromFile(store => setSeeding(`Loading ${store}…`));
      await Promise.all(Object.values(stores).map(s => s.reload()));
      toast("Heather's master lists are loaded. This is now the live catalog.");
    } catch (e) { toast('⚠️ ' + e.message, 'error'); }
    setSeeding(null);
  };

  const savedOrConflict = async (store, next, what) => {
    const r = await store.save(next);
    if (r.ok) { if (what) toast(what); return true; }
    if (r.conflict) toast(`⚠️ ${r.by || 'Someone else'} saved this just before you. Their copy has been reloaded — please redo your change.`, 'error');
    else toast('⚠️ Could not save — check the connection and try again.', 'error');
    return false;
  };

  const tabs = [
    { id: 'dashboard', label: 'Dashboard', badge: alerts && alerts.counts.total ? alerts.counts.total : null },
    { id: 'inventory', label: 'Inventory', badge: products.data ? products.data.filter(p => p.active !== false).length : null },
    { id: 'po', label: 'Purchase Orders', badge: purchase_orders.data ? (purchase_orders.data.filter(p => p.status === 'placed' || p.status === 'partial').length || null) : null },
    { id: 'prefcards', label: 'Preference Cards', badge: prefcards.data ? prefcards.data.filter(c => c.active !== false).length : null },
    { id: 'reports', label: 'Reports' },
    { id: 'settings', label: 'Settings' },
  ];

  if (firstError) return <div className="card"><div className="card-body" style={{ color: 'var(--red)' }}>⚠️ {firstError}</div></div>;
  if (anyLoading) return <div style={{ color: 'var(--gray-500)', padding: 20 }}>Opening the Stock Room…</div>;

  if (needsSeed) return (
    <div className="card" style={{ maxWidth: 640 }}>
      <div className="card-header">First-time setup</div>
      <div className="card-body">
        <p style={{ fontSize: '0.9rem', lineHeight: 1.5 }}>The catalog on this server is empty. Load Heather's master lists — about 1,250 products (965 lenses), 12 preference cards, 15 procedures, and the medication and equipment lists for each procedure — as the starting point. This happens once; after that everything is edited here.</p>
        {seeding ? <div style={{ color: 'var(--teal)', fontWeight: 600 }}>{seeding}</div>
          : <button className="btn btn-primary" onClick={runSeed}>Load Heather's master lists</button>}
      </div>
    </div>
  );

  const ctx = { stores, savedOrConflict, toast, me, setTab, onHand, alerts, refreshAll, refreshAlerts };
  return (
    <div>
      <InvTabs tabs={tabs} value={tab} onChange={setTab} />
      {tab === 'dashboard' && <InvDashboardV1 {...ctx} />}
      {tab === 'inventory' && <InvCatalog {...ctx} />}
      {tab === 'po' && <InvPurchaseOrders {...ctx} />}
      {tab === 'prefcards' && <InvPrefCards {...ctx} />}
      {tab === 'reports' && <InvReports {...ctx} />}
      {tab === 'settings' && <InvSettings {...ctx} />}
    </div>
  );
}
function InvPlaceholder({ title, step, text }) {
  return <div className="card" style={{ maxWidth: 640 }}><div className="card-header">{title}</div><div className="card-body" style={{ color: 'var(--gray-500)', fontSize: '0.88rem' }}>Arrives in Step {step} of the inventory build. {text}</div></div>;
}

// ─── Dashboard (counts only until stock arrives in Step 3) ──────
function InvDashboard({ stores, setTab }) {
  const P = stores.products.data || [], C = stores.prefcards.data || [], PR = (stores.procedures.data || {}).list || [];
  const s = stores.settings.data || {};
  const tile = (label, value, sub) => (
    <div className="card" style={{ margin: 0 }}><div className="card-body"><div style={{ fontSize: '0.7rem', textTransform: 'uppercase', color: 'var(--gray-500)', fontWeight: 600 }}>{label}</div><div style={{ fontSize: '1.7rem', fontWeight: 700, color: 'var(--navy)' }}>{value}</div>{sub && <div style={{ fontSize: '0.76rem', color: 'var(--gray-500)' }}>{sub}</div>}</div></div>
  );
  return (
    <div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 12, marginBottom: 14 }}>
        {tile('Products in catalog', P.filter(p => p.active !== false).length, `${P.filter(p => p.isIol && p.active !== false).length} lenses`)}
        {tile('Preference cards', C.filter(c => c.active !== false).length, `${C.length - C.filter(c => c.active !== false).length} inactive`)}
        {tile('Procedures', PR.length)}
        {tile('Stock on hand', '—', 'arrives in Step 3')}
        {tile('Alerts', '—', 'expiry · low PAR · price changes, Step 3')}
      </div>
      <div className="card"><div className="card-header">Where the data came from</div><div className="card-body" style={{ fontSize: '0.86rem', color: 'var(--gray-600)' }}>
        {s.seedSource ? <>Master lists loaded {s.seededAt ? new Date(s.seededAt).toLocaleString() : ''} from <em>{s.seedSource}</em>. Everything is now edited here; Heather's file is no longer the source.</> : 'Catalog entered by hand.'}
        <div style={{ marginTop: 8 }}><button className="btn btn-outline btn-sm" onClick={() => setTab('inventory')}>Open the catalog →</button></div>
      </div></div>
    </div>
  );
}

// ─── Catalog ───────────────────────────────────────────────────
function InvCatalog({ stores, savedOrConflict, toast, onHand, refreshAll }) {
  const P = stores.products.data || [];
  const [mode, setMode] = invUseState('products'); // 'products' | 'lenses'
  const [receiving, setReceiving] = invUseState(null); // null | { product }
  const [adjusting, setAdjusting] = invUseState(null); // null | { product, lot }
  const [q, setQ] = invUseState('');
  const [group, setGroup] = invUseState('all');
  const [showArchived, setShowArchived] = invUseState(false);
  const [limit, setLimit] = invUseState(150);
  const [editing, setEditing] = invUseState(null); // product object or 'new'
  const [scanMsg, setScanMsg] = invUseState('');

  const list = invUseMemo(() => {
    const n = invNorm(q); const words = n ? n.split(' ') : [];
    return P.filter(p => (showArchived ? p.active === false : p.active !== false))
      .filter(p => group === 'all' || p.group === group)
      .filter(p => !words.length || words.every(w => invNorm(p.description).includes(w) || invNorm(p.manufacturerCode).includes(w) || invNorm(p.manufacturer).includes(w) || invNorm(p.upc).includes(w) || invNorm(p.supplier).includes(w)))
      .sort((a, b) => a.description.localeCompare(b.description));
  }, [P, q, group, showArchived]);

  const onSearchKey = (e) => {
    if (e.key !== 'Enter') return;
    e.preventDefault(); // a scanner ends with Enter; without this the keypress lands in the modal's form and submits it
    const { product, scan } = invFindByScan(P, q);
    if (product) { setEditing(product); setScanMsg(''); }
    else if (scan.gtin && scan.gtin.length >= 8) setScanMsg(`No product has barcode ${scan.gtin}. Add it, then paste the barcode into the UPC field.`);
  };

  const saveProduct = async (prod) => {
    const isNew = !P.some(p => p.id === prod.id);
    const dup = P.find(p => p.id !== prod.id && invNorm(p.description) === invNorm(prod.description));
    if (dup) { toast(`⚠️ "${dup.description}" already exists${dup.active === false ? ' (archived)' : ''}.`, 'error'); return; }
    const next = isNew ? [...P, prod] : P.map(p => p.id === prod.id ? prod : p);
    if (await savedOrConflict(stores.products, next, isNew ? 'Product added' : 'Product saved')) setEditing(null);
  };
  const setActive = async (prod, active) => {
    const next = P.map(p => p.id === prod.id ? { ...p, active } : p);
    if (await savedOrConflict(stores.products, next, active ? 'Product restored' : 'Product archived')) setEditing(null);
  };

  const counts = invUseMemo(() => { const c = { all: 0 }; for (const p of P) { if (p.active === false) continue; c.all++; c[p.group] = (c[p.group] || 0) + 1; } return c; }, [P]);
  const chip = (id, label) => <button key={id} className={`btn btn-sm ${group === id ? 'btn-teal' : 'btn-outline'}`} onClick={() => { setGroup(id); setLimit(150); }}>{label}{counts[id] != null ? ` (${counts[id]})` : ''}</button>;

  return (
    <div>
      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginBottom: 10 }}>
        <div style={{ display: 'flex', gap: 4 }}>
          <button className={`btn btn-sm ${mode === 'products' ? 'btn-teal' : 'btn-outline'}`} onClick={() => setMode('products')}>Products</button>
          <button className={`btn btn-sm ${mode === 'lenses' ? 'btn-teal' : 'btn-outline'}`} onClick={() => setMode('lenses')}>Lenses on hand</button>
        </div>
        <div style={{ flex: '1 1 320px' }}><input type="text" value={q} onChange={e => { setQ(e.target.value); setScanMsg(''); setLimit(150); }} onKeyDown={onSearchKey} placeholder="Search description, code, UPC, manufacturer — or scan a barcode and press Enter" /></div>
        <button className="btn btn-teal" onClick={() => setReceiving({ product: null })}>📦 Receive delivery</button>
        <button className="btn btn-primary" onClick={() => setEditing('new')}>+ Add Product</button>
        <label style={{ fontSize: '0.8rem', color: 'var(--gray-600)', display: 'flex', gap: 6, alignItems: 'center' }}><input type="checkbox" checked={showArchived} onChange={e => setShowArchived(e.target.checked)} /> Show archived</label>
      </div>
      {scanMsg && <div style={{ background: 'var(--amber-light)', color: 'var(--amber)', padding: '8px 12px', borderRadius: 8, fontSize: '0.84rem', marginBottom: 10 }}>{scanMsg}</div>}
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 12 }}>{chip('all', 'All')}{INV_GROUPS.map(g => chip(g, g))}</div>

      {mode === 'lenses' ? <InvLensesOnHand products={P} onHand={onHand} onOpen={p => setEditing(p)} /> : (
      <div className="card" style={{ margin: 0 }}>
        <div style={{ overflow: 'auto', maxHeight: 'calc(100vh - 330px)' }}>
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead><tr>
              <th style={invTh}>Description</th><th style={invTh}>Mfr code</th><th style={invTh}>Manufacturer</th><th style={invTh}>Supplier</th><th style={invTh}>Group · shelf</th><th style={invTh}>Buy / dispense</th><th style={{ ...invTh, textAlign: 'right' }}>In stock</th><th style={{ ...invTh, textAlign: 'right' }}>Price</th><th style={{ ...invTh, textAlign: 'right' }}>PAR</th><th style={invTh}>Flags</th>
            </tr></thead>
            <tbody>
              {list.slice(0, limit).map(p => (
                <tr key={p.id} onClick={() => setEditing(p)} style={{ cursor: 'pointer' }} onMouseEnter={e => e.currentTarget.style.background = 'var(--gray-50)'} onMouseLeave={e => e.currentTarget.style.background = ''}>
                  <td style={invCell}><strong>{p.description}</strong>{p.upc ? <div style={{ fontSize: '0.7rem', color: 'var(--gray-500)' }}>UPC {p.upc}</div> : null}</td>
                  <td style={invCell}>{p.manufacturerCode}</td>
                  <td style={invCell}>{p.manufacturer}</td>
                  <td style={invCell}>{p.supplier}</td>
                  <td style={invCell}>{p.group}<div style={{ fontSize: '0.7rem', color: 'var(--gray-500)' }}>{p.shelf}</div></td>
                  <td style={invCell}>{p.purchaseUom}{p.unitsPerPurchase > 1 ? ` of ${p.unitsPerPurchase}` : ''} → {p.dispenseUom}</td>
                  <td style={{ ...invCell, textAlign: 'right' }}>{(() => { const b = onHand.products[p.id]; if (!b) return <span style={{ color: 'var(--gray-400)' }}>0</span>; const low = p.parMin > 0 && b.available < p.parMin; return <span style={{ fontWeight: 600, color: low ? 'var(--amber)' : b.onHand <= 0 ? 'var(--gray-400)' : 'inherit' }}>{invFmtQty(b.available)}{b.reserved ? <span style={{ fontWeight: 400, color: 'var(--gray-500)', fontSize: '0.72rem' }}> +{invFmtQty(b.reserved)} res.</span> : null}</span>; })()}</td>
                  <td style={{ ...invCell, textAlign: 'right' }}>{invMoney(p.purchasePrice)}</td>
                  <td style={{ ...invCell, textAlign: 'right' }}>{p.parMin || '—'}</td>
                  <td style={invCell}>{p.isIol && <InvTag color="var(--teal)">IOL</InvTag>}{p.isDrug && <InvTag>Drug</InvTag>}{p.isControlled && <InvTag color="var(--red)">Controlled</InvTag>}{p.expiryRequired && <InvTag color="var(--amber)">Expiry</InvTag>}{p.isBuyAndBill && <InvTag>Buy &amp; bill</InvTag>}</td>
                </tr>
              ))}
              {list.length === 0 && <tr><td colSpan={10} style={{ ...invCell, color: 'var(--gray-500)', textAlign: 'center', padding: 30 }}>Nothing matches.</td></tr>}
            </tbody>
          </table>
        </div>
        <div style={{ padding: '8px 12px', fontSize: '0.78rem', color: 'var(--gray-500)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <span>Showing {Math.min(limit, list.length)} of {list.length}</span>
          {list.length > limit && <button className="btn btn-outline btn-sm" onClick={() => setLimit(l => l + 300)}>Show more</button>}
        </div>
      </div>)}

      {editing && <InvProductModal product={editing === 'new' ? null : editing} lists={stores} onSave={saveProduct} onArchive={setActive} onClose={() => setEditing(null)}
        stockRows={editing !== 'new' && <InvStockRows product={editing} onHand={onHand} onReceive={p => setReceiving({ product: p })} onAdjust={(p, lot) => setAdjusting({ product: p, lot })} />} />}
      {receiving && <InvReceiveModal products={P} shelves={stores.shelves.data} initialProduct={receiving.product} toast={toast} onClose={() => setReceiving(null)} onDone={() => { setReceiving(null); refreshAll(); }} />}
      {adjusting && <InvAdjustModal product={adjusting.product} lot={adjusting.lot} toast={toast} onClose={() => setAdjusting(null)} onDone={() => { setAdjusting(null); refreshAll(); }} />}
    </div>
  );
}

function InvProductModal({ product, lists, onSave, onArchive, onClose, stockRows }) {
  const blank = { id: invId('P'), description: '', manufacturerCode: '', manufacturer: '', group: 'Surgery Supplies', shelf: 'OR', supplier: '', purchaseUom: 'Box', dispenseUom: 'Each', unitsPerPurchase: 1, purchasePrice: 0, retailPrice: 0, upc: '', isIol: false, iolModel: '', iolPower: '', expiryRequired: false, isDrug: false, isControlled: false, isBuyAndBill: false, parMin: 0, active: true, notes: '' };
  const [f, setF] = invUseState(product ? { ...blank, ...product } : blank);
  const set = (k) => (e) => setF(s => ({ ...s, [k]: e.target.type === 'checkbox' ? e.target.checked : e.target.value }));
  const names = (store) => ((lists[store].data || []).filter(x => x.active !== false).map(x => x.name));
  const opts = (store, current) => { const n = names(store); if (current && !n.includes(current)) n.unshift(current); return ['', ...n]; };
  const submit = (e) => {
    e.preventDefault();
    if (!f.description.trim()) return;
    const out = { ...f, description: f.description.trim(), unitsPerPurchase: Number(f.unitsPerPurchase) || 1, purchasePrice: Number(f.purchasePrice) || 0, retailPrice: Number(f.retailPrice) || 0, parMin: Number(f.parMin) || 0, upc: String(f.upc || '').replace(/\s/g, '') };
    if (out.isIol && !out.iolModel) { const m = out.description.match(/^(.*?)\s+([+-]?\d+(?:\.\d+)?(?:\/[+-]?\d+(?:\.\d+)?)?)\s*$/); if (m) { out.iolModel = m[1]; out.iolPower = m[2]; } }
    onSave(out);
  };
  const sel = (k, store) => <select value={f[k] || ''} onChange={set(k)}>{opts(store, f[k]).map(n => <option key={n} value={n}>{n || '—'}</option>)}</select>;
  return (
    <InvModal title={product ? 'Edit product' : 'Add product'} onClose={onClose} wide>
      <form onSubmit={submit}>
        <div className="field-row c3">
          <InvField label="Description" span={2}><input type="text" value={f.description} onChange={set('description')} autoFocus required /></InvField>
          <InvField label="Group"><select value={f.group} onChange={set('group')}>{INV_GROUPS.map(g => <option key={g}>{g}</option>)}</select></InvField>
        </div>
        <div className="field-row c3">
          <InvField label="Manufacturer code / catalog #"><input type="text" value={f.manufacturerCode} onChange={set('manufacturerCode')} /></InvField>
          <InvField label="Manufacturer">{sel('manufacturer', 'manufacturers')}</InvField>
          <InvField label="Default supplier">{sel('supplier', 'suppliers')}</InvField>
        </div>
        <div className="field-row c4">
          <InvField label="Buy in">{sel('purchaseUom', 'uoms')}</InvField>
          <InvField label="Units per purchase"><input type="number" min="1" value={f.unitsPerPurchase} onChange={set('unitsPerPurchase')} /></InvField>
          <InvField label="Dispense in">{sel('dispenseUom', 'uoms')}</InvField>
          <InvField label="Shelf">{sel('shelf', 'shelves')}</InvField>
        </div>
        <div className="field-row c4">
          <InvField label="Purchase price (per buy unit)"><input type="number" step="0.01" min="0" value={f.purchasePrice} onChange={set('purchasePrice')} /></InvField>
          <InvField label="Retail price"><input type="number" step="0.01" min="0" value={f.retailPrice} onChange={set('retailPrice')} /></InvField>
          <InvField label="PAR minimum"><input type="number" min="0" value={f.parMin} onChange={set('parMin')} /></InvField>
          <InvField label="UPC / GTIN (scan here)"><input type="text" value={f.upc} onChange={set('upc')} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); const s = invParseGS1(e.target.value); if (s.gtin) setF(x => ({ ...x, upc: s.gtin })); } }} /></InvField>
        </div>
        <div className="field-row c2" style={{ alignItems: 'end' }}>
          <div style={{ display: 'flex', gap: 14, flexWrap: 'wrap', fontSize: '0.84rem' }}>
            {[['isIol', 'IOL / lens'], ['isDrug', 'Drug'], ['isControlled', 'Controlled substance'], ['expiryRequired', 'Expiry required'], ['isBuyAndBill', 'Buy & bill']].map(([k, l]) => (
              <label key={k} style={{ display: 'flex', gap: 5, alignItems: 'center' }}><input type="checkbox" checked={!!f[k]} onChange={set(k)} /> {l}</label>
            ))}
          </div>
          {f.isIol && <div className="field-row c2" style={{ margin: 0 }}><InvField label="Lens model"><input type="text" value={f.iolModel || ''} onChange={set('iolModel')} placeholder="e.g. Clareon PanOptix Pro PXYAT0" /></InvField><InvField label="Power"><input type="text" value={f.iolPower || ''} onChange={set('iolPower')} placeholder="+16.0" /></InvField></div>}
        </div>
        <InvField label="Notes"><textarea value={f.notes} onChange={set('notes')} rows={2} /></InvField>
        {stockRows || null}
        <div className="modal-actions" style={{ marginTop: 14, justifyContent: 'space-between' }}>
          <div>{product && (product.active === false
            ? <button type="button" className="btn btn-outline btn-sm" onClick={() => onArchive(product, true)}>Restore</button>
            : <button type="button" className="btn btn-danger btn-sm" onClick={() => onArchive(product, false)}>Archive</button>)}</div>
          <div style={{ display: 'flex', gap: 8 }}><button type="button" className="btn btn-outline" onClick={onClose}>Cancel</button><button type="submit" className="btn btn-primary">Save</button></div>
        </div>
      </form>
    </InvModal>
  );
}

// ─── Preference cards ──────────────────────────────────────────
function InvPrefCards({ stores, savedOrConflict, toast }) {
  const C = stores.prefcards.data || [], P = stores.products.data || [], PR = (stores.procedures.data || {}).list || [];
  const byId = invUseMemo(() => Object.fromEntries(P.map(p => [p.id, p])), [P]);
  const [selId, setSelId] = invUseState(null);
  const [showInactive, setShowInactive] = invUseState(false);
  const card = C.find(c => c.id === selId) || null;
  const [draft, setDraft] = invUseState(null);
  invUseEffect(() => { setDraft(card ? JSON.parse(JSON.stringify(card)) : null); }, [selId, stores.prefcards.version]);
  const dirty = draft && card && JSON.stringify(draft) !== JSON.stringify(card);

  const persist = async (next, msg) => savedOrConflict(stores.prefcards, next, msg);
  const saveDraft = async () => { if (!draft) return; if (await persist(C.map(c => c.id === draft.id ? draft : c), 'Card saved')) {} };
  const newCard = async () => { const c = { id: invId('C'), name: 'New card', specification: '', active: true, procedures: [], iolFamilies: [], items: [] }; if (await persist([...C, c], 'Card created')) setSelId(c.id); };
  const duplicate = async () => { if (!card) return; const c = { ...JSON.parse(JSON.stringify(card)), id: invId('C'), name: card.name + ' (copy)' }; if (await persist([...C, c], 'Card duplicated')) setSelId(c.id); };
  const upd = (patch) => setDraft(d => ({ ...d, ...patch }));
  const updItem = (i, patch) => setDraft(d => ({ ...d, items: d.items.map((it, k) => k === i ? { ...it, ...patch } : it) }));
  const addItem = (p) => setDraft(d => d.items.some(it => it.productId === p.id) ? d : { ...d, items: [...d.items, { productId: p.id, qty: 1, uom: p.dispenseUom, note: '', skipSameDaySecondEye: false, skipDifferentDaySecondEye: false, sort: d.items.length }] });
  const move = (i, dir) => setDraft(d => { const a = [...d.items]; const j = i + dir; if (j < 0 || j >= a.length) return d; [a[i], a[j]] = [a[j], a[i]]; return { ...d, items: a.map((it, k) => ({ ...it, sort: k })) }; });
  const famList = Object.keys(((stores.settings.data || {}).iolFamilyRules) || {});
  const list = C.filter(c => showInactive || c.active !== false).sort((a, b) => a.name.localeCompare(b.name));

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '300px 1fr', gap: 14, alignItems: 'start' }}>
      <div className="card" style={{ margin: 0 }}>
        <div className="card-header" style={{ justifyContent: 'space-between' }}>Cards <button className="btn btn-primary btn-sm" onClick={newCard}>+ New</button></div>
        <div>
          {list.map(c => (
            <div key={c.id} onClick={() => setSelId(c.id)} style={{ padding: '10px 14px', borderBottom: '1px solid var(--gray-100)', cursor: 'pointer', background: c.id === selId ? 'var(--gray-100)' : '', opacity: c.active === false ? 0.6 : 1 }}>
              <div style={{ fontWeight: 600, fontSize: '0.86rem' }}>{c.name}</div>
              <div style={{ fontSize: '0.72rem', color: 'var(--gray-500)' }}>{c.specification ? c.specification + ' · ' : ''}{c.items.length} items · {c.procedures.length} procedure{c.procedures.length === 1 ? '' : 's'}{c.active === false ? ' · inactive' : ''}</div>
            </div>
          ))}
          <label style={{ display: 'flex', gap: 6, padding: '8px 14px', fontSize: '0.76rem', color: 'var(--gray-500)' }}><input type="checkbox" checked={showInactive} onChange={e => setShowInactive(e.target.checked)} /> Show inactive</label>
        </div>
      </div>

      {!draft ? <div style={{ color: 'var(--gray-500)', padding: 30 }}>Pick a card on the left, or create one.</div> : (
        <div>
          <div className="card" style={{ margin: '0 0 12px' }}>
            <div className="card-header" style={{ justifyContent: 'space-between' }}>
              <span>Card</span>
              <span style={{ display: 'flex', gap: 6 }}>
                <button className="btn btn-outline btn-sm" onClick={duplicate}>Duplicate</button>
                <button className="btn btn-outline btn-sm" onClick={() => upd({ active: draft.active === false })}>{draft.active === false ? 'Activate' : 'Deactivate'}</button>
                <button className="btn btn-primary btn-sm" disabled={!dirty} onClick={saveDraft}>{dirty ? 'Save changes' : 'Saved'}</button>
              </span>
            </div>
            <div className="card-body">
              <div className="field-row c3">
                <InvField label="Name" span={2}><input type="text" value={draft.name} onChange={e => upd({ name: e.target.value })} /></InvField>
                <InvField label="Short label"><input type="text" value={draft.specification || ''} onChange={e => upd({ specification: e.target.value })} /></InvField>
              </div>
              <div className="field-row c2">
                <InvField label="Used for these procedures (from the worklist)">
                  <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: '0.8rem', maxHeight: 170, overflow: 'auto', border: '1px solid var(--gray-200)', borderRadius: 8, padding: 8 }}>
                    {PR.map(pr => <label key={pr.id} style={{ display: 'flex', gap: 5 }}><input type="checkbox" checked={draft.procedures.includes(pr.id)} onChange={e => upd({ procedures: e.target.checked ? [...draft.procedures, pr.id] : draft.procedures.filter(x => x !== pr.id) })} /> {pr.name}</label>)}
                  </div>
                </InvField>
                <InvField label="Only when the lens is one of these families (blank = any lens)">
                  <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, fontSize: '0.8rem', padding: 8, border: '1px solid var(--gray-200)', borderRadius: 8 }}>
                    {famList.length === 0 && <span style={{ color: 'var(--gray-500)' }}>No lens families defined yet (Settings → IOL families).</span>}
                    {famList.map(fam => <label key={fam} style={{ display: 'flex', gap: 5 }}><input type="checkbox" checked={(draft.iolFamilies || []).includes(fam)} onChange={e => upd({ iolFamilies: e.target.checked ? [...(draft.iolFamilies || []), fam] : (draft.iolFamilies || []).filter(x => x !== fam) })} /> {fam}</label>)}
                  </div>
                  <div style={{ fontSize: '0.72rem', color: 'var(--gray-500)', marginTop: 4 }}>A card with a lens family wins over a plain procedure card when the worklist lens matches.</div>
                </InvField>
              </div>
            </div>
          </div>

          <div className="card" style={{ margin: 0 }}>
            <div className="card-header">Items on this card</div>
            <div className="card-body">
              <div style={{ marginBottom: 10 }}><InvProductPicker products={P} onPick={addItem} placeholder="Add a product: type to search or scan…" /></div>
              <table style={{ width: '100%', borderCollapse: 'collapse' }}>
                <thead><tr><th style={invTh}></th><th style={invTh}>Product</th><th style={{ ...invTh, width: 70 }}>Qty</th><th style={{ ...invTh, width: 110 }}>Unit</th><th style={invTh}>Note</th><th style={{ ...invTh, width: 150 }} title="Leave this item off the second eye of a bilateral case">Skip on 2nd eye</th><th style={invTh}></th></tr></thead>
                <tbody>
                  {draft.items.map((it, i) => { const p = byId[it.productId]; return (
                    <tr key={it.productId + i}>
                      <td style={{ ...invCell, whiteSpace: 'nowrap' }}><button className="btn btn-outline btn-sm" style={{ padding: '2px 6px' }} onClick={() => move(i, -1)}>↑</button> <button className="btn btn-outline btn-sm" style={{ padding: '2px 6px' }} onClick={() => move(i, 1)}>↓</button></td>
                      <td style={invCell}>{p ? <><strong>{p.description}</strong><div style={{ fontSize: '0.7rem', color: 'var(--gray-500)' }}>{p.manufacturerCode} · {p.manufacturer}</div></> : <span style={{ color: 'var(--red)' }}>Unknown product {it.productId}</span>}</td>
                      <td style={invCell}><input type="number" min="0" step="0.5" value={it.qty} onChange={e => updItem(i, { qty: Number(e.target.value) })} /></td>
                      <td style={invCell}><input type="text" value={it.uom || ''} onChange={e => updItem(i, { uom: e.target.value })} /></td>
                      <td style={invCell}><input type="text" value={it.note || ''} onChange={e => updItem(i, { note: e.target.value })} /></td>
                      <td style={{ ...invCell, fontSize: '0.74rem' }}><label style={{ display: 'flex', gap: 4 }}><input type="checkbox" checked={!!it.skipSameDaySecondEye} onChange={e => updItem(i, { skipSameDaySecondEye: e.target.checked })} /> same day</label><label style={{ display: 'flex', gap: 4 }}><input type="checkbox" checked={!!it.skipDifferentDaySecondEye} onChange={e => updItem(i, { skipDifferentDaySecondEye: e.target.checked })} /> different day</label></td>
                      <td style={invCell}><button className="btn btn-danger btn-sm" onClick={() => setDraft(d => ({ ...d, items: d.items.filter((_, k) => k !== i) }))}>✕</button></td>
                    </tr>); })}
                  {draft.items.length === 0 && <tr><td colSpan={7} style={{ ...invCell, color: 'var(--gray-500)', textAlign: 'center' }}>No items yet.</td></tr>}
                </tbody>
              </table>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ─── Settings ──────────────────────────────────────────────────
function InvSettings(ctx) {
  const [sub, setSub] = invUseState('procedures');
  const tabs = [['procedures', 'Procedures'], ['manufacturers', 'Manufacturers'], ['suppliers', 'Suppliers'], ['shelves', 'Shelves'], ['uoms', 'Units'], ['kits', 'Kits'], ['iol', 'IOL families'], ['import', 'Import CSV'], ['stockdata', 'Stock data']];
  return (
    <div>
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 12 }}>{tabs.map(([id, l]) => <button key={id} className={`btn btn-sm ${sub === id ? 'btn-teal' : 'btn-outline'}`} onClick={() => setSub(id)}>{l}</button>)}</div>
      {sub === 'procedures' && <InvProcedures {...ctx} />}
      {['manufacturers', 'suppliers', 'shelves', 'uoms'].includes(sub) && <InvNameList key={sub} store={ctx.stores[sub]} label={tabs.find(t => t[0] === sub)[1]} singular={{ manufacturers: 'manufacturer', suppliers: 'supplier', shelves: 'shelf', uoms: 'unit' }[sub]} {...ctx} />}
      {sub === 'kits' && <InvKits {...ctx} />}
      {sub === 'iol' && <InvIolFamilies {...ctx} />}
      {sub === 'import' && <InvCsvImport {...ctx} />}
      {sub === 'stockdata' && <InvStockData {...ctx} />}
    </div>
  );
}

function InvNameList({ store, label, singular, savedOrConflict }) {
  const L = store.data || [];
  const [name, setName] = invUseState('');
  const [editing, setEditing] = invUseState(null);
  const add = async (e) => { e.preventDefault(); const n = name.trim(); if (!n) return; if (L.some(x => invNorm(x.name) === invNorm(n))) return; if (await savedOrConflict(store, [...L, { id: invId('X'), name: n, active: true }], `${label}: added ${n}`)) setName(''); };
  const rename = async (id, n) => { if (!n.trim()) return; if (await savedOrConflict(store, L.map(x => x.id === id ? { ...x, name: n.trim() } : x), 'Renamed')) setEditing(null); };
  const toggle = (x) => savedOrConflict(store, L.map(y => y.id === x.id ? { ...y, active: y.active === false } : y), x.active === false ? 'Restored' : 'Hidden');
  return (
    <div className="card" style={{ maxWidth: 620, margin: 0 }}>
      <div className="card-header">{label}</div>
      <div className="card-body">
        <form onSubmit={add} style={{ display: 'flex', gap: 8, marginBottom: 12 }}><input type="text" value={name} onChange={e => setName(e.target.value)} placeholder={`New ${singular || label.toLowerCase()}`} /><button className="btn btn-primary" type="submit">Add</button></form>
        <table style={{ width: '100%', borderCollapse: 'collapse' }}><tbody>
          {[...L].sort((a, b) => a.name.localeCompare(b.name)).map(x => (
            <tr key={x.id} style={{ opacity: x.active === false ? 0.5 : 1 }}>
              <td style={invCell}>{editing === x.id ? <input type="text" autoFocus defaultValue={x.name} onBlur={e => rename(x.id, e.target.value)} onKeyDown={e => { if (e.key === 'Enter') rename(x.id, e.target.value); if (e.key === 'Escape') setEditing(null); }} /> : x.name}</td>
              <td style={{ ...invCell, textAlign: 'right', whiteSpace: 'nowrap' }}><button className="btn btn-outline btn-sm" onClick={() => setEditing(x.id)}>Rename</button> <button className="btn btn-outline btn-sm" onClick={() => toggle(x)}>{x.active === false ? 'Restore' : 'Hide'}</button></td>
            </tr>))}
        </tbody></table>
      </div>
    </div>
  );
}

function InvProcedures({ stores, savedOrConflict }) {
  const D = stores.procedures.data || { list: [], config: {}, nonCardEquipment: [] };
  const P = stores.products.data || [], C = stores.prefcards.data || [];
  const byId = invUseMemo(() => Object.fromEntries(P.map(p => [p.id, p])), [P]);
  const [key, setKey] = invUseState('cataract');
  const [draft, setDraft] = invUseState(null);
  invUseEffect(() => { setDraft(JSON.parse(JSON.stringify(D.config[key] || { meds: [], equipment: [], dischargeMeds: [] }))); }, [key, stores.procedures.version]);
  const dirty = draft && JSON.stringify(draft) !== JSON.stringify(D.config[key] || { meds: [], equipment: [], dischargeMeds: [] });
  const save = () => savedOrConflict(stores.procedures, { ...D, config: { ...D.config, [key]: draft } }, 'Procedure settings saved');
  const saveList = (list) => savedOrConflict(stores.procedures, { ...D, list }, 'Procedure list saved');
  const [pickFor, setPickFor] = invUseState(null); // { section, i }

  const section = (sec, title, hint) => (
    <div className="card" style={{ margin: '0 0 12px' }}>
      <div className="card-header">{title}</div>
      <div className="card-body">
        <div style={{ fontSize: '0.76rem', color: 'var(--gray-500)', marginBottom: 8 }}>{hint}</div>
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead><tr><th style={{ ...invTh, width: '30%' }}>Name (as shown in the chart)</th><th style={{ ...invTh, width: 90 }}>Standard</th><th style={{ ...invTh, width: 110 }}>Pulls to stock</th><th style={invTh}>Linked product</th><th style={{ ...invTh, width: 40 }}></th></tr></thead>
          <tbody>
            {(draft[sec] || []).map((it, i) => { const p = it.productId ? byId[it.productId] : null; return (
              <tr key={i}>
                <td style={invCell}><input type="text" value={it.name} onChange={e => setDraft(d => ({ ...d, [sec]: d[sec].map((x, k) => k === i ? { ...x, name: e.target.value } : x) }))} /></td>
                <td style={{ ...invCell, textAlign: 'center' }}><input type="checkbox" checked={!!it.standard} onChange={e => setDraft(d => ({ ...d, [sec]: d[sec].map((x, k) => k === i ? { ...x, standard: e.target.checked } : x) }))} /></td>
                <td style={{ ...invCell, textAlign: 'center' }}><input type="checkbox" checked={!!it.pullsToStock} onChange={e => setDraft(d => ({ ...d, [sec]: d[sec].map((x, k) => k === i ? { ...x, pullsToStock: e.target.checked } : x) }))} /></td>
                <td style={invCell}>
                  {pickFor && pickFor.section === sec && pickFor.i === i
                    ? <InvProductPicker products={P} autoFocus onPick={p2 => { setDraft(d => ({ ...d, [sec]: d[sec].map((x, k) => k === i ? { ...x, productId: p2.id, linkGuessed: false } : x) })); setPickFor(null); }} />
                    : <span style={{ display: 'flex', gap: 6, alignItems: 'center', flexWrap: 'wrap' }}>
                        {p ? <span style={{ fontSize: '0.82rem' }}>{p.description}</span> : <span style={{ color: it.pullsToStock ? 'var(--red)' : 'var(--gray-400)', fontSize: '0.8rem' }}>{it.pullsToStock ? 'Needs a product' : 'Not linked'}</span>}
                        {it.linkGuessed && p && <InvTag color="var(--amber)">Best guess — please confirm</InvTag>}
                        {it.linkGuessed && p && <button className="btn btn-teal btn-sm" style={{ padding: '2px 8px' }} onClick={() => setDraft(d => ({ ...d, [sec]: d[sec].map((x, k) => k === i ? { ...x, linkGuessed: false } : x) }))}>Confirm</button>}
                        <button className="btn btn-outline btn-sm" style={{ padding: '2px 8px' }} onClick={() => setPickFor({ section: sec, i })}>{p ? 'Change' : 'Link'}</button>
                        {p && <button className="btn btn-outline btn-sm" style={{ padding: '2px 8px' }} onClick={() => setDraft(d => ({ ...d, [sec]: d[sec].map((x, k) => k === i ? { ...x, productId: null, linkGuessed: false } : x) }))}>Unlink</button>}
                      </span>}
                </td>
                <td style={invCell}><button className="btn btn-danger btn-sm" onClick={() => setDraft(d => ({ ...d, [sec]: d[sec].filter((_, k) => k !== i) }))}>✕</button></td>
              </tr>); })}
          </tbody>
        </table>
        <button className="btn btn-outline btn-sm" style={{ marginTop: 8 }} onClick={() => setDraft(d => ({ ...d, [sec]: [...(d[sec] || []), { name: '', standard: false, pullsToStock: false, productId: null }] }))}>+ Add row</button>
      </div>
    </div>
  );

  return (
    <div>
      <div className="card" style={{ margin: '0 0 12px' }}>
        <div className="card-header">Procedure list</div>
        <div className="card-body">
          <div style={{ fontSize: '0.76rem', color: 'var(--gray-500)', marginBottom: 8 }}>The names the worklist uses. Each maps to a procedure type (which decides the med/equipment lists below) and a default preference card. A card marked for a lens family wins over the default when the worklist lens matches.</div>
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead><tr><th style={invTh}>Name</th><th style={{ ...invTh, width: 130 }}>Type</th><th style={{ ...invTh, width: 110 }}>CPT</th><th style={{ ...invTh, width: 90 }}>Eye</th><th style={{ ...invTh, width: '32%' }}>Default card</th><th style={{ ...invTh, width: 70 }}></th></tr></thead>
            <tbody>
              {D.list.map(pr => (
                <tr key={pr.id} style={{ opacity: pr.active === false ? 0.5 : 1 }}>
                  <td style={invCell}><input type="text" key={pr.id + stores.procedures.version} defaultValue={pr.name} onBlur={e => { if (e.target.value.trim() && e.target.value !== pr.name) saveList(D.list.map(x => x.id === pr.id ? { ...x, name: e.target.value.trim() } : x)); }} /></td>
                  <td style={invCell}><select value={pr.key} onChange={e => saveList(D.list.map(x => x.id === pr.id ? { ...x, key: e.target.value } : x))}>{INV_PROC_KEYS.map(([k, l]) => <option key={k} value={k}>{l}</option>)}</select></td>
                  <td style={invCell}><input type="text" key={pr.id + 'c' + stores.procedures.version} defaultValue={pr.cptCode || ''} onBlur={e => { if (e.target.value !== (pr.cptCode || '')) saveList(D.list.map(x => x.id === pr.id ? { ...x, cptCode: e.target.value.trim() } : x)); }} /></td>
                  <td style={invCell}><select value={pr.eye || ''} onChange={e => saveList(D.list.map(x => x.id === pr.id ? { ...x, eye: e.target.value } : x))}><option value="">—</option><option>OD</option><option>OS</option><option>OU</option></select></td>
                  <td style={invCell}><select value={pr.defaultCardId || ''} onChange={e => saveList(D.list.map(x => x.id === pr.id ? { ...x, defaultCardId: e.target.value || null } : x))}><option value="">— none —</option>{C.filter(c => c.active !== false || c.id === pr.defaultCardId).map(c => <option key={c.id} value={c.id}>{c.name}</option>)}</select></td>
                  <td style={invCell}><button className="btn btn-outline btn-sm" onClick={() => saveList(D.list.map(x => x.id === pr.id ? { ...x, active: x.active === false } : x))}>{pr.active === false ? 'Restore' : 'Hide'}</button></td>
                </tr>))}
            </tbody>
          </table>
          <button className="btn btn-outline btn-sm" style={{ marginTop: 8 }} onClick={() => saveList([...D.list, { id: invId('PR'), name: 'New procedure', key: 'cataract', cptCode: '', eye: '', active: true, defaultCardId: null }])}>+ Add procedure</button>
        </div>
      </div>

      <div style={{ display: 'flex', gap: 6, alignItems: 'center', marginBottom: 12, flexWrap: 'wrap' }}>
        <span style={{ fontSize: '0.8rem', color: 'var(--gray-600)', fontWeight: 600 }}>Meds &amp; equipment for:</span>
        {INV_PROC_KEYS.map(([k, l]) => <button key={k} className={`btn btn-sm ${key === k ? 'btn-teal' : 'btn-outline'}`} onClick={() => setKey(k)}>{l}</button>)}
        <span style={{ flex: 1 }} />
        <button className="btn btn-primary btn-sm" disabled={!dirty} onClick={save}>{dirty ? 'Save changes' : 'Saved'}</button>
      </div>
      {draft && section('meds', 'Intra-op medications', '"Standard" is pre-checked on every chart. "Pulls to stock" means each patient use deducts the linked product from stock — leave it off for shared multi-use bottles.')}
      {draft && section('equipment', 'Equipment', 'Equipment used during the case. Link a product only if something is consumed per case (e.g. a laser pack).')}
      {draft && section('dischargeMeds', 'Discharge medications', 'Given or sent home at discharge. Link the exact product (e.g. Diamox tablets) so the right item is pulled.')}
    </div>
  );
}

function InvKits({ stores, savedOrConflict }) {
  const K = stores.kits.data || [], P = stores.products.data || [];
  const byId = invUseMemo(() => Object.fromEntries(P.map(p => [p.id, p])), [P]);
  const [selId, setSelId] = invUseState(null);
  const kit = K.find(k => k.id === selId);
  const persist = (next, msg) => savedOrConflict(stores.kits, next, msg);
  const upd = (patch) => persist(K.map(k => k.id === kit.id ? { ...k, ...patch } : k));
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '280px 1fr', gap: 14, alignItems: 'start' }}>
      <div className="card" style={{ margin: 0 }}>
        <div className="card-header" style={{ justifyContent: 'space-between' }}>Kits <button className="btn btn-primary btn-sm" onClick={async () => { const k = { id: invId('K'), name: 'New kit', components: [], active: true }; if (await persist([...K, k], 'Kit created')) setSelId(k.id); }}>+ New</button></div>
        {K.length === 0 && <div style={{ padding: 14, fontSize: '0.82rem', color: 'var(--gray-500)' }}>A kit is a bundle counted as one thing — e.g. a "cataract pack" made of specific products. None yet.</div>}
        {K.map(k => <div key={k.id} onClick={() => setSelId(k.id)} style={{ padding: '10px 14px', borderBottom: '1px solid var(--gray-100)', cursor: 'pointer', background: k.id === selId ? 'var(--gray-100)' : '', opacity: k.active === false ? 0.6 : 1 }}><div style={{ fontWeight: 600, fontSize: '0.86rem' }}>{k.name}</div><div style={{ fontSize: '0.72rem', color: 'var(--gray-500)' }}>{k.components.length} components</div></div>)}
      </div>
      {kit && (
        <div className="card" style={{ margin: 0 }}>
          <div className="card-header" style={{ justifyContent: 'space-between' }}>Kit <button className="btn btn-outline btn-sm" onClick={() => upd({ active: kit.active === false })}>{kit.active === false ? 'Activate' : 'Deactivate'}</button></div>
          <div className="card-body">
            <InvField label="Name"><input type="text" key={kit.id + stores.kits.version} defaultValue={kit.name} onBlur={e => { if (e.target.value.trim() && e.target.value !== kit.name) upd({ name: e.target.value.trim() }); }} /></InvField>
            <div style={{ margin: '10px 0' }}><InvProductPicker products={P} onPick={p => { if (!kit.components.some(c => c.productId === p.id)) upd({ components: [...kit.components, { productId: p.id, qty: 1 }] }); }} placeholder="Add a component…" /></div>
            <table style={{ width: '100%', borderCollapse: 'collapse' }}><tbody>
              {kit.components.map((c, i) => <tr key={c.productId}><td style={invCell}>{byId[c.productId] ? byId[c.productId].description : c.productId}</td><td style={{ ...invCell, width: 90 }}><input type="number" min="0" value={c.qty} onChange={e => upd({ components: kit.components.map((x, k) => k === i ? { ...x, qty: Number(e.target.value) } : x) })} /></td><td style={{ ...invCell, width: 40 }}><button className="btn btn-danger btn-sm" onClick={() => upd({ components: kit.components.filter((_, k) => k !== i) })}>✕</button></td></tr>)}
            </tbody></table>
          </div>
        </div>
      )}
    </div>
  );
}

function InvIolFamilies({ stores, savedOrConflict }) {
  const S = stores.settings.data || {}, P = stores.products.data || [];
  const byId = invUseMemo(() => Object.fromEntries(P.map(p => [p.id, p])), [P]);
  const rules = S.iolFamilyRules || {}, assoc = S.iolAssociations || {};
  const [name, setName] = invUseState(''); const [text, setText] = invUseState(''); const [type, setType] = invUseState('keyword');
  const persist = (patch, msg) => savedOrConflict(stores.settings, { ...S, ...patch }, msg);
  const add = (e) => { e.preventDefault(); if (!name.trim() || !text.trim()) return; persist({ iolFamilyRules: { ...rules, [name.trim()]: { matchType: type, matchText: text.trim() } } }, 'Family added'); setName(''); setText(''); };
  const remove = (fam) => { const r = { ...rules }; delete r[fam]; const a = { ...assoc }; delete a[fam]; persist({ iolFamilyRules: r, iolAssociations: a }, 'Family removed'); };
  const matchCount = (fam) => P.filter(p => p.isIol && invIolFamilyOf(p, rules) === fam).length;
  return (
    <div className="card" style={{ margin: 0, maxWidth: 900 }}>
      <div className="card-header">IOL families</div>
      <div className="card-body">
        <div style={{ fontSize: '0.8rem', color: 'var(--gray-600)', marginBottom: 10 }}>A family groups lens models (by a keyword or a model prefix) so a preference card can say "for LAL cases only", and so extra supplies (a cartridge, peel packs) come along automatically whenever a lens in that family is on the worklist.</div>
        <form onSubmit={add} style={{ display: 'grid', gridTemplateColumns: '1fr 130px 1fr auto', gap: 8, marginBottom: 12 }}>
          <input type="text" placeholder="Family name (e.g. LAL)" value={name} onChange={e => setName(e.target.value)} />
          <select value={type} onChange={e => setType(e.target.value)}><option value="keyword">contains</option><option value="prefix">model starts with</option></select>
          <input type="text" placeholder="text to match in the lens description" value={text} onChange={e => setText(e.target.value)} />
          <button className="btn btn-primary" type="submit">Add</button>
        </form>
        {Object.keys(rules).map(fam => (
          <div key={fam} style={{ border: '1px solid var(--gray-200)', borderRadius: 8, padding: 10, marginBottom: 8 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
              <div><strong>{fam}</strong> <span style={{ fontSize: '0.78rem', color: 'var(--gray-500)' }}>— {rules[fam].matchType === 'prefix' ? 'model starts with' : 'description contains'} "{rules[fam].matchText}" · matches {matchCount(fam)} lenses</span></div>
              <button className="btn btn-danger btn-sm" onClick={() => remove(fam)}>Remove</button>
            </div>
            <div style={{ marginTop: 8, fontSize: '0.78rem', color: 'var(--gray-600)' }}>Supplies that come along with this family:</div>
            {(assoc[fam] || []).map((a, i) => <div key={i} style={{ display: 'flex', gap: 8, alignItems: 'center', fontSize: '0.82rem', padding: '4px 0' }}><span style={{ flex: 1 }}>{byId[a.productId] ? byId[a.productId].description : a.productId}</span><input type="number" min="0" style={{ width: 70 }} value={a.qty} onChange={e => persist({ iolAssociations: { ...assoc, [fam]: assoc[fam].map((x, k) => k === i ? { ...x, qty: Number(e.target.value) } : x) } })} /><button className="btn btn-danger btn-sm" onClick={() => persist({ iolAssociations: { ...assoc, [fam]: assoc[fam].filter((_, k) => k !== i) } })}>✕</button></div>)}
            <div style={{ marginTop: 6 }}><InvProductPicker products={P} onPick={p => persist({ iolAssociations: { ...assoc, [fam]: [...(assoc[fam] || []), { productId: p.id, qty: 1 }] } }, 'Supply added')} placeholder="Add a supply for this family…" /></div>
          </div>
        ))}
      </div>
    </div>
  );
}
// Which family does a lens belong to? (keyword = description contains; prefix = model starts with)
function invIolFamilyOf(product, rules) {
  const d = invNorm(product.description), m = invNorm(product.iolModel || product.description);
  for (const [fam, r] of Object.entries(rules || {})) {
    const t = invNorm(r.matchText); if (!t) continue;
    if (r.matchType === 'prefix' ? m.startsWith(t) : d.includes(t)) return fam;
  }
  return null;
}

function InvCsvImport({ stores, savedOrConflict, toast }) {
  const P = stores.products.data || [];
  const [preview, setPreview] = invUseState(null);
  const COLS = { description: ['description', 'name', 'product'], manufacturerCode: ['manufacturer_code', 'mfr code', 'code', 'catalog #', 'catalog number', 'sku'], manufacturer: ['manufacturer', 'mfr'], group: ['product_group', 'group', 'category'], shelf: ['shelf_location', 'shelf'], supplier: ['default_supplier', 'supplier', 'vendor'], purchaseUom: ['purchase_uom', 'buy unit', 'purchase unit'], dispenseUom: ['dispense_uom', 'dispense unit', 'unit'], unitsPerPurchase: ['units_per_purchase', 'units per purchase', 'per'], purchasePrice: ['purchase_price', 'price', 'cost'], upc: ['upc', 'gtin', 'barcode'], parMin: ['par_min', 'par'], isIol: ['is_iol', 'iol'], isDrug: ['is_drug', 'drug'], isControlled: ['is_controlled', 'controlled'], expiryRequired: ['exp_required', 'expiry required'], isBuyAndBill: ['is_buy_and_bill', 'buy and bill'] };
  const onFile = (e) => {
    const file = e.target.files[0]; if (!file) return;
    Papa.parse(file, { header: true, skipEmptyLines: true, complete: (res) => {
      const headers = (res.meta.fields || []).map(h => ({ raw: h, n: invNorm(h) }));
      const col = (key) => { const h = headers.find(h => COLS[key].includes(h.n)); return h ? h.raw : null; };
      if (!col('description')) { toast('⚠️ The file needs a "description" column.', 'error'); return; }
      const bool = (v) => /^(true|yes|y|1|x)$/i.test(String(v || '').trim());
      const rows = res.data.map(r => { const o = {}; for (const k of Object.keys(COLS)) { const c = col(k); if (c) o[k] = r[c]; } return o; }).filter(o => o.description && o.description.trim());
      const byDesc = new Map(P.map(p => [invNorm(p.description), p])), byUpc = new Map(P.filter(p => p.upc).map(p => [p.upc.replace(/^0+/, ''), p]));
      const adds = [], updates = [];
      for (const o of rows) {
        const existing = byDesc.get(invNorm(o.description)) || (o.upc && byUpc.get(String(o.upc).replace(/\D/g, '').replace(/^0+/, '')));
        const patch = {};
        for (const [k, v] of Object.entries(o)) { if (v === undefined || v === '') continue; if (['isIol', 'isDrug', 'isControlled', 'expiryRequired', 'isBuyAndBill'].includes(k)) patch[k] = bool(v); else if (['unitsPerPurchase', 'purchasePrice', 'parMin'].includes(k)) patch[k] = Number(String(v).replace(/[$,]/g, '')) || 0; else patch[k] = String(v).trim(); }
        if (existing) updates.push({ ...existing, ...patch, description: existing.description });
        else adds.push({ id: invId('P'), description: patch.description, manufacturerCode: '', manufacturer: '', group: 'Surgery Supplies', shelf: 'OR', supplier: '', purchaseUom: 'Each', dispenseUom: 'Each', unitsPerPurchase: 1, purchasePrice: 0, retailPrice: 0, upc: '', isIol: false, expiryRequired: false, isDrug: false, isControlled: false, isBuyAndBill: false, parMin: 0, active: true, notes: '', ...patch });
      }
      setPreview({ adds, updates, columns: Object.keys(COLS).filter(k => col(k)) });
    } });
    e.target.value = '';
  };
  const apply = async () => {
    const upd = new Map(preview.updates.map(u => [u.id, u]));
    const next = [...P.map(p => upd.get(p.id) || p), ...preview.adds];
    if (await savedOrConflict(stores.products, next, `Imported: ${preview.adds.length} new, ${preview.updates.length} updated`)) { InvApi.audit('INV_IMPORT', `CSV import: ${preview.adds.length} new, ${preview.updates.length} updated`); setPreview(null); }
  };
  return (
    <div className="card" style={{ margin: 0, maxWidth: 760 }}>
      <div className="card-header">Import products from CSV</div>
      <div className="card-body">
        <div style={{ fontSize: '0.8rem', color: 'var(--gray-600)', marginBottom: 10 }}>Needs a <strong>description</strong> column; optional columns: manufacturer_code, manufacturer, group, shelf, supplier, purchase_uom, dispense_uom, units_per_purchase, purchase_price, upc, par_min, is_iol, is_drug, is_controlled, exp_required, is_buy_and_bill. Rows matching an existing description (or UPC) update that product; others are added. Nothing is deleted.</div>
        <input type="file" accept=".csv,text/csv" onChange={onFile} />
        {preview && (
          <div style={{ marginTop: 12 }}>
            <div style={{ fontSize: '0.86rem' }}>Recognized columns: {preview.columns.join(', ')}</div>
            <div style={{ fontSize: '0.86rem', margin: '6px 0' }}><strong>{preview.adds.length}</strong> new products, <strong>{preview.updates.length}</strong> updates.</div>
            {preview.adds.slice(0, 8).map(a => <div key={a.id} style={{ fontSize: '0.78rem', color: 'var(--gray-500)' }}>+ {a.description}</div>)}
            <div className="modal-actions" style={{ marginTop: 10, justifyContent: 'flex-start' }}><button className="btn btn-primary" onClick={apply}>Apply import</button><button className="btn btn-outline" onClick={() => setPreview(null)}>Cancel</button></div>
          </div>
        )}
      </div>
    </div>
  );
}

// Shared with the rest of the app and with tests.
window.SurgSuiteInventory = { StockRoomView, parseGS1: invParseGS1, findByScan: invFindByScan, iolFamilyOf: invIolFamilyOf, api: InvApi };
