// ═══════════════════════════════════════════════════════════════
// inventory-po.jsx — purchase orders and receiving (Inventory Step 5)
// ═══════════════════════════════════════════════════════════════
//
// WHAT THIS IS (in plain English):
// The ordering side of the Stock Room. A purchase order (PO) is a list of
// what we asked a supplier for. It moves Creating → Order Placed →
// Partially Received → Completed (or Cancelled). Lines can be added by
// search/scan, pulled from the PAR report's purchase advice, or pasted in
// from a vendor's order confirmation (Henry Schein and Medline formats are
// recognized exactly; anything else goes through a generic "description
// qty price" reader). Vendor wording you link to a product once is
// remembered, so the same wording links itself next time.
//
// Receiving against a PO: each line shows ordered / previously received /
// receiving now, plus shelf, lot, expiry, serial; "+ line" splits a delivery
// across lots. Approving writes "received" ledger lines tagged with the PO
// number and advances the PO's status.
//
// Data: the `purchase_orders` drawer (an array of POs, each with its lines and
// receipts); vendor aliases and per-supplier PO codes live in `settings`.
// Ported from Heather's app (poAutoNumber, parseHscheinOrderConfirmation,
// parseMedlineOrderDetails, parseGenericOrderText, poRememberAlias).
// ═══════════════════════════════════════════════════════════════

const PO_STATUS = {
  creating: { label: 'Creating', color: 'var(--gray-500)' },
  placed: { label: 'Order Placed', color: 'var(--navy)' },
  partial: { label: 'Partially Received', color: 'var(--amber)' },
  completed: { label: 'Completed', color: 'var(--green)' },
  cancelled: { label: 'Cancelled', color: 'var(--red)' },
};
const poMoney = (n) => '$' + (Number(n) || 0).toFixed(2);
const poParseMoney = (s) => Number(String(s || '').replace(/[$,]/g, '')) || 0;

// ─── PO number: <supplier code or initial><M><DD><YY><creator initials>, e.g. A91126HD ───
const PO_DEFAULT_CODES = { bvi: 'BVI', 'crestpoint management': 'Crest', crestpoint: 'Crest', medline: 'Med' }; // Heather's overrides; Settings → poSupplierCodes adds more
function poAutoNumber(supplier, codes, userName, existing) {
  const all = { ...PO_DEFAULT_CODES }; for (const [k, v] of Object.entries(codes || {})) all[invNorm(k)] = v;
  const code = all[invNorm(supplier)] || (String(supplier || '').trim().charAt(0) || 'X');
  const d = new Date();
  const datePart = (d.getMonth() + 1) + String(d.getDate()).padStart(2, '0') + String(d.getFullYear()).slice(-2);
  const initials = String(userName || '').split(/[\s@.]+/).filter(Boolean).slice(0, 2).map(w => w.charAt(0)).join('') || 'SS';
  const base = (code + datePart + initials).toUpperCase();
  return poUniqueNumber(base, existing);
}
function poUniqueNumber(base, existing, excludeId) {
  const taken = new Set((existing || []).filter(p => p.id !== excludeId).map(p => invNorm(p.number)));
  let n = base, i = 2;
  while (taken.has(invNorm(n))) { n = `${base}-${i}`; i++; }
  return n;
}

// ─── Order-text parsers (from Heather's app) ─────────────────────
function poParseHenrySchein(text) {
  const lines = text.split('\n').map(l => l.trim()).filter(Boolean);
  const priceAnchor = /^\d{1,2}\/\d{1,2}\/\d{2,4}\s+\$?([\d,]+\.\d{2})\s+(\d+)\s+\$?([\d,]+\.\d{2})\s*$/;
  const codeAnchor = /^(\d{4,})\s+(.+?)\s+\(([^)]+)\)\s*$/;
  const out = [];
  for (let i = 0; i < lines.length; i++) {
    const pm = lines[i].match(priceAnchor); if (!pm) continue;
    const unitPrice = poParseMoney(pm[1]), qty = parseInt(pm[2], 10) || 1;
    const descParts = []; let code = '';
    for (let j = i + 1; j < lines.length; j++) {
      if (priceAnchor.test(lines[j])) break;
      if (/^(SUBTOTAL|Tax|Shipping|Order Total)/i.test(lines[j])) break;
      const cm = lines[j].match(codeAnchor); if (cm) { code = cm[1]; continue; }
      if (/^[A-Z]{2,4}$/.test(lines[j])) continue;
      if (/^(Contract|-)$/.test(lines[j])) continue;
      if (/^https?:\/\//i.test(lines[j]) || /^\d{1,2}\/\d{1,2}\/\d{2,4},?\s+\d{1,2}:\d{2}\s*(AM|PM)/i.test(lines[j])) continue;
      descParts.push(lines[j]);
    }
    const description = descParts.join(' ').trim();
    if (description) out.push({ description, code, qty, unitPrice });
  }
  return out;
}
function poParseMedline(text) {
  const lines = text.split('\n'); const out = [];
  const itemRe = /^(\d+)\s+([A-Z0-9]+)\s+(?:C\s+)?(.+?)\s+((?:BX|CS|CA|PK|DZ|CT)\s+of\s+\d+\s+EA|EA)\s+(\d+)\s+\$?([\d,]+\.\d{2})\s+\$?([\d,]+\.\d{2})\s+(.+)$/;
  const stopRe = /^(Pending:|Disclaimer|All sales|HCPCS Code)/i;
  for (let i = 0; i < lines.length; i++) {
    const m = lines[i].match(itemRe); if (!m) continue;
    let desc = m[3].trim(); const code = m[2], qty = parseInt(m[5], 10) || 1, unitPrice = poParseMoney(m[6]);
    for (let j = i + 1; j < Math.min(i + 3, lines.length); j++) { const next = (lines[j] || '').trim(); if (!next || itemRe.test(next) || stopRe.test(next)) break; desc = (desc + ' ' + next).trim(); }
    out.push({ description: desc, code, qty, unitPrice, uomText: m[4] });
  }
  return out;
}
function poParseGeneric(text) {
  const skip = /^(sub\s*total|order\s*total|grand\s*total|total|tax|shipping|freight|discount|order\s*#|invoice|date|page|thank you|terms|balance|payment|phone|fax|email|www\.|http|bill\s*to|ship\s*to|qty|quantity|description|item)/i;
  const dateOnly = /^\d{1,2}\/\d{1,2}\/\d{2,4}\b/;
  const out = [];
  for (const raw of text.split('\n')) {
    const line = raw.trim();
    if (!line || line.length < 4 || skip.test(line) || dateOnly.test(line)) continue;
    let m = line.match(/^(.+?)\s+(\d+(?:\.\d+)?)\s+\$?([\d,]+\.\d{2})\s+\$?([\d,]+\.\d{2})\s*$/);
    if (m) { out.push({ description: m[1].trim(), code: '', qty: parseFloat(m[2]) || 1, unitPrice: poParseMoney(m[3]) }); continue; }
    m = line.match(/^(.+?)\s+(\d+(?:\.\d+)?)\s+\$?([\d,]+\.\d{2})\s*$/);
    if (m) { out.push({ description: m[1].trim(), code: '', qty: parseFloat(m[2]) || 1, unitPrice: poParseMoney(m[3]) }); continue; }
    m = line.match(/^(.+?)\s+\$?([\d,]+\.\d{2})\s*$/);
    if (m && /[a-zA-Z]{3,}/.test(m[1])) { out.push({ description: m[1].trim(), code: '', qty: 1, unitPrice: poParseMoney(m[2]) }); continue; }
    const cells = line.split(/\s{2,}|\t+/).map(c => c.trim()).filter(Boolean);
    if (cells.length < 2) continue;
    const nums = [], descParts = [];
    for (const c of cells) { const bare = c.replace(/^\$/, '').replace(/,/g, '').replace(/%$/, ''); if (/^\d+(\.\d+)?$/.test(bare)) nums.push(bare); else descParts.push(c); }
    if (!descParts.length || !nums.length) continue;
    const description = descParts.join(' ').trim();
    if (description.length < 3 || !/[a-zA-Z]{3,}/.test(description)) continue;
    const prices = nums.filter(n => n.includes('.')), ints = nums.filter(n => !n.includes('.'));
    out.push({ description, code: '', qty: ints.length ? parseInt(ints[0], 10) || 1 : 1, unitPrice: prices.length ? Number(prices[0]) : 0 });
  }
  return out;
}
function poParseOrderText(text) {
  const t = String(text || '');
  if (/order confirmation/i.test(t) && /henry schein/i.test(t)) { const r = poParseHenrySchein(t); if (r.length) return { vendor: 'Henry Schein', lines: r }; }
  if (/medline/i.test(t) && /(order details|standard shipping items)/i.test(t)) { const r = poParseMedline(t); if (r.length) return { vendor: 'Medline', lines: r }; }
  return { vendor: 'generic', lines: poParseGeneric(t) };
}

// ─── Linking vendor wording to catalog products ──────────────────
function poAliasLookup(aliases, desc, code) {
  const d = invNorm(desc), c = invNorm(code);
  return (aliases || []).find(a => (c && a.codeKey === c) || (d && a.descKey === d)) || null;
}
function poAliasRemember(aliases, desc, code, productId) {
  const d = invNorm(desc), c = invNorm(code); if (!d && !c) return aliases || [];
  const list = (aliases || []).filter(a => !((c && a.codeKey === c) || (d && a.descKey === d)));
  list.push({ descKey: d, codeKey: c, productId, updatedAt: new Date().toISOString() });
  return list.slice(-2000);
}
// Fuzzy: token overlap between the vendor's wording and each product's description / code.
function poSuggest(products, desc, code, n) {
  const words = invNorm(desc).split(/[^a-z0-9.%]+/).filter(w => w.length >= 3);
  const c = invNorm(code);
  const scored = [];
  for (const p of products) {
    if (p.active === false) continue;
    let s = 0;
    if (c && (invNorm(p.manufacturerCode) === c || invNorm(p.upc) === c)) s += 100;
    const pd = invNorm(p.description) + ' ' + invNorm(p.manufacturerCode);
    for (const w of words) if (pd.includes(w)) s += w.length;
    if (s > 0) scored.push([s, p]);
  }
  return scored.sort((a, b) => b[0] - a[0]).slice(0, n || 3).map(x => x[1]);
}
// Exact match first (description or code), then a remembered alias, else null + suggestions.
function poLinkLine(products, aliases, desc, code) {
  const d = invNorm(desc), c = invNorm(code);
  let p = products.find(x => x.active !== false && (invNorm(x.description) === d || (c && (invNorm(x.manufacturerCode) === c || invNorm(x.upc) === c))));
  if (p) return { product: p, how: 'exact' };
  const a = poAliasLookup(aliases, desc, code);
  if (a) { p = products.find(x => x.id === a.productId); if (p) return { product: p, how: 'remembered' }; }
  return { product: null, how: 'none', suggestions: poSuggest(products, desc, code, 3) };
}

// ─── Screens ─────────────────────────────────────────────────────
function InvPurchaseOrders({ stores, savedOrConflict, toast, onHand, refreshAll, me }) {
  const POS = stores.purchase_orders.data || [], P = stores.products.data || [], S = stores.settings.data || {};
  const byId = invUseMemo(() => Object.fromEntries(P.map(p => [p.id, p])), [P]);
  const [statusFilter, setStatusFilter] = invUseState(() => ({ creating: true, placed: true, partial: true, completed: false, cancelled: false }));
  const [q, setQ] = invUseState('');
  const [openId, setOpenId] = invUseState(null);
  const [receivingId, setReceivingId] = invUseState(null);
  const open = POS.find(p => p.id === openId) || null;
  const persist = (next, msg) => savedOrConflict(stores.purchase_orders, next, msg);
  const userName = (me && (me.name || me.email)) || '';

  const newPO = async (preset) => {
    const supplier = (preset && preset.supplier) || '';
    const po = { id: invId('PO'), number: supplier ? poAutoNumber(supplier, S.poSupplierCodes, userName, POS) : '', supplier, status: 'creating', createdAt: new Date().toISOString(), createdBy: userName, expectedDate: '', notes: (preset && preset.notes) || '', lines: (preset && preset.lines) || [] };
    if (await persist([...POS, po], 'PO created')) setOpenId(po.id);
  };
  const list = POS.filter(p => statusFilter[p.status]).filter(p => { const n = invNorm(q); return !n || invNorm(p.number).includes(n) || invNorm(p.supplier).includes(n) || (p.lines || []).some(l => invNorm(l.description).includes(n)); }).sort((a, b) => (b.createdAt || '').localeCompare(a.createdAt || ''));
  const total = (po) => (po.lines || []).reduce((n, l) => n + (Number(l.qty) || 0) * (Number(l.unitCost) || 0), 0);
  const received = (po) => (po.lines || []).reduce((n, l) => n + (Number(l.receivedQty) || 0), 0);
  const ordered = (po) => (po.lines || []).reduce((n, l) => n + (Number(l.qty) || 0), 0);

  if (open) return <InvPOEditor po={open} stores={stores} byId={byId} persist={persist} toast={toast} onHand={onHand} refreshAll={refreshAll} userName={userName} onBack={() => setOpenId(null)} onReceive={() => setReceivingId(open.id)} receiving={receivingId === open.id} onReceiveClose={() => setReceivingId(null)} />;

  return (
    <div>
      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginBottom: 10 }}>
        <div style={{ flex: '1 1 260px' }}><input type="text" value={q} onChange={e => setQ(e.target.value)} placeholder="Search PO number, supplier, item…" /></div>
        <button className="btn btn-primary" onClick={() => newPO()}>+ New PO</button>
      </div>
      <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', marginBottom: 12, fontSize: '0.8rem' }}>
        {Object.entries(PO_STATUS).map(([k, v]) => <label key={k} style={{ display: 'flex', gap: 5, alignItems: 'center' }}><input type="checkbox" checked={!!statusFilter[k]} onChange={e => setStatusFilter({ ...statusFilter, [k]: e.target.checked })} /> <span style={{ color: v.color, fontWeight: 600 }}>{v.label}</span> ({POS.filter(p => p.status === k).length})</label>)}
      </div>
      <div className="card" style={{ margin: 0 }}>
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead><tr><th style={invTh}>PO #</th><th style={invTh}>Supplier</th><th style={invTh}>Status</th><th style={invTh}>Created</th><th style={{ ...invTh, textAlign: 'right' }}>Lines</th><th style={{ ...invTh, textAlign: 'right' }}>Received</th><th style={{ ...invTh, textAlign: 'right' }}>Total</th><th style={invTh}>Expected</th></tr></thead>
          <tbody>
            {list.map(po => (
              <tr key={po.id} onClick={() => setOpenId(po.id)} style={{ cursor: 'pointer' }}>
                <td style={invCell}><strong>{po.number || '(no number)'}</strong></td><td style={invCell}>{po.supplier || '—'}</td>
                <td style={invCell}><InvTag color={PO_STATUS[po.status].color}>{PO_STATUS[po.status].label}</InvTag></td>
                <td style={invCell}>{po.createdAt ? new Date(po.createdAt).toLocaleDateString() : ''}<div style={{ fontSize: '0.7rem', color: 'var(--gray-500)' }}>{po.createdBy}</div></td>
                <td style={{ ...invCell, textAlign: 'right' }}>{(po.lines || []).length}</td>
                <td style={{ ...invCell, textAlign: 'right' }}>{received(po)} / {ordered(po)}</td>
                <td style={{ ...invCell, textAlign: 'right' }}>{poMoney(total(po))}</td>
                <td style={invCell}>{po.expectedDate || '—'}</td>
              </tr>))}
            {list.length === 0 && <tr><td colSpan={8} style={{ ...invCell, textAlign: 'center', color: 'var(--gray-500)', padding: 30 }}>No purchase orders match. Create one, or build one from the PAR report.</td></tr>}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function InvPOEditor({ po, stores, byId, persist, toast, onHand, refreshAll, userName, onBack, onReceive, receiving, onReceiveClose }) {
  const POS = stores.purchase_orders.data || [], P = stores.products.data || [], S = stores.settings.data || {};
  const suppliers = (stores.suppliers.data || []).filter(s => s.active !== false).map(s => s.name).sort();
  const [draft, setDraft] = invUseState(() => JSON.parse(JSON.stringify(po)));
  invUseEffect(() => { setDraft(JSON.parse(JSON.stringify(po))); }, [po.id, stores.purchase_orders.version]);
  const dirty = JSON.stringify(draft) !== JSON.stringify(po);
  const editable = draft.status === 'creating' || draft.status === 'placed';
  const [paste, setPaste] = invUseState(''); const [pasteOpen, setPasteOpen] = invUseState(false);
  const upd = (patch) => setDraft(d => ({ ...d, ...patch }));
  const updLine = (key, patch) => setDraft(d => ({ ...d, lines: d.lines.map(l => l.key === key ? { ...l, ...patch } : l) }));
  const save = async (extra, msg) => { const next = { ...draft, ...(extra || {}) }; if (next.supplier && !next.number) next.number = poAutoNumber(next.supplier, S.poSupplierCodes, userName, POS); next.number = poUniqueNumber(next.number || 'PO', POS, next.id); const ok = await persist(POS.map(p => p.id === next.id ? next : p), msg || 'PO saved'); if (ok) setDraft(next); return ok; };
  const lineFromProduct = (p, qty, unitCost, extra) => ({ key: invId('L'), productId: p.id, description: p.description, code: p.manufacturerCode || '', qty: qty || 1, uom: p.purchaseUom, unitCost: unitCost != null ? unitCost : (Number(p.purchasePrice) || 0), receivedQty: 0, receipts: [], ...(extra || {}) });
  const addProduct = (p) => setDraft(d => d.lines.some(l => l.productId === p.id) ? { ...d, lines: d.lines.map(l => l.productId === p.id ? { ...l, qty: Number(l.qty) + 1 } : l) } : { ...d, lines: [...d.lines, lineFromProduct(p)] });
  const total = draft.lines.reduce((n, l) => n + (Number(l.qty) || 0) * (Number(l.unitCost) || 0), 0);

  // From the PAR report: every product of this supplier that is short, with its purchase advice.
  const addFromPar = () => {
    if (!draft.supplier) { toast('⚠️ Pick a supplier first.', 'error'); return; }
    const added = [];
    for (const p of P) {
      if (p.active === false || !(Number(p.parMin) > 0) || invNorm(p.supplier) !== invNorm(draft.supplier)) continue;
      const b = onHand.products[p.id] || { available: 0 }; const short = p.parMin - b.available; if (short <= 0) continue;
      const buy = Math.ceil(short / (Number(p.unitsPerPurchase) || 1));
      if (draft.lines.some(l => l.productId === p.id)) continue;
      added.push(lineFromProduct(p, buy, null, { note: `PAR: ${invFmtQty(b.available)} of ${p.parMin} on hand` }));
    }
    if (!added.length) { toast('Nothing from this supplier is below PAR.'); return; }
    setDraft(d => ({ ...d, lines: [...d.lines, ...added] })); toast(`${added.length} short item${added.length === 1 ? '' : 's'} added from the PAR report`);
  };
  // Paste the vendor's order text: parse, link, suggest.
  const applyPaste = () => {
    const parsed = poParseOrderText(paste);
    if (!parsed.lines.length) { toast('⚠️ Could not find any order lines in that text.', 'error'); return; }
    const aliases = S.poVendorAliases || [];
    const lines = parsed.lines.map(pl => {
      const link = poLinkLine(P, aliases, pl.description, pl.code);
      const p = link.product;
      return { key: invId('L'), productId: p ? p.id : null, description: p ? p.description : pl.description, vendorText: pl.description, code: pl.code || '', qty: pl.qty, uom: p ? p.purchaseUom : (pl.uomText || 'Each'), unitCost: pl.unitPrice, receivedQty: 0, receipts: [], linkedHow: link.how, suggestions: link.suggestions ? link.suggestions.map(s => s.id) : [] };
    });
    setDraft(d => ({ ...d, lines: [...d.lines, ...lines] })); setPaste(''); setPasteOpen(false);
    const linked = lines.filter(l => l.productId).length, rem = lines.filter(l => l.linkedHow === 'remembered').length;
    toast(`Added ${lines.length} line(s) — ${linked} linked automatically${rem ? ` (${rem} from wording you confirmed before)` : ''}${lines.length - linked ? `, ${lines.length - linked} need a product` : ''}`);
  };
  const linkLine = async (l, p) => {
    updLine(l.key, { productId: p.id, description: p.description, uom: l.uom || p.purchaseUom, linkedHow: 'confirmed', suggestions: [] });
    if (l.vendorText || l.code) await savedOrConflictSettings({ ...S, poVendorAliases: poAliasRemember(S.poVendorAliases, l.vendorText, l.code, p.id) });
  };
  const savedOrConflictSettings = (next) => stores.settings.save(next);
  const setStatus = async (status) => {
    const patch = { status }; if (status === 'placed' && !draft.placedAt) patch.placedAt = new Date().toISOString();
    if (status === 'placed' && draft.lines.some(l => !l.productId)) { toast('⚠️ Every line needs a product before the order is placed (so it can be received).', 'error'); return; }
    if (await save(patch, `PO ${PO_STATUS[status].label}`)) InvApi.audit('INV_PO', `PO ${draft.number} → ${status}`);
  };
  const [pickFor, setPickFor] = invUseState(null);

  return (
    <div>
      <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 10, flexWrap: 'wrap' }}>
        <button className="btn btn-outline btn-sm" onClick={onBack}>← All POs</button>
        <strong style={{ fontSize: '1.05rem', color: 'var(--navy)' }}>{draft.number || 'New PO'}</strong>
        <InvTag color={PO_STATUS[draft.status].color}>{PO_STATUS[draft.status].label}</InvTag>
        <span style={{ flex: 1 }} />
        {editable && <button className="btn btn-primary btn-sm" disabled={!dirty} onClick={() => save()}>{dirty ? 'Save' : 'Saved'}</button>}
        {draft.status === 'creating' && <button className="btn btn-teal btn-sm" onClick={() => setStatus('placed')}>Mark Order Placed</button>}
        {(draft.status === 'placed' || draft.status === 'partial') && <button className="btn btn-teal btn-sm" disabled={dirty} title={dirty ? 'Save first' : ''} onClick={onReceive}>📦 Receive</button>}
        {editable && <button className="btn btn-danger btn-sm" onClick={() => { if (window.confirm('Cancel this PO?')) setStatus('cancelled'); }}>Cancel PO</button>}
        <button className="btn btn-outline btn-sm" onClick={async () => { const copy = { ...JSON.parse(JSON.stringify(draft)), id: invId('PO'), number: poAutoNumber(draft.supplier, S.poSupplierCodes, userName, POS), status: 'creating', createdAt: new Date().toISOString(), createdBy: userName, placedAt: null, lines: draft.lines.map(l => ({ ...l, key: invId('L'), receivedQty: 0, receipts: [] })) }; if (await persist([...POS, copy], 'PO duplicated')) onBack(); }}>Duplicate</button>
      </div>

      <div className="card" style={{ margin: '0 0 12px' }}>
        <div className="card-body">
          <div className="field-row c4">
            <InvField label="Supplier"><input type="text" list="po-suppliers" value={draft.supplier} disabled={!editable} onChange={e => { const sup = e.target.value; upd({ supplier: sup, number: draft.status === 'creating' && (!draft.number || draft.numberAuto !== false) ? poAutoNumber(sup, S.poSupplierCodes, userName, POS) : draft.number }); }} placeholder="Type to pick…" /><datalist id="po-suppliers">{suppliers.map(s => <option key={s} value={s} />)}</datalist></InvField>
            <InvField label="PO number"><input type="text" value={draft.number} disabled={!editable} onChange={e => upd({ number: e.target.value, numberAuto: false })} /></InvField>
            <InvField label="Expected delivery"><input type="date" value={draft.expectedDate || ''} disabled={!editable} onChange={e => upd({ expectedDate: e.target.value })} /></InvField>
            <InvField label="Invoice #"><input type="text" value={draft.invoiceNumber || ''} onChange={e => upd({ invoiceNumber: e.target.value })} /></InvField>
          </div>
          <InvField label="Notes"><input type="text" value={draft.notes || ''} disabled={!editable} onChange={e => upd({ notes: e.target.value })} /></InvField>
        </div>
      </div>

      {editable && <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 10, flexWrap: 'wrap' }}>
        <div style={{ flex: '1 1 300px' }}><InvProductPicker products={P} onPick={addProduct} placeholder="Add a line: search or scan…" /></div>
        <button className="btn btn-outline btn-sm" onClick={addFromPar}>⚡ Add short items from PAR</button>
        <button className="btn btn-outline btn-sm" onClick={() => setPasteOpen(o => !o)}>📋 Paste order text</button>
      </div>}
      {pasteOpen && <div className="card" style={{ margin: '0 0 12px' }}><div className="card-body">
        <div style={{ fontSize: '0.8rem', color: 'var(--gray-600)', marginBottom: 6 }}>Copy the vendor's order confirmation (the page or the email) and paste it here. Henry Schein and Medline confirmations are read exactly; anything else is read as "description, quantity, price" lines.</div>
        <textarea rows={8} value={paste} onChange={e => setPaste(e.target.value)} placeholder="Paste here…" />
        <div className="modal-actions" style={{ justifyContent: 'flex-start', marginTop: 8 }}><button className="btn btn-primary btn-sm" onClick={applyPaste}>Read lines</button><button className="btn btn-outline btn-sm" onClick={() => setPasteOpen(false)}>Close</button></div>
      </div></div>}

      <div className="card" style={{ margin: 0 }}>
        <table style={{ width: '100%', borderCollapse: 'collapse' }}>
          <thead><tr><th style={invTh}>Item</th><th style={{ ...invTh, width: 90 }}>Qty</th><th style={{ ...invTh, width: 90 }}>Unit</th><th style={{ ...invTh, width: 110 }}>Unit cost</th><th style={{ ...invTh, width: 100, textAlign: 'right' }}>Line total</th><th style={{ ...invTh, width: 110, textAlign: 'right' }}>Received</th><th style={{ ...invTh, width: 40 }}></th></tr></thead>
          <tbody>
            {draft.lines.map(l => { const p = l.productId ? byId[l.productId] : null; return (
              <InvReact.Fragment key={l.key}>
                <tr>
                  <td style={invCell}>
                    {p ? <><strong>{p.description}</strong> <InvTag color="var(--green)">Linked{l.linkedHow === 'remembered' ? ' (remembered)' : ''}</InvTag>{l.vendorText && invNorm(l.vendorText) !== invNorm(p.description) ? <div style={{ fontSize: '0.7rem', color: 'var(--gray-500)' }}>vendor wording: {l.vendorText}</div> : null}</>
                      : <><strong>{l.description}</strong> <InvTag color="var(--amber)">Needs product</InvTag>{l.code ? <div style={{ fontSize: '0.7rem', color: 'var(--gray-500)' }}>code {l.code}</div> : null}</>}
                    {l.note && <div style={{ fontSize: '0.7rem', color: 'var(--gray-500)' }}>{l.note}</div>}
                  </td>
                  <td style={invCell}><input type="number" min="0" value={l.qty} disabled={!editable} onChange={e => updLine(l.key, { qty: Number(e.target.value) })} /></td>
                  <td style={invCell}><input type="text" value={l.uom || ''} disabled={!editable} onChange={e => updLine(l.key, { uom: e.target.value })} /></td>
                  <td style={invCell}><input type="number" step="0.01" min="0" value={l.unitCost} disabled={!editable} onChange={e => updLine(l.key, { unitCost: Number(e.target.value) })} /></td>
                  <td style={{ ...invCell, textAlign: 'right' }}>{poMoney((Number(l.qty) || 0) * (Number(l.unitCost) || 0))}</td>
                  <td style={{ ...invCell, textAlign: 'right', color: l.receivedQty >= l.qty && l.qty > 0 ? 'var(--green)' : undefined }}>{l.receivedQty || 0} / {l.qty}</td>
                  <td style={invCell}>{editable && !l.receivedQty && <button className="btn btn-danger btn-sm" onClick={() => setDraft(d => ({ ...d, lines: d.lines.filter(x => x.key !== l.key) }))}>✕</button>}</td>
                </tr>
                {!p && editable && <tr><td colSpan={7} style={{ ...invCell, paddingTop: 0, background: 'var(--gray-50)' }}>
                  <div style={{ display: 'flex', gap: 6, alignItems: 'center', flexWrap: 'wrap', fontSize: '0.78rem' }}>
                    <span style={{ color: 'var(--gray-600)' }}>Did you mean:</span>
                    {(l.suggestions || []).map(id => byId[id]).filter(Boolean).map(s => <button key={s.id} className="btn btn-outline btn-sm" style={{ padding: '2px 8px' }} onClick={() => linkLine(l, s)}>{s.description}</button>)}
                    {pickFor === l.key ? <div style={{ flex: '1 1 260px' }}><InvProductPicker products={P} autoFocus onPick={p2 => { linkLine(l, p2); setPickFor(null); }} placeholder="Search the catalog…" /></div> : <button className="btn btn-outline btn-sm" style={{ padding: '2px 8px' }} onClick={() => setPickFor(l.key)}>Search…</button>}
                  </div>
                </td></tr>}
              </InvReact.Fragment>); })}
            {draft.lines.length === 0 && <tr><td colSpan={7} style={{ ...invCell, textAlign: 'center', color: 'var(--gray-500)', padding: 24 }}>No lines yet.</td></tr>}
          </tbody>
          <tfoot><tr><td colSpan={4} style={{ ...invCell, textAlign: 'right', fontWeight: 600 }}>Order total</td><td style={{ ...invCell, textAlign: 'right', fontWeight: 700 }}>{poMoney(total)}</td><td colSpan={2} style={invCell}></td></tr></tfoot>
        </table>
      </div>
      {(po.receipts || []).length > 0 && <div className="card" style={{ margin: '12px 0 0' }}><div className="card-header">Deliveries received</div><div className="card-body" style={{ fontSize: '0.82rem' }}>{po.receipts.map((r, i) => <div key={i} style={{ padding: '4px 0', borderBottom: '1px solid var(--gray-100)' }}>{new Date(r.at).toLocaleString()} · {r.by} · {r.lines} line(s), {r.units} units{r.note ? ' · ' + r.note : ''}</div>)}</div></div>}
      {receiving && <InvPOReceiveModal po={po} stores={stores} byId={byId} persist={persist} toast={toast} refreshAll={refreshAll} userName={userName} onClose={onReceiveClose} />}
    </div>
  );
}

// ─── Receive against a PO ─────────────────────────────────────────
function InvPOReceiveModal({ po, stores, byId, persist, toast, refreshAll, userName, onClose }) {
  const POS = stores.purchase_orders.data || [], P = stores.products.data || [];
  const shelfNames = (stores.shelves.data || []).filter(s => s.active !== false).map(s => s.name);
  const [rows, setRows] = invUseState(() => po.lines.filter(l => l.productId).map(l => { const p = byId[l.productId] || {}; return [{ key: invId('r'), lineKey: l.key, product: p, remaining: Math.max(0, (Number(l.qty) || 0) - (Number(l.receivedQty) || 0)), qty: 0, unit: 'purchase', shelf: p.shelf || 'OR', lot: '', expiry: '', serial: '' }]; }).flat());
  const [note, setNote] = invUseState(''); const [busy, setBusy] = invUseState(false);
  const upd = (key, patch) => setRows(rs => rs.map(r => r.key === key ? { ...r, ...patch } : r));
  const addLot = (row) => setRows(rs => { const i = rs.findIndex(r => r.key === row.key); const copy = { ...row, key: invId('r'), qty: 0, lot: '', expiry: '', serial: '' }; return [...rs.slice(0, i + 1), copy, ...rs.slice(i + 1)]; });
  const fillAll = () => setRows(rs => { const seen = new Set(); return rs.map(r => { if (seen.has(r.lineKey)) return r; seen.add(r.lineKey); return { ...r, qty: r.remaining, unit: 'purchase' }; }); });
  const onScan = (p, scan) => {
    const row = rows.find(r => r.product.id === p.id && !r.serial) || rows.find(r => r.product.id === p.id);
    if (!row) { toast(`⚠️ ${p.description} is not on this PO. Receive it from the Inventory tab instead.`, 'error'); return; }
    if (scan && scan.serial && row.serial && row.serial !== scan.serial) { setRows(rs => { const i = rs.findIndex(r => r.key === row.key); const copy = { ...row, key: invId('r'), qty: 1, unit: 'dispense', lot: scan.lot || '', expiry: scan.expiry || '', serial: scan.serial }; return [...rs.slice(0, i + 1), copy, ...rs.slice(i + 1)]; }); return; }
    upd(row.key, { qty: (scan && scan.serial) ? 1 : Number(row.qty) + 1, unit: (scan && scan.serial) ? 'dispense' : row.unit, lot: (scan && scan.lot) || row.lot, expiry: (scan && scan.expiry) || row.expiry, serial: (scan && scan.serial) || row.serial });
  };
  const dispenseQty = (r) => r.unit === 'purchase' ? Number(r.qty) * (Number(r.product.unitsPerPurchase) || 1) : Number(r.qty);
  const lineOf = (r) => po.lines.find(l => l.key === r.lineKey);
  const approve = async () => {
    const active = rows.filter(r => dispenseQty(r) > 0);
    if (!active.length) { toast('⚠️ Nothing to receive — enter a quantity.', 'error'); return; }
    const needExp = active.find(r => r.product.expiryRequired && !r.expiry); if (needExp) { toast(`⚠️ ${needExp.product.description} needs an expiry date.`, 'error'); return; }
    setBusy(true);
    try {
      const lines = active.map(r => { const l = lineOf(r); const per = Number(r.product.unitsPerPurchase) || 1; return { type: 'received', productId: r.product.id, qty: dispenseQty(r), lot: r.lot, serial: r.serial, expiry: r.expiry, shelf: r.shelf, unitCost: Math.round(((Number(l.unitCost) || 0) / per) * 10000) / 10000, uom: r.product.dispenseUom, note: `PO ${po.number}${note ? ' — ' + note : ''}`, ref: { kind: 'po', id: po.number } }; });
      await invPostLines(lines);
      const now = new Date().toISOString();
      const recByLine = {}; for (const r of active) recByLine[r.lineKey] = (recByLine[r.lineKey] || 0) + (r.unit === 'purchase' ? Number(r.qty) : Number(r.qty) / (Number(r.product.unitsPerPurchase) || 1));
      const newLines = po.lines.map(l => recByLine[l.key] ? { ...l, receivedQty: Math.round(((Number(l.receivedQty) || 0) + recByLine[l.key]) * 1000) / 1000, receipts: [...(l.receipts || []), ...active.filter(r => r.lineKey === l.key).map(r => ({ at: now, by: userName, qty: dispenseQty(r), uom: r.product.dispenseUom, lot: r.lot, serial: r.serial, expiry: r.expiry, shelf: r.shelf }))] } : l);
      const allDone = newLines.every(l => !l.productId || (Number(l.receivedQty) || 0) >= (Number(l.qty) || 0));
      const next = { ...po, lines: newLines, status: allDone ? 'completed' : 'partial', receivedAt: allDone ? now : po.receivedAt, receipts: [...(po.receipts || []), { at: now, by: userName, lines: active.length, units: active.reduce((n, r) => n + dispenseQty(r), 0), note }] };
      if (await persist(POS.map(p => p.id === po.id ? next : p), allDone ? `PO ${po.number} completed — all items received` : `Partial delivery recorded on PO ${po.number}`)) { InvApi.audit('INV_RECEIVE', `PO ${po.number}: ${active.length} line(s) received`); refreshAll(); onClose(); }
    } catch (e) { toast('⚠️ ' + e.message, 'error'); }
    setBusy(false);
  };
  return (
    <InvModal title={`Receive PO ${po.number} — ${po.supplier}`} onClose={onClose} wide>
      <div style={{ fontSize: '0.8rem', color: 'var(--gray-600)', marginBottom: 8 }}>Scan each box (fills lot / expiry / serial and counts one), or type the quantities. Use <strong>+ lot</strong> when part of a line arrives under a different lot number.</div>
      <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
        <div style={{ flex: 1 }}><InvProductPicker products={P} autoFocus onPick={onScan} placeholder="Scan a box on this order…" /></div>
        <button type="button" className="btn btn-outline btn-sm" onClick={fillAll} title="Only if the whole order arrived complete">✓ All items received</button>
      </div>
      <table style={{ width: '100%', borderCollapse: 'collapse' }}>
        <thead><tr><th style={invTh}>Item</th><th style={{ ...invTh, width: 70 }}>Ordered</th><th style={{ ...invTh, width: 70 }}>Before</th><th style={{ ...invTh, width: 150 }}>Receiving now</th><th style={{ ...invTh, width: 100 }}>Shelf</th><th style={{ ...invTh, width: 100 }}>Lot</th><th style={{ ...invTh, width: 130 }}>Expiry</th><th style={{ ...invTh, width: 110 }}>Serial</th><th style={{ ...invTh, width: 50 }}></th></tr></thead>
        <tbody>{rows.map(r => { const l = lineOf(r); const p = r.product; const full = (Number(l.receivedQty) || 0) >= (Number(l.qty) || 0); return (
          <tr key={r.key} style={{ background: full ? 'var(--green-light)' : '' }}>
            <td style={invCell}><strong>{p.description}</strong><div style={{ fontSize: '0.7rem', color: 'var(--gray-500)' }}>{p.manufacturerCode}{full ? ' · ✓ fully received' : ''}</div></td>
            <td style={invCell}>{l.qty} {l.uom}</td>
            <td style={invCell}>{l.receivedQty || '—'}</td>
            <td style={invCell}><div style={{ display: 'flex', gap: 4 }}><input type="number" min="0" value={r.qty} onChange={e => upd(r.key, { qty: e.target.value })} style={{ width: 64 }} /><select value={r.unit} onChange={e => upd(r.key, { unit: e.target.value })}><option value="purchase">{p.purchaseUom}</option><option value="dispense">{p.dispenseUom}</option></select></div>{!full && r.remaining > 0 && <a href="#" style={{ fontSize: '0.7rem' }} onClick={e => { e.preventDefault(); upd(r.key, { qty: r.remaining, unit: 'purchase' }); }}>all remaining ({r.remaining})</a>}</td>
            <td style={invCell}><select value={r.shelf} onChange={e => upd(r.key, { shelf: e.target.value })}>{[...new Set([r.shelf, ...shelfNames])].filter(Boolean).map(s => <option key={s}>{s}</option>)}</select></td>
            <td style={invCell}><input type="text" value={r.lot} onChange={e => upd(r.key, { lot: e.target.value })} /></td>
            <td style={invCell}><input type="date" value={r.expiry} onChange={e => upd(r.key, { expiry: e.target.value })} style={{ borderColor: p.expiryRequired && !r.expiry && dispenseQty(r) > 0 ? 'var(--red)' : undefined }} /></td>
            <td style={invCell}><input type="text" value={r.serial} onChange={e => upd(r.key, { serial: e.target.value })} /></td>
            <td style={invCell}><button type="button" className="btn btn-outline btn-sm" style={{ padding: '2px 6px' }} onClick={() => addLot(r)} title="Another lot for this item">+ lot</button></td>
          </tr>); })}</tbody>
      </table>
      {po.lines.some(l => !l.productId) && <div style={{ fontSize: '0.78rem', color: 'var(--amber)', marginTop: 6 }}>⚠️ {po.lines.filter(l => !l.productId).length} line(s) on this PO have no product and can't be received — link them on the PO first.</div>}
      <div className="field-row c2" style={{ marginTop: 12 }}>
        <InvField label="Note (packing slip #, carrier, …)"><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} onClick={approve}>{busy ? 'Saving…' : '✓ Approve & add to stock'}</button></div>
      </div>
    </InvModal>
  );
}

Object.assign(window.SurgSuiteInventory, { InvPurchaseOrders, poAutoNumber, poUniqueNumber, poParseOrderText, poParseHenrySchein, poParseMedline, poParseGeneric, poLinkLine, poAliasRemember, poSuggest, PO_STATUS });
