// ═══════════════════════════════════════════════════════════════
// inventory-stock.jsx — stock on hand, receiving, alerts, reports (Inventory Step 3)
// ═══════════════════════════════════════════════════════════════
//
// WHAT THIS IS (in plain English):
// The second half of the Stock Room. inventory.jsx (Step 2) is the catalog
// and settings; this file is everything that involves actual quantities:
//
//   • stock on hand per product, by shelf / lot / serial, read from the
//     server's ledger ("bank statement") — never typed in directly
//   • Receive a delivery: scan the box, the product / lot / expiry / serial
//     fill in, type the quantity, post it → "received" lines
//   • Adjust: signed correction with a required reason → "adjusted" line
//   • Alerts: expiring lots, products below PAR, price changes (server computes)
//   • Reports: PAR report with purchase advice (CSV), transaction history
//   • Stock data (Settings): trial-run stock, Heather's PAR levels, and the
//     import of Heather's export file (read in the browser, never uploaded
//     anywhere but our own server, and only the stock part of it)
//
// Loaded after inventory.jsx; both are plain scripts compiled by the same
// in-browser Babel, so functions here are visible there and vice versa.
// ═══════════════════════════════════════════════════════════════

// ─── Data hooks ────────────────────────────────────────────────
function useInvOnHand() {
  const [state, setState] = invUseState({ products: {}, loading: true, computedAt: null });
  const reload = invUseCallback(async () => {
    try { const r = await fetch('/api/inventory/onhand', { headers: InvApi.headers() }); const d = await r.json(); setState({ products: d.products || {}, loading: false, computedAt: d.computedAt }); }
    catch { setState(s => ({ ...s, loading: false })); }
  }, []);
  invUseEffect(() => { reload(); }, [reload]);
  return { ...state, reload };
}
function useInvAlerts() {
  const [alerts, setAlerts] = invUseState(null);
  const reload = invUseCallback(async () => {
    try { const r = await fetch('/api/inventory/alerts', { headers: InvApi.headers() }); setAlerts(await r.json()); } catch {}
  }, []);
  invUseEffect(() => { reload(); }, [reload]);
  return { alerts, reload };
}
async function invPostLines(lines) {
  const r = await fetch('/api/inventory/ledger', { method: 'POST', headers: InvApi.headers(), body: JSON.stringify({ lines }) });
  const d = await r.json().catch(() => ({}));
  if (!r.ok) throw new Error(d.error ? (d.errors ? d.error + ' ' + d.errors.map(e => `line ${e.index + 1}: ${e.error}`).join('; ') : d.error) : `Could not save (${r.status})`);
  return d;
}
async function invFetchLedger(params) {
  const q = new URLSearchParams(Object.fromEntries(Object.entries(params || {}).filter(([, v]) => v))).toString();
  const r = await fetch('/api/inventory/ledger' + (q ? '?' + q : ''), { headers: InvApi.headers() });
  return r.json();
}
const invFmtQty = (n) => (n == null ? '—' : (Math.round(n * 1000) / 1000).toString());
const invToday = () => new Date().toLocaleDateString('en-CA', { timeZone: 'America/Detroit' });
const invDaysUntil = (d) => d ? Math.round((new Date(d + 'T00:00:00') - new Date(invToday() + 'T00:00:00')) / 86400000) : null;
function invExpiryTag(expiry) {
  const d = invDaysUntil(expiry); if (d == null) return null;
  if (d < 0) return <InvTag color="var(--red)">expired</InvTag>;
  if (d <= 30) return <InvTag color="var(--red)">{d}d left</InvTag>;
  if (d <= 60) return <InvTag color="var(--amber)">{d}d left</InvTag>;
  return null;
}
// Catalog unit cost per dispense unit (catalog price is per purchase unit).
const invCatalogUnitCost = (p) => (Number(p.purchasePrice) || 0) / (Number(p.unitsPerPurchase) || 1);
const invLotUnitCost = (p, lot) => (lot && lot.unitCost != null) ? lot.unitCost : invCatalogUnitCost(p);
function invDownloadCsv(name, rows) {
  const esc = (v) => { const s = String(v == null ? '' : v); return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s; };
  const csv = rows.map(r => r.map(esc).join(',')).join('\n');
  const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([csv], { type: 'text/csv' })); a.download = name; a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 1000);
}

// ─── Stock rows for one product (used inside the product modal) ────
function InvStockRows({ product, onHand, onReceive, onAdjust }) {
  const bal = onHand.products[product.id];
  const lots = bal ? bal.lots : [];
  return (
    <div className="card" style={{ margin: '12px 0 0' }}>
      <div className="card-header" style={{ justifyContent: 'space-between' }}>
        <span>Stock on hand · {bal ? `${invFmtQty(bal.onHand)} ${product.dispenseUom}` : '0'}{bal && bal.reserved ? ` (${invFmtQty(bal.reserved)} reserved)` : ''}</span>
        <button type="button" className="btn btn-teal btn-sm" onClick={() => onReceive(product)}>+ Receive</button>
      </div>
      <div className="card-body" style={{ padding: 0 }}>
        {lots.length === 0 ? <div style={{ padding: 12, fontSize: '0.82rem', color: 'var(--gray-500)' }}>Nothing on the shelf. Receive a delivery or record a count to add stock.</div> : (
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead><tr><th style={invTh}>Shelf</th><th style={invTh}>Lot</th><th style={invTh}>Serial</th><th style={invTh}>Expiry</th><th style={{ ...invTh, textAlign: 'right' }}>On hand</th><th style={{ ...invTh, textAlign: 'right' }}>Unit cost</th><th style={invTh}></th></tr></thead>
            <tbody>{lots.map((l, i) => (
              <tr key={i}>
                <td style={invCell}>{l.shelf || '—'}</td><td style={invCell}>{l.lot || '—'}</td><td style={invCell}>{l.serial || '—'}</td>
                <td style={invCell}>{l.expiry || '—'} {invExpiryTag(l.expiry)}</td>
                <td style={{ ...invCell, textAlign: 'right' }}><strong>{invFmtQty(l.onHand)}</strong>{l.reserved ? <span style={{ color: 'var(--gray-500)', fontSize: '0.74rem' }}> ({invFmtQty(l.reserved)} res.)</span> : null}</td>
                <td style={{ ...invCell, textAlign: 'right' }}>{invMoney(invLotUnitCost(product, l))}</td>
                <td style={{ ...invCell, textAlign: 'right' }}><button type="button" className="btn btn-outline btn-sm" onClick={() => onAdjust(product, l)}>Adjust</button></td>
              </tr>))}</tbody>
          </table>
        )}
      </div>
    </div>
  );
}

// ─── Receive a delivery ────────────────────────────────────────
function InvReceiveModal({ products, shelves, initialProduct, onDone, onClose, toast }) {
  const [lines, setLines] = invUseState(() => initialProduct ? [blankLine(initialProduct, {})] : []);
  const [note, setNote] = invUseState('');
  const [busy, setBusy] = invUseState(false);
  const shelfNames = (shelves || []).filter(s => s.active !== false).map(s => s.name);
  function blankLine(p, scan) {
    return { key: invId('r'), product: p, qty: 1, unit: 'purchase', lot: scan.lot || '', expiry: scan.expiry || '', serial: scan.serial || '', shelf: p.shelf || 'OR', cost: Number(p.purchasePrice) || 0, orderedFor: '' };
  }
  // From the picker: (product, scan) when a barcode was scanned, (product, null) when picked from the list.
  const addByScanOrPick = (product, scanInfo) => {
    const scan = scanInfo || { lot: '', expiry: '', serial: '' };
    if (!product) { toast('⚠️ No product matches that barcode. Add it to the catalog first (paste the barcode into its UPC field).', 'error'); return; }
    // Same product + same lot/serial scanned again → bump quantity instead of a second line.
    setLines(ls => {
      const same = ls.findIndex(l => l.product.id === product.id && (l.lot || '') === (scan.lot || '') && (l.serial || '') === (scan.serial || ''));
      if (same >= 0 && !scan.serial) return ls.map((l, i) => i === same ? { ...l, qty: Number(l.qty) + 1 } : l);
      return [...ls, blankLine(product, scan)];
    });
  };
  const upd = (key, patch) => setLines(ls => ls.map(l => l.key === key ? { ...l, ...patch } : l));
  const dispenseQty = (l) => l.unit === 'purchase' ? Number(l.qty) * (Number(l.product.unitsPerPurchase) || 1) : Number(l.qty);
  const unitCost = (l) => l.unit === 'purchase' ? (Number(l.cost) || 0) / (Number(l.product.unitsPerPurchase) || 1) : (Number(l.cost) || 0);
  const post = async () => {
    const bad = lines.find(l => !(dispenseQty(l) > 0)); if (bad) { toast('⚠️ Every line needs a quantity.', 'error'); return; }
    const needExp = lines.find(l => l.product.expiryRequired && !l.expiry); if (needExp) { toast(`⚠️ ${needExp.product.description} needs an expiry date.`, 'error'); return; }
    setBusy(true);
    try {
      const out = lines.map(l => ({ type: 'received', productId: l.product.id, qty: dispenseQty(l), lot: l.lot, serial: l.serial, expiry: l.expiry, shelf: l.shelf, unitCost: Math.round(unitCost(l) * 10000) / 10000, uom: l.product.dispenseUom, orderedFor: l.product.isIol ? l.orderedFor : '', note: note || 'Delivery received', ref: { kind: 'other', id: 'delivery' } }));
      await invPostLines(out);
      InvApi.audit('INV_RECEIVE', `Received ${out.length} line(s): ${out.slice(0, 3).map(o => `${o.qty}×${lines.find(l => l.product.id === o.productId).product.description}`).join('; ')}`);
      toast(`Received ${out.length} item${out.length === 1 ? '' : 's'} into stock`);
      onDone();
    } catch (e) { toast('⚠️ ' + e.message, 'error'); }
    setBusy(false);
  };
  return (
    <InvModal title="Receive a delivery" onClose={onClose} wide>
      <div style={{ fontSize: '0.8rem', color: 'var(--gray-600)', marginBottom: 8 }}>Scan each box (or search), then check the quantity. A lens-box barcode fills in lot, expiry, and serial by itself.</div>
      <InvProductPicker products={products} autoFocus onPick={addByScanOrPick} placeholder="Scan a barcode or type to search, then Enter…" />
      {lines.length > 0 && (
        <table style={{ width: '100%', borderCollapse: 'collapse', marginTop: 10 }}>
          <thead><tr><th style={{ ...invTh, width: '28%' }}>Product</th><th style={{ ...invTh, width: 150 }}>Qty</th><th style={{ ...invTh, width: 110 }}>Lot</th><th style={{ ...invTh, width: 130 }}>Expiry</th><th style={{ ...invTh, width: 120 }}>Serial</th><th style={{ ...invTh, width: 110 }}>Shelf</th><th style={{ ...invTh, width: 110 }}>Cost / unit</th><th style={{ ...invTh, width: 30 }}></th></tr></thead>
          <tbody>{lines.map(l => { const p = l.product; const catalog = Number(p.purchasePrice) || 0; const diff = l.unit === 'purchase' && catalog > 0 && Math.abs(Number(l.cost) - catalog) > 0.005; return (
            <InvReact.Fragment key={l.key}>
              <tr>
                <td style={invCell}><strong>{p.description}</strong><div style={{ fontSize: '0.7rem', color: 'var(--gray-500)' }}>{p.manufacturerCode} · {p.purchaseUom}{p.unitsPerPurchase > 1 ? ` of ${p.unitsPerPurchase} ${p.dispenseUom}` : ''} · on shelf → {invFmtQty(dispenseQty(l))} {p.dispenseUom}</div></td>
                <td style={invCell}><div style={{ display: 'flex', gap: 4 }}><input type="number" min="0" step="1" value={l.qty} onChange={e => upd(l.key, { qty: e.target.value })} style={{ width: 64 }} /><select value={l.unit} onChange={e => upd(l.key, { unit: e.target.value, cost: e.target.value === 'purchase' ? (Number(p.purchasePrice) || 0) : invCatalogUnitCost(p) })}><option value="purchase">{p.purchaseUom}</option><option value="dispense">{p.dispenseUom}</option></select></div></td>
                <td style={invCell}><input type="text" value={l.lot} onChange={e => upd(l.key, { lot: e.target.value })} /></td>
                <td style={invCell}><input type="date" value={l.expiry} onChange={e => upd(l.key, { expiry: e.target.value })} style={{ borderColor: p.expiryRequired && !l.expiry ? 'var(--red)' : undefined }} /></td>
                <td style={invCell}><input type="text" value={l.serial} onChange={e => upd(l.key, { serial: e.target.value })} /></td>
                <td style={invCell}><select value={l.shelf} onChange={e => upd(l.key, { shelf: e.target.value })}>{[...new Set([l.shelf, ...shelfNames])].filter(Boolean).map(s => <option key={s}>{s}</option>)}</select></td>
                <td style={invCell}><input type="number" step="0.01" min="0" value={l.cost} onChange={e => upd(l.key, { cost: e.target.value })} style={{ borderColor: diff ? 'var(--amber)' : undefined }} title={diff ? `Catalog price is ${invMoney(catalog)} per ${p.purchaseUom} — this will be flagged as a price change` : ''} /></td>
                <td style={invCell}><button type="button" className="btn btn-danger btn-sm" onClick={() => setLines(ls => ls.filter(x => x.key !== l.key))}>✕</button></td>
              </tr>
              {p.isIol && <tr><td colSpan={8} style={{ ...invCell, paddingTop: 0, borderBottom: '1px solid var(--gray-200)' }}><div style={{ display: 'flex', gap: 8, alignItems: 'center', fontSize: '0.78rem', color: 'var(--gray-600)' }}><span>Ordered for patient (name or MRN, optional):</span><input type="text" value={l.orderedFor} onChange={e => upd(l.key, { orderedFor: e.target.value })} style={{ maxWidth: 280 }} placeholder="e.g. Smith, J — MRN 12345" /></div></td></tr>}
            </InvReact.Fragment>); })}</tbody>
        </table>
      )}
      <div className="field-row c2" style={{ marginTop: 12 }}>
        <InvField label="Note (packing slip #, who delivered, …)"><input type="text" value={note} onChange={e => setNote(e.target.value)} /></InvField>
        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', alignItems: 'end' }}>
          <button type="button" className="btn btn-outline" onClick={onClose}>Cancel</button>
          <button type="button" className="btn btn-primary" disabled={busy || lines.length === 0} onClick={post}>{busy ? 'Saving…' : `Put ${lines.length || ''} item${lines.length === 1 ? '' : 's'} into stock`}</button>
        </div>
      </div>
    </InvModal>
  );
}

// ─── Adjust one lot ────────────────────────────────────────────
const INV_ADJUST_REASONS = ['Count correction', 'Expired — discarded', 'Damaged / opened not used', 'Found on shelf', 'Used, not charted', 'Transferred to another location', 'Other'];
function InvAdjustModal({ product, lot, onDone, onClose, toast }) {
  const [mode, setMode] = invUseState('set'); // 'set' = new count, 'delta' = +/- change
  const [value, setValue] = invUseState(String(lot.onHand));
  const [reason, setReason] = invUseState(INV_ADJUST_REASONS[0]);
  const [detail, setDetail] = invUseState('');
  const [busy, setBusy] = invUseState(false);
  const delta = mode === 'set' ? Number(value) - lot.onHand : Number(value);
  const post = async () => {
    if (!Number.isFinite(delta) || delta === 0) { toast('⚠️ Nothing to change.', 'error'); return; }
    if (reason === 'Other' && !detail.trim()) { toast('⚠️ Please say why.', 'error'); return; }
    setBusy(true);
    try {
      await invPostLines([{ type: 'adjusted', productId: product.id, qty: delta, lot: lot.lot, serial: lot.serial, shelf: lot.shelf, expiry: lot.expiry, note: reason + (detail ? ' — ' + detail : ''), ref: { kind: 'adjustment', id: 'manual' } }]);
      InvApi.audit('INV_ADJUST', `${delta > 0 ? '+' : ''}${delta} ${product.description}${lot.lot ? ' lot ' + lot.lot : ''}${lot.serial ? ' sn ' + lot.serial : ''}: ${reason}`);
      toast('Adjustment recorded'); onDone();
    } catch (e) { toast('⚠️ ' + e.message, 'error'); }
    setBusy(false);
  };
  return (
    <InvModal title={`Adjust — ${product.description}`} onClose={onClose}>
      <div style={{ fontSize: '0.82rem', color: 'var(--gray-600)', marginBottom: 10 }}>{lot.shelf || '—'} · lot {lot.lot || '—'} · serial {lot.serial || '—'} · on hand <strong>{invFmtQty(lot.onHand)} {product.dispenseUom}</strong></div>
      <div style={{ display: 'flex', gap: 6, marginBottom: 10 }}>
        <button type="button" className={`btn btn-sm ${mode === 'set' ? 'btn-teal' : 'btn-outline'}`} onClick={() => { setMode('set'); setValue(String(lot.onHand)); }}>Set the count to…</button>
        <button type="button" className={`btn btn-sm ${mode === 'delta' ? 'btn-teal' : 'btn-outline'}`} onClick={() => { setMode('delta'); setValue('-1'); }}>Add / remove…</button>
      </div>
      <div className="field-row c2">
        <InvField label={mode === 'set' ? 'New count' : 'Change (use − to remove)'}><input type="number" step="1" value={value} onChange={e => setValue(e.target.value)} autoFocus /></InvField>
        <InvField label="Result"><div style={{ padding: '9px 0', fontWeight: 600 }}>{invFmtQty(lot.onHand)} → {invFmtQty(lot.onHand + (Number.isFinite(delta) ? delta : 0))} {product.dispenseUom} <span style={{ color: delta < 0 ? 'var(--red)' : 'var(--green)', fontSize: '0.8rem' }}>({delta > 0 ? '+' : ''}{Number.isFinite(delta) ? delta : 0})</span></div></InvField>
      </div>
      <div className="field-row c2">
        <InvField label="Reason"><select value={reason} onChange={e => setReason(e.target.value)}>{INV_ADJUST_REASONS.map(r => <option key={r}>{r}</option>)}</select></InvField>
        <InvField label="Details"><input type="text" value={detail} onChange={e => setDetail(e.target.value)} placeholder={reason === 'Other' ? 'required' : 'optional'} /></InvField>
      </div>
      <div className="modal-actions"><button type="button" className="btn btn-outline" onClick={onClose}>Cancel</button><button type="button" className="btn btn-primary" disabled={busy} onClick={post}>Record adjustment</button></div>
    </InvModal>
  );
}

// ─── Lenses on hand (every serial on the shelf) ─────────────────
function InvLensesOnHand({ products, onHand, onOpen }) {
  const [q, setQ] = invUseState('');
  const rows = invUseMemo(() => {
    const out = [];
    for (const p of products) { if (!p.isIol || p.active === false) continue; const b = onHand.products[p.id]; if (!b) continue; for (const l of b.lots) if (l.onHand > 0) out.push({ p, l }); }
    const n = invNorm(q);
    return out.filter(r => !n || invNorm(r.p.description).includes(n) || invNorm(r.l.serial).includes(n) || invNorm(r.l.lot).includes(n)).sort((a, b) => a.p.description.localeCompare(b.p.description));
  }, [products, onHand, q]);
  const fam = (p) => { try { return invIolFamilyOf(p, (window.__invSettings || {}).iolFamilyRules || {}); } catch { return null; } };
  return (
    <div className="card" style={{ margin: 0 }}>
      <div className="card-header" style={{ justifyContent: 'space-between' }}><span>Lenses on hand · {rows.reduce((n, r) => n + r.l.onHand, 0)}</span><input type="text" value={q} onChange={e => setQ(e.target.value)} placeholder="Filter by model, serial, lot…" style={{ maxWidth: 280 }} /></div>
      <div style={{ overflow: 'auto', maxHeight: 'calc(100vh - 330px)' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead><tr><th style={invTh}>Lens</th><th style={invTh}>Power</th><th style={invTh}>Family</th><th style={invTh}>Serial</th><th style={invTh}>Lot</th><th style={invTh}>Expiry</th><th style={invTh}>Shelf</th><th style={{ ...invTh, textAlign: 'right' }}>Qty</th><th style={{ ...invTh, textAlign: 'right' }}>Cost</th></tr></thead>
          <tbody>{rows.map((r, i) => (
            <tr key={i} onClick={() => onOpen(r.p)} style={{ cursor: 'pointer' }}>
              <td style={invCell}><strong>{r.p.iolModel || r.p.description}</strong></td><td style={invCell}>{r.p.iolPower}</td><td style={invCell}>{fam(r.p) || '—'}</td><td style={invCell}>{r.l.serial || '—'}</td><td style={invCell}>{r.l.lot || '—'}</td><td style={invCell}>{r.l.expiry || '—'} {invExpiryTag(r.l.expiry)}</td><td style={invCell}>{r.l.shelf || '—'}</td><td style={{ ...invCell, textAlign: 'right' }}>{invFmtQty(r.l.onHand)}{r.l.reserved ? ` (${invFmtQty(r.l.reserved)} res.)` : ''}</td><td style={{ ...invCell, textAlign: 'right' }}>{invMoney(invLotUnitCost(r.p, r.l))}</td>
            </tr>))}
            {rows.length === 0 && <tr><td colSpan={9} style={{ ...invCell, color: 'var(--gray-500)', textAlign: 'center', padding: 30 }}>No lenses in stock.</td></tr>}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// ─── Alerts panel ──────────────────────────────────────────────
function InvAlertsPanel({ alerts, stores, savedOrConflict, onChanged, compact }) {
  if (!alerts) return <div style={{ color: 'var(--gray-500)', fontSize: '0.84rem' }}>Checking alerts…</div>;
  const S = stores.settings.data || {};
  const ack = async (lineId) => { if (await savedOrConflict(stores.settings, { ...S, acknowledgedPriceChanges: [...(S.acknowledgedPriceChanges || []), lineId] }, 'Price change noted')) onChanged && onChanged(); };
  const acceptPrice = async (pc) => {
    const P = stores.products.data || []; const p = P.find(x => x.id === pc.productId); if (!p) return;
    const next = P.map(x => x.id === p.id ? { ...x, purchasePrice: Math.round(pc.receivedUnitCost * (Number(p.unitsPerPurchase) || 1) * 100) / 100 } : x);
    if (await savedOrConflict(stores.products, next, `Catalog price updated for ${p.description}`)) await ack(pc.lineId);
  };
  const sec = (title, color, items, render) => items.length > 0 && (
    <div style={{ marginBottom: 10 }}>
      <div style={{ fontSize: '0.74rem', fontWeight: 700, color, textTransform: 'uppercase', marginBottom: 4 }}>{title} · {items.length}</div>
      {items.slice(0, compact ? 5 : 100).map(render)}
      {compact && items.length > 5 && <div style={{ fontSize: '0.74rem', color: 'var(--gray-500)' }}>…and {items.length - 5} more</div>}
    </div>
  );
  const row = { display: 'flex', gap: 8, alignItems: 'center', fontSize: '0.82rem', padding: '4px 0', borderBottom: '1px solid var(--gray-100)' };
  const expired = alerts.expiring.filter(e => e.level === 'expired'), soon = alerts.expiring.filter(e => e.level === 'soon'), notice = alerts.expiring.filter(e => e.level === 'notice');
  if (alerts.counts.total === 0 && notice.length === 0) return <div style={{ color: 'var(--green)', fontSize: '0.86rem' }}>✅ No alerts — nothing expired or expiring within 30 days, nothing below PAR, no unreviewed price changes.</div>;
  return (
    <div>
      {sec('Expired on the shelf', 'var(--red)', expired, e => <div key={e.productId + e.lot + e.serial} style={row}><span style={{ flex: 1 }}><strong>{e.description}</strong> {e.lot ? 'lot ' + e.lot : ''}{e.serial ? ' sn ' + e.serial : ''} · {e.shelf || '—'}</span><span>{e.onHand} on hand</span><span style={{ color: 'var(--red)' }}>expired {e.expiry}</span></div>)}
      {sec('Expiring within 30 days', 'var(--red)', soon, e => <div key={e.productId + e.lot + e.serial} style={row}><span style={{ flex: 1 }}><strong>{e.description}</strong> {e.lot ? 'lot ' + e.lot : ''}{e.serial ? ' sn ' + e.serial : ''} · {e.shelf || '—'}</span><span>{e.onHand} on hand</span><span>{e.expiry} ({e.daysLeft}d)</span></div>)}
      {sec('Below PAR', 'var(--amber)', alerts.belowPar, b => <div key={b.productId} style={row}><span style={{ flex: 1 }}><strong>{b.description}</strong> · {b.supplier || '—'}</span><span>{invFmtQty(b.available)} of {b.parMin} {b.dispenseUom}</span><span style={{ color: 'var(--amber)' }}>short {invFmtQty(b.shortfall)}</span></div>)}
      {sec('Price changes', 'var(--navy)', alerts.priceChanges, pc => <div key={pc.lineId} style={row}><span style={{ flex: 1 }}><strong>{pc.description}</strong> received at {invMoney(pc.receivedUnitCost)} vs catalog {invMoney(pc.catalogUnitCost)} per unit</span><span style={{ color: pc.changePct > 0 ? 'var(--red)' : 'var(--green)', fontWeight: 600 }}>{pc.changePct > 0 ? '+' : ''}{pc.changePct}%</span>{!compact && <><button className="btn btn-outline btn-sm" onClick={() => acceptPrice(pc)}>Update catalog price</button><button className="btn btn-outline btn-sm" onClick={() => ack(pc.lineId)}>Dismiss</button></>}</div>)}
      {!compact && sec('Expiring in 31–60 days', 'var(--gray-500)', notice, e => <div key={e.productId + e.lot + e.serial} style={row}><span style={{ flex: 1 }}>{e.description} {e.lot ? 'lot ' + e.lot : ''}{e.serial ? ' sn ' + e.serial : ''}</span><span>{e.onHand} on hand</span><span>{e.expiry} ({e.daysLeft}d)</span></div>)}
    </div>
  );
}

// ─── Dashboard (replaces the Step 2 placeholder tiles) ─────────
function InvDashboardV1({ stores, onHand, alerts, savedOrConflict, setTab, refreshAlerts }) {
  const P = stores.products.data || [], byId = invUseMemo(() => Object.fromEntries(P.map(p => [p.id, p])), [P]);
  const S = stores.settings.data || {};
  const [recent, setRecent] = invUseState(null);
  invUseEffect(() => { invFetchLedger({ limit: 20 }).then(d => setRecent(d.lines || [])).catch(() => setRecent([])); }, [onHand.computedAt]);
  const totals = invUseMemo(() => {
    let value = 0, lenses = 0, lensValue = 0, units = 0;
    for (const [pid, b] of Object.entries(onHand.products)) { const p = byId[pid]; if (!p) continue; for (const l of b.lots) { if (l.onHand <= 0) continue; const v = l.onHand * invLotUnitCost(p, l); value += v; units += l.onHand; if (p.isIol) { lenses += l.onHand; lensValue += v; } } }
    return { value, lenses, lensValue, units };
  }, [onHand, byId]);
  const tile = (label, value, sub, color) => <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.6rem', fontWeight: 700, color: color || 'var(--navy)' }}>{value}</div>{sub && <div style={{ fontSize: '0.76rem', color: 'var(--gray-500)' }}>{sub}</div>}</div></div>;
  const c = alerts ? alerts.counts : null;
  return (
    <div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(170px, 1fr))', gap: 12, marginBottom: 14 }}>
        {tile('Stock value on hand', invMoney(totals.value), `${invFmtQty(totals.units)} units across ${Object.values(onHand.products).filter(b => b.onHand > 0).length} products`)}
        {tile('Lenses on hand', totals.lenses, invMoney(totals.lensValue))}
        {tile('Below PAR', c ? c.belowPar : '…', 'products under their minimum', c && c.belowPar ? 'var(--amber)' : undefined)}
        {tile('Expiring ≤ 30 days', c ? c.expiringSoon + c.expired : '…', c ? `${c.expired} already expired` : '', c && (c.expiringSoon + c.expired) ? 'var(--red)' : undefined)}
        {tile('Price changes', c ? c.priceChanges : '…', 'received cost ≠ catalog', c && c.priceChanges ? 'var(--navy)' : undefined)}
        {tile('Open POs', (stores.purchase_orders && stores.purchase_orders.data || []).filter(p => p.status === 'placed' || p.status === 'partial').length, (() => { const n = (stores.purchase_orders && stores.purchase_orders.data || []).filter(p => p.status === 'partial').length; return n ? `${n} partially received` : 'placed, awaiting delivery'; })())}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14, alignItems: 'start' }}>
        <div className="card" style={{ margin: 0 }}><div className="card-header" style={{ justifyContent: 'space-between' }}><span>Alerts</span><button className="btn btn-outline btn-sm" onClick={refreshAlerts}>Refresh</button></div><div className="card-body"><InvAlertsPanel alerts={alerts} stores={stores} savedOrConflict={savedOrConflict} onChanged={refreshAlerts} /></div></div>
        <div className="card" style={{ margin: 0 }}><div className="card-header">Last 20 movements</div><div className="card-body" style={{ padding: 0 }}>
          {recent === null ? <div style={{ padding: 12, color: 'var(--gray-500)' }}>Loading…</div> : recent.length === 0 ? <div style={{ padding: 12, color: 'var(--gray-500)', fontSize: '0.84rem' }}>No stock movements yet.</div> : (
            <table style={{ width: '100%', borderCollapse: 'collapse' }}><tbody>{recent.map(l => <tr key={l.id}><td style={{ ...invCell, whiteSpace: 'nowrap', fontSize: '0.74rem', color: 'var(--gray-500)' }}>{new Date(l.at).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })}</td><td style={invCell}><InvTag color={l.type === 'received' ? 'var(--green)' : l.type === 'dispensed' ? 'var(--navy)' : l.type === 'adjusted' ? 'var(--amber)' : 'var(--gray-500)'}>{l.type}</InvTag></td><td style={invCell}>{byId[l.productId] ? byId[l.productId].description : l.productId}{l.lot ? <span style={{ color: 'var(--gray-500)', fontSize: '0.74rem' }}> lot {l.lot}</span> : null}{l.serial ? <span style={{ color: 'var(--gray-500)', fontSize: '0.74rem' }}> sn {l.serial}</span> : null}</td><td style={{ ...invCell, textAlign: 'right', fontWeight: 600 }}>{l.type === 'adjusted' && l.qty > 0 ? '+' : ''}{l.type === 'dispensed' || l.type === 'returned' ? '−' : ''}{invFmtQty(Math.abs(l.qty))}</td></tr>)}</tbody></table>
          )}
        </div></div>
      </div>
      <div style={{ marginTop: 12, fontSize: '0.78rem', color: 'var(--gray-500)' }}>{S.seedSource ? <>Master lists loaded from <em>{S.seedSource}</em>. </> : null}Stock numbers come from the ledger — every receipt, use, and adjustment is a line nobody edits. <button className="btn btn-outline btn-sm" onClick={() => setTab('reports')}>Open reports →</button></div>
    </div>
  );
}

// ─── Reports: PAR report and transaction history ─────────────────
function InvReports({ stores, onHand }) {
  const [sub, setSub] = invUseState('par');
  return (
    <div>
      <div style={{ display: 'flex', gap: 6, marginBottom: 12 }}>{[['par', 'PAR report'], ['transactions', 'Transactions']].map(([id, l]) => <button key={id} className={`btn btn-sm ${sub === id ? 'btn-teal' : 'btn-outline'}`} onClick={() => setSub(id)}>{l}</button>)}</div>
      {sub === 'par' && <InvParReport stores={stores} onHand={onHand} />}
      {sub === 'transactions' && <InvTransactions stores={stores} />}
    </div>
  );
}
function InvParReport({ stores, onHand }) {
  const P = stores.products.data || [];
  const [onlyShort, setOnlyShort] = invUseState(true);
  const rows = invUseMemo(() => P.filter(p => p.active !== false && Number(p.parMin) > 0).map(p => {
    const b = onHand.products[p.id] || { onHand: 0, reserved: 0, available: 0 };
    const short = Math.max(0, p.parMin - b.available);
    const per = Number(p.unitsPerPurchase) || 1;
    const buy = short > 0 ? Math.ceil(short / per) : 0;
    return { p, b, short, buy, cost: buy * (Number(p.purchasePrice) || 0) };
  }).filter(r => !onlyShort || r.short > 0).sort((a, b) => (b.short / b.p.parMin) - (a.short / a.p.parMin) || a.p.description.localeCompare(b.p.description)), [P, onHand, onlyShort]);
  const bySupplier = invUseMemo(() => { const m = {}; for (const r of rows) (m[r.p.supplier || '— no supplier —'] = m[r.p.supplier || '— no supplier —'] || []).push(r); return m; }, [rows]);
  const exportCsv = () => invDownloadCsv(`par-report-${invToday()}.csv`, [['Supplier', 'Mfr code', 'Description', 'In stock', 'Reserved', 'PAR min', 'Short', 'Buy (purchase units)', 'Purchase unit', 'Est. cost'], ...rows.map(r => [r.p.supplier, r.p.manufacturerCode, r.p.description, r.b.onHand, r.b.reserved, r.p.parMin, r.short, r.buy, r.p.purchaseUom, r.cost.toFixed(2)])]);
  return (
    <div>
      <div style={{ display: 'flex', gap: 10, alignItems: 'center', marginBottom: 10, flexWrap: 'wrap' }}>
        <label style={{ fontSize: '0.82rem', display: 'flex', gap: 6, alignItems: 'center' }}><input type="checkbox" checked={onlyShort} onChange={e => setOnlyShort(e.target.checked)} /> Only items below PAR</label>
        <span style={{ flex: 1 }} />
        <span style={{ fontSize: '0.82rem', color: 'var(--gray-600)' }}>{rows.length} items · est. {invMoney(rows.reduce((n, r) => n + r.cost, 0))}</span>
        <button className="btn btn-outline btn-sm" onClick={exportCsv}>↓ CSV</button>
      </div>
      {Object.keys(bySupplier).sort().map(sup => (
        <div className="card" key={sup} style={{ margin: '0 0 12px' }}>
          <div className="card-header">{sup} · {bySupplier[sup].length}</div>
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead><tr><th style={invTh}>Mfr code</th><th style={invTh}>Description</th><th style={{ ...invTh, textAlign: 'right' }}>In stock</th><th style={{ ...invTh, textAlign: 'right' }}>PAR min</th><th style={{ ...invTh, textAlign: 'right' }}>Short</th><th style={invTh}>Purchase advice</th><th style={{ ...invTh, textAlign: 'right' }}>Est. cost</th></tr></thead>
            <tbody>{bySupplier[sup].map(r => <tr key={r.p.id}><td style={invCell}>{r.p.manufacturerCode}</td><td style={invCell}><strong>{r.p.description}</strong></td><td style={{ ...invCell, textAlign: 'right' }}>{invFmtQty(r.b.available)}{r.b.reserved ? <span style={{ color: 'var(--gray-500)', fontSize: '0.72rem' }}> (+{invFmtQty(r.b.reserved)} res.)</span> : null}</td><td style={{ ...invCell, textAlign: 'right' }}>{r.p.parMin}</td><td style={{ ...invCell, textAlign: 'right', color: r.short ? 'var(--red)' : 'var(--gray-400)', fontWeight: 600 }}>{r.short || '—'}</td><td style={invCell}>{r.buy ? `Buy ${r.buy} ${r.p.purchaseUom}${r.buy === 1 ? '' : 's'}${(Number(r.p.unitsPerPurchase) || 1) > 1 ? ` (${r.buy * r.p.unitsPerPurchase} ${r.p.dispenseUom})` : ''}` : 'OK'}</td><td style={{ ...invCell, textAlign: 'right' }}>{r.buy ? invMoney(r.cost) : '—'}</td></tr>)}</tbody>
          </table>
        </div>
      ))}
      {rows.length === 0 && <div className="card"><div className="card-body" style={{ color: 'var(--green)' }}>✅ Everything with a PAR minimum is at or above it.</div></div>}
    </div>
  );
}
function InvTransactions({ stores }) {
  const P = stores.products.data || [], byId = invUseMemo(() => Object.fromEntries(P.map(p => [p.id, p])), [P]);
  const [f, setF] = invUseState({ from: '', to: '', type: '', q: '' });
  const [data, setData] = invUseState(null);
  const load = () => invFetchLedger({ from: f.from, to: f.to, type: f.type, limit: 2000 }).then(setData).catch(() => setData({ lines: [], total: 0 }));
  invUseEffect(() => { load(); }, [f.from, f.to, f.type]);
  const lines = invUseMemo(() => { const n = invNorm(f.q); return !data ? [] : data.lines.filter(l => !n || invNorm((byId[l.productId] || {}).description).includes(n) || invNorm(l.lot).includes(n) || invNorm(l.serial).includes(n) || invNorm(l.note).includes(n) || invNorm(l.by).includes(n)); }, [data, f.q, byId]);
  const exportCsv = () => invDownloadCsv(`transactions-${invToday()}.csv`, [['When', 'Type', 'Product', 'Qty', 'Unit', 'Lot', 'Serial', 'Expiry', 'Shelf', 'Unit cost', 'By', 'Note'], ...lines.map(l => [l.at, l.type, (byId[l.productId] || {}).description || l.productId, l.qty, l.uom || '', l.lot, l.serial, l.expiry, l.shelf, l.unitCost == null ? '' : l.unitCost, l.by, l.note])]);
  return (
    <div>
      <div style={{ display: 'flex', gap: 8, alignItems: 'end', marginBottom: 10, flexWrap: 'wrap' }}>
        <InvField label="From"><input type="date" value={f.from} onChange={e => setF({ ...f, from: e.target.value })} /></InvField>
        <InvField label="To"><input type="date" value={f.to} onChange={e => setF({ ...f, to: e.target.value })} /></InvField>
        <InvField label="Type"><select value={f.type} onChange={e => setF({ ...f, type: e.target.value })}><option value="">All</option>{['received', 'dispensed', 'adjusted', 'returned', 'reserved', 'released'].map(t => <option key={t}>{t}</option>)}</select></InvField>
        <div style={{ flex: '1 1 220px' }}><InvField label="Search"><input type="text" value={f.q} onChange={e => setF({ ...f, q: e.target.value })} placeholder="product, lot, serial, note, who…" /></InvField></div>
        <button className="btn btn-outline btn-sm" onClick={exportCsv}>↓ CSV</button>
      </div>
      <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}>When</th><th style={invTh}>Type</th><th style={invTh}>Product</th><th style={{ ...invTh, textAlign: 'right' }}>Qty</th><th style={invTh}>Lot / serial</th><th style={invTh}>Shelf</th><th style={{ ...invTh, textAlign: 'right' }}>Unit cost</th><th style={invTh}>By</th><th style={invTh}>Note</th></tr></thead>
            <tbody>{lines.map(l => <tr key={l.id}><td style={{ ...invCell, whiteSpace: 'nowrap' }}>{new Date(l.at).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })}</td><td style={invCell}>{l.type}</td><td style={invCell}>{(byId[l.productId] || {}).description || l.productId}</td><td style={{ ...invCell, textAlign: 'right', fontWeight: 600 }}>{l.type === 'dispensed' || l.type === 'returned' ? '−' : (l.type === 'adjusted' && l.qty > 0 ? '+' : '')}{invFmtQty(Math.abs(l.qty))}</td><td style={invCell}>{l.lot || ''}{l.serial ? ' / ' + l.serial : ''}</td><td style={invCell}>{l.shelf}</td><td style={{ ...invCell, textAlign: 'right' }}>{l.unitCost == null ? '—' : invMoney(l.unitCost)}</td><td style={{ ...invCell, fontSize: '0.76rem' }}>{l.by}</td><td style={{ ...invCell, fontSize: '0.76rem', color: 'var(--gray-600)' }}>{l.note}{l.orderedFor ? ` · for ${l.orderedFor}` : ''}</td></tr>)}
              {data && lines.length === 0 && <tr><td colSpan={9} style={{ ...invCell, textAlign: 'center', color: 'var(--gray-500)', padding: 30 }}>No movements match.</td></tr>}</tbody>
          </table>
        </div>
        {data && <div style={{ padding: '8px 12px', fontSize: '0.78rem', color: 'var(--gray-500)' }}>{lines.length} of {data.total} movements</div>}
      </div>
    </div>
  );
}

// ─── Settings → Stock data: trial-run stock, PAR levels, Heather's export ──
function InvStockData({ stores, savedOrConflict, toast, me, onHand, refreshAll }) {
  const P = stores.products.data || [], S = stores.settings.data || {};
  const [busy, setBusy] = invUseState(null);
  const [preview, setPreview] = invUseState(null);
  const isAdmin = me && me.role === 'admin';
  const hasStock = Object.values(onHand.products).some(b => b.onHand > 0);

  const loadSeed = async () => { const r = await fetch('/inventory-seed.json', { cache: 'no-store' }); if (!r.ok) throw new Error('inventory-seed.json is missing on the server'); return r.json(); };
  const postInBatches = async (lines, label) => { for (let i = 0; i < lines.length; i += 400) { setBusy(`${label} ${Math.min(i + 400, lines.length)} / ${lines.length}…`); await invPostLines(lines.slice(i, i + 400)); } };

  const loadTrialStock = async () => {
    setBusy('Reading trial-run stock…');
    try {
      const seed = await loadSeed(); const os = seed.openingStock || { rows: [], fillIn: null };
      const byId = new Map(P.map(p => [p.id, p]));
      const note = 'Trial-run stock (placeholder until cutover)';
      const lines = os.rows.filter(r => byId.has(r.productId)).map(r => ({ type: 'received', productId: r.productId, qty: r.qty, lot: r.lot, serial: r.serial, expiry: r.expiry, shelf: r.shelf, unitCost: r.unitCost, uom: r.uom, note, ref: { kind: 'import', id: 'trial-run' } }));
      if (os.fillIn) { const stocked = new Set(os.rows.map(r => r.productId)); for (const p of P) if (p.active !== false && !stocked.has(p.id)) lines.push({ type: 'received', productId: p.id, qty: os.fillIn.qty, shelf: p.shelf || 'OR', unitCost: Math.round(invCatalogUnitCost(p) * 10000) / 10000, uom: p.dispenseUom, note: os.fillIn.note, ref: { kind: 'import', id: 'trial-run' } }); }
      await postInBatches(lines, 'Loading');
      await savedOrConflict(stores.settings, { ...(stores.settings.data || {}), trialStockLoadedAt: new Date().toISOString() });
      InvApi.audit('INV_IMPORT', `Trial-run stock loaded: ${lines.length} lines`);
      toast(`Trial-run stock loaded: ${lines.length} lines`); refreshAll();
    } catch (e) { toast('⚠️ ' + e.message, 'error'); }
    setBusy(null);
  };
  const applyPar = async () => {
    setBusy('Applying PAR levels…');
    try {
      const seed = await loadSeed(); const map = new Map((seed.parLevels || []).map(x => [x.productId, x.parMin]));
      let n = 0; const next = P.map(p => { if (map.has(p.id) && Number(p.parMin) !== map.get(p.id)) { n++; return { ...p, parMin: map.get(p.id) }; } return p; });
      if (await savedOrConflict(stores.products, next, `PAR levels applied to ${n} products`)) await savedOrConflict(stores.settings, { ...(stores.settings.data || {}), parLevelsAppliedAt: new Date().toISOString() });
      refreshAll();
    } catch (e) { toast('⚠️ ' + e.message, 'error'); }
    setBusy(null);
  };
  const resetStock = async () => {
    if (!window.confirm('Bring EVERY product to zero on hand (as adjustment lines), so a fresh import can follow? The history is kept; nothing is deleted.')) return;
    setBusy('Resetting stock…');
    try { const r = await fetch('/api/inventory/reset-stock', { method: 'POST', headers: InvApi.headers(), body: JSON.stringify({ note: 'Cutover reset before importing real stock' }) }); const d = await r.json(); if (!r.ok) throw new Error(d.error || 'Reset failed'); toast(`Stock reset: ${d.count} lines`); refreshAll(); }
    catch (e) { toast('⚠️ ' + e.message, 'error'); }
    setBusy(null);
  };

  // Heather's export: read in the browser; only cv_products + cv_inventory are looked at.
  const onExportFile = (e) => {
    const file = e.target.files[0]; if (!file) return;
    const reader = new FileReader();
    reader.onload = () => {
      try {
        const j = JSON.parse(reader.result); const d = j.data || j;
        const herProducts = d.cv_products || [], herInv = d.cv_inventory || [];
        if (!Array.isArray(herInv) || !herProducts.length) throw new Error('This does not look like a Clear Vision backup (no products / inventory inside).');
        const byDesc = new Map(P.map(p => [invNorm(p.description), p]));
        const herById = new Map(herProducts.map(p => [p.id, p]));
        const newProducts = [], lines = [], skipped = [];
        for (const row of herInv) {
          const hp = herById.get(row.product_id); if (!hp) { skipped.push('unknown product ' + row.product_id); continue; }
          if (/test stock|fill-in for trial/i.test(row.notes || '')) { skipped.push('trial filler: ' + hp.description); continue; }
          const qty = Number(row.quantity); if (!(qty > 0)) continue;
          let p = byDesc.get(invNorm(hp.description));
          if (!p) {
            p = { id: invId('P'), description: invNorm(hp.description) ? String(hp.description).trim() : 'Unnamed', manufacturerCode: hp.manufacturer_code || '', manufacturer: hp.manufacturer || '', group: hp.product_group || 'Other', shelf: hp.shelf_location || 'OR', supplier: hp.default_supplier || '', purchaseUom: hp.purchase_uom || 'Each', dispenseUom: hp.dispense_uom || 'Each', unitsPerPurchase: Number(hp.units_per_purchase) || 1, purchasePrice: Number(hp.purchase_price) || 0, retailPrice: 0, upc: hp.upc || '', isIol: !!hp.is_iol, expiryRequired: !!hp.exp_required, isDrug: !!hp.is_drug, isControlled: !!hp.is_controlled, isBuyAndBill: !!hp.is_buy_and_bill, parMin: Number(hp.par_min) || 0, active: hp.active !== false, notes: '' };
            newProducts.push(p); byDesc.set(invNorm(p.description), p);
          }
          lines.push({ type: 'received', productId: p.id, qty, lot: row.lot_number || '', serial: row.serial_number || '', expiry: /^\d{4}-\d{2}-\d{2}$/.test(row.expiration_date || '') ? row.expiration_date : '', shelf: row.shelf || p.shelf, unitCost: Number(row.unit_cost) || Math.round(invCatalogUnitCost(p) * 10000) / 10000, uom: row.uom || p.dispenseUom, orderedFor: p.isIol && row.patient_name ? String(row.patient_name) : '', note: `Imported from Heather's export ${j._exported_at ? j._exported_at.slice(0, 10) : ''}`.trim(), ref: { kind: 'import', id: 'heather-' + (j._exported_at ? j._exported_at.slice(0, 10) : invToday()) } });
        }
        setPreview({ lines, newProducts, skipped, exportedAt: j._exported_at, mode: hasStock ? 'replace' : 'add' });
      } catch (err) { toast('⚠️ ' + err.message, 'error'); }
    };
    reader.readAsText(file); e.target.value = '';
  };
  const applyImport = async () => {
    setBusy('Importing…');
    try {
      if (preview.mode === 'replace') { const r = await fetch('/api/inventory/reset-stock', { method: 'POST', headers: InvApi.headers(), body: JSON.stringify({ note: 'Reset before importing Heather\'s export' }) }); if (!r.ok) throw new Error('Reset failed — is your account an admin?'); }
      if (preview.newProducts.length) { const ok = await savedOrConflict(stores.products, [...P, ...preview.newProducts], `${preview.newProducts.length} products added from the export`); if (!ok) throw new Error('Could not add the new products'); }
      await postInBatches(preview.lines, 'Importing');
      await savedOrConflict(stores.settings, { ...(stores.settings.data || {}), heatherImportAt: new Date().toISOString(), heatherExportDate: preview.exportedAt || null });
      InvApi.audit('INV_IMPORT', `Heather export imported (${preview.mode}): ${preview.lines.length} stock lines, ${preview.newProducts.length} new products`);
      toast(`Imported ${preview.lines.length} stock lines`); setPreview(null); refreshAll();
    } catch (e) { toast('⚠️ ' + e.message, 'error'); }
    setBusy(null);
  };

  const box = (title, body) => <div className="card" style={{ margin: '0 0 12px', maxWidth: 820 }}><div className="card-header">{title}</div><div className="card-body" style={{ fontSize: '0.86rem' }}>{body}</div></div>;
  return (
    <div>
      {busy && <div style={{ background: 'var(--gray-100)', padding: '8px 12px', borderRadius: 8, marginBottom: 12, fontWeight: 600, color: 'var(--teal)' }}>{busy}</div>}
      {box('PAR levels from Heather\'s app', <>
        <p style={{ marginBottom: 8, color: 'var(--gray-600)' }}>Her app carries real PAR minimums for ~170 products (Diazepam 100, 3cc syringes 50, …). Apply them once; after that PAR is edited on each product.</p>
        {S.parLevelsAppliedAt ? <span style={{ color: 'var(--green)' }}>✅ Applied {new Date(S.parLevelsAppliedAt).toLocaleString()}</span> : <button className="btn btn-primary" disabled={!!busy} onClick={applyPar}>Apply Heather's PAR levels</button>}
      </>)}
      {box('Trial-run stock (placeholder numbers)', <>
        <p style={{ marginBottom: 8, color: 'var(--gray-600)' }}>Loads the demo lots from Heather's file plus 15 of every other product, so every screen can be tried with numbers in it. It is replaced by her real stock at cutover (below).</p>
        {S.trialStockLoadedAt ? <span style={{ color: 'var(--green)' }}>✅ Loaded {new Date(S.trialStockLoadedAt).toLocaleString()}</span> : <button className="btn btn-primary" disabled={!!busy} onClick={loadTrialStock}>Load trial-run stock</button>}
      </>)}
      {box('Import Heather\'s real stock (her export file)', <>
        <p style={{ marginBottom: 8, color: 'var(--gray-600)' }}>In her app: Settings → Backup / Restore → Download backup file. Pick that file here. It is read on this computer; only the products and stock rows are used, and only those are sent to SurgSuite's server — patients, charts and everything else in the file are ignored. Trial-filler rows in her file are skipped.</p>
        {S.heatherImportAt && <div style={{ color: 'var(--green)', marginBottom: 6 }}>✅ Last imported {new Date(S.heatherImportAt).toLocaleString()} (export dated {S.heatherExportDate ? S.heatherExportDate.slice(0, 10) : '?'})</div>}
        <input type="file" accept=".json,application/json" onChange={onExportFile} disabled={!!busy} />
        {preview && (
          <div style={{ marginTop: 10, border: '1px solid var(--gray-200)', borderRadius: 8, padding: 10 }}>
            <div><strong>{preview.lines.length}</strong> stock rows to load, <strong>{preview.newProducts.length}</strong> products not yet in the catalog (will be added), {preview.skipped.length} rows skipped.</div>
            <div style={{ margin: '8px 0', display: 'flex', gap: 12, fontSize: '0.82rem' }}>
              <label style={{ display: 'flex', gap: 5 }}><input type="radio" checked={preview.mode === 'add'} onChange={() => setPreview({ ...preview, mode: 'add' })} /> Add to what's on hand</label>
              <label style={{ display: 'flex', gap: 5 }}><input type="radio" checked={preview.mode === 'replace'} onChange={() => setPreview({ ...preview, mode: 'replace' })} disabled={!isAdmin} /> Replace: zero everything first, then load{!isAdmin ? ' (admin only)' : ''}</label>
            </div>
            {preview.skipped.slice(0, 5).map((s, i) => <div key={i} style={{ fontSize: '0.74rem', color: 'var(--gray-500)' }}>skipped: {s}</div>)}
            <div className="modal-actions" style={{ justifyContent: 'flex-start', marginTop: 8 }}><button className="btn btn-primary" disabled={!!busy} onClick={applyImport}>Import</button><button className="btn btn-outline" onClick={() => setPreview(null)}>Cancel</button></div>
          </div>
        )}
      </>)}
      {isAdmin && box('Reset stock to zero (cutover only)', <>
        <p style={{ marginBottom: 8, color: 'var(--gray-600)' }}>Writes an adjustment for every lot so on-hand becomes zero everywhere. Use only right before importing real numbers. Nothing is deleted — the history stays.</p>
        <button className="btn btn-danger btn-sm" disabled={!!busy} onClick={resetStock}>Reset all stock to zero</button>
      </>)}
    </div>
  );
}

Object.assign(window.SurgSuiteInventory, { InvReceiveModal, InvAdjustModal, InvStockRows, InvLensesOnHand, InvAlertsPanel, InvDashboardV1, InvReports, InvStockData, useInvOnHand, useInvAlerts });
