// Serial-number manager for a serialized product (0050) — lives inside the
// product edit modal but talks to the API directly (units are records, not
// form fields). All buttons are type="button": this renders inside the
// product <form>, and a bare <button> submits it (the v0.81.2 lesson).
const SerialsPanel = ({ productId, onStockChange }) => {
    const [serials, setSerials] = React.useState([]);
    const [bulk, setBulk]       = React.useState('');
    const [msg, setMsg]         = React.useState('');
    const [busy, setBusy]       = React.useState(false);
    const [showAll, setShowAll] = React.useState(false);

    const load = () => api.getProductSerials(productId).then(setSerials).catch(() => {});
    React.useEffect(() => { load(); }, [productId]);

    const counts = serials.reduce((c, s) => ({ ...c, [s.status]: (c[s.status] || 0) + 1 }), {});
    const visible = showAll ? serials : serials.filter(s => s.status !== 'sold');

    const handleAdd = async () => {
        const list = bulk.split('\n').map(s => s.trim()).filter(Boolean);
        if (!list.length) return;
        setBusy(true); setMsg('');
        try {
            const out = await api.addProductSerials(productId, list);
            setMsg(`Added ${out.added} unit(s)` + (out.skipped.length ? ` — skipped ${out.skipped.length} duplicate(s): ${out.skipped.slice(0, 5).join(', ')}${out.skipped.length > 5 ? '…' : ''}` : ''));
            setBulk('');
            await load(); onStockChange && onStockChange();
        } catch (e) { setMsg(e.message); }
        finally { setBusy(false); }
    };
    const handleRemove = async (s) => {
        if (!await confirmAction(`Remove serial "${s.serial}" from stock?`)) return;
        try { await api.deleteProductSerial(productId, s.id); await load(); onStockChange && onStockChange(); }
        catch (e) { setMsg(e.message); }
    };

    const STATUS_STYLE = {
        in_stock: { label: 'In stock', color: 'var(--accent, #3b82f6)' },
        reserved: { label: 'Reserved', color: 'var(--warning)' },
        sold:     { label: 'Sold',     color: 'var(--text-3)' },
    };

    return (
        <div className="form-group full-width" style={{ border: '1px solid var(--border, #e5e7eb)', borderRadius: '0.5rem', padding: '0.75rem' }}>
            <label className="form-label"><i className="fas fa-barcode" style={{ marginRight: '0.35rem' }}></i>
                Serial Numbers — {counts.in_stock || 0} in stock{counts.reserved ? `, ${counts.reserved} reserved` : ''}{counts.sold ? `, ${counts.sold} sold` : ''}
            </label>
            {msg && <div className="api-success" style={{ margin: '0.35rem 0' }}>{msg}</div>}
            <div style={{ display: 'flex', gap: '0.5rem', alignItems: 'flex-start', marginBottom: '0.5rem' }}>
                <textarea className="form-input" style={{ minHeight: 60, flex: 1, fontFamily: 'monospace' }}
                    value={bulk} onChange={e => setBulk(e.target.value)}
                    placeholder={'One serial per line:\nSN-0001\nSN-0002'} />
                <button type="button" className="btn btn-primary btn-small" disabled={busy || !bulk.trim()} onClick={handleAdd}>
                    <i className="fas fa-plus"></i> Add units
                </button>
            </div>
            {visible.length > 0 && (
                <div style={{ maxHeight: '10rem', overflowY: 'auto', border: '1px solid var(--border, #e5e7eb)', borderRadius: '0.375rem' }}>
                    <table className="data-table" style={{ margin: 0 }}>
                        <tbody>
                            {visible.map(s => {
                                const st = STATUS_STYLE[s.status] || STATUS_STYLE.in_stock;
                                return (
                                    <tr key={s.id}>
                                        <td style={{ fontFamily: 'monospace', fontSize: '0.8125rem' }}>{s.serial}</td>
                                        <td><span className="tag-pill" style={{ background: st.color, color: '#fff', fontSize: '0.65rem' }}>{st.label}</span></td>
                                        <td style={{ fontSize: '0.75rem', color: 'var(--text-3)' }}>{s.invoice_number || ''}</td>
                                        <td style={{ textAlign: 'right' }}>
                                            {s.status === 'in_stock' && (
                                                <button type="button" className="btn-icon-sm danger" title="Remove from stock" onClick={() => handleRemove(s)}><i className="fas fa-times"></i></button>
                                            )}
                                        </td>
                                    </tr>
                                );
                            })}
                        </tbody>
                    </table>
                </div>
            )}
            {(counts.sold || 0) > 0 && (
                <button type="button" className="btn btn-secondary btn-small" style={{ marginTop: '0.4rem' }} onClick={() => setShowAll(v => !v)}>
                    {showAll ? 'Hide sold units' : `Show ${counts.sold} sold unit(s)`}
                </button>
            )}
        </div>
    );
};

// ── Families (sizes) ────────────────────────────────────────────────────────
// "Pepperoni – Large" is a plain product row; the family view is a READ-TIME
// grouping on the " – " suffix (server/lib/catalog.js). Nothing is stored.
const SPLIT_RX = /^(.*\S)\s+[–-]\s+(\S.*)$/;
const splitFamily = (name) => { const m = SPLIT_RX.exec(name || ''); return m ? { base: m[1], size: m[2] } : null; };
const groupFamilies = (rows) => {
    const out = []; const fams = new Map();
    for (const p of rows) {
        const f = splitFamily(p.name);
        if (!f) { out.push({ kind: 'single', key: p.id, row: p, category: p.category, sort: p.sort_order, name: p.name }); continue; }
        const k = `${(p.category || '').toLowerCase()}|${f.base.toLowerCase()}`;
        if (!fams.has(k)) { const g = { kind: 'family', key: 'fam:' + k, base: f.base, rows: [], category: p.category, sort: p.sort_order, name: f.base }; fams.set(k, g); out.push(g); }
        const g = fams.get(k); g.rows.push({ ...p, size: f.size });
        if (g.sort == null || (p.sort_order != null && p.sort_order < g.sort)) g.sort = p.sort_order;
    }
    return out;
};
const money = (n) => '$' + parseFloat(n || 0).toFixed(2);

// Suggestion chips: add-on names the tenant already uses elsewhere in the
// catalog (Extra Cheese / Extra Meat / Extra Sauce…), one click to add.
// Picked-not-typed: the datalist alone only showed after you started typing,
// so staff editing a new pizza never saw that the names already existed.
const OptionChips = ({ known, existing, onAdd }) => {
    const free = known.filter(n => !existing.some(x => x.toLowerCase() === n.toLowerCase())).slice(0, 12); // cap: offer, not a wall
    if (!free.length) return null;
    return (
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.3rem', marginTop: '0.4rem' }}>
            {free.map(n => <button key={n} type="button" className="suggest-chip" onClick={() => onAdd(n)} title={`Add "${n}"`}><i className="fas fa-plus" style={{ fontSize: '0.6rem', marginRight: 4 }}></i>{n}</button>)}
        </div>
    );
};

// Add a row to the add-ons grid: free text, suggestions from names the
// tenant already uses (chips + datalist), Enter to add, no duplicates.
const OptionNameAdd = ({ known, existing, onAdd, menu }) => {
    const [v, setV] = React.useState('');
    const add = () => { const n = v.trim().replace(/\s+/g, ' '); if (!n || existing.some(x => x.toLowerCase() === n.toLowerCase())) return; onAdd(n); setV(''); };
    if (existing.length >= 20) return null;
    return (
        <>
            {existing.length === 0 && <p className="form-hint" style={{ margin: '0 0 0.4rem' }}>
                {menu ? 'Extra cheese, meat, sauce… add a name, then set a price for each size.' : 'Add a name, then set a price for each size.'}</p>}
            <OptionChips known={known} existing={existing} onAdd={onAdd} />
            <div style={{ display: 'flex', gap: '0.35rem', marginTop: '0.4rem', maxWidth: 420 }}>
                <input className="form-input" value={v} maxLength={60} placeholder={menu ? 'New add-on — e.g. Extra Cheese' : 'New add-on — e.g. Extended Warranty'} list="opt-names-fam"
                       onChange={e => setV(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); add(); } }} />
                <datalist id="opt-names-fam">{known.filter(n => !existing.includes(n)).map(n => <option key={n} value={n} />)}</datalist>
                <button type="button" className="btn btn-secondary btn-small" onClick={add} disabled={!v.trim()}><i className="fas fa-plus"></i> Add-on</button>
            </div>
        </>
    );
};

const ProductsView = ({ currentUser, tenant, onTenantChange }) => {
    const [products, setProducts] = React.useState([]);
    const [loading, setLoading]   = React.useState(true);
    const [search, setSearch]     = React.useState('');
    const [editing, setEditing]   = React.useState(null); // null | 'new' | product object
    const [saving, setSaving]     = React.useState(false);
    const [formErr, setFormErr]   = React.useState('');
    const canEdit = currentUser.role !== 'rep';

    // Catalog style (tenant.catalog, server/lib/catalog.js): 'menu' hides the
    // warehouse half of the form and lists items menu-shaped; 'stock' is the
    // classic Inventory. Same rows underneath either way.
    const catalog    = (tenant && tenant.catalog) || { style: 'stock', sizes: [] };
    const menu       = catalog.style === 'menu';
    const sizesVocab = catalog.sizes || [];

    const emptyForm = { sku: '', name: '', description: '', category: '', unit_price: '', unit_cost: '', stock_qty: 0, is_active: true, item_type: 'physical', billing_interval: 'month', tags_on_paid: [], tasks_on_paid: [], is_serialized: false, reorder_point: '', published: menu, sort_order: '', options: [] };
    const [form, setForm] = React.useState(emptyForm);
    // Sizes pricing (new items only): 'one' price, or one row per size.
    const [pricing, setPricing]       = React.useState('one');
    const [sizePrices, setSizePrices] = React.useState({});   // { 'Large': '17.50' }
    // Editing a whole family at once (2026-08-24, Doug's walkthrough): the
    // group from the list, plus which existing rows are marked for removal.
    // Add-ons (0060): single item → form.options [{name, price}]; family →
    // one grid: optNames × sizes, price per cell ('' = not offered on that size).
    const [optNames, setOptNames]   = React.useState([]);        // family grid rows
    const [optGrid, setOptGrid]     = React.useState({});        // { 'Extra Cheese': { Small: '1.25', Large: '' } }
    const [family, setFamily]       = React.useState(null);   // { base, rows:[{id,size,unit_price,...}] }
    const [removeIds, setRemoveIds] = React.useState([]);
    const isAdmin = currentUser.role === 'admin';
    // Inline vocabulary adds (admin): no round trip to Settings and back.
    const [newCat, setNewCat]   = React.useState(null);   // null = closed, '' = open
    const [newSize, setNewSize] = React.useState(null);
    const addCategoryInline = async () => {
        const name = (newCat || '').trim(); if (!name) return;
        try {
            const c = await api.createProductCategory({ name });
            setCategories(cs => [...cs, c].sort((a, b) => a.name.localeCompare(b.name)));
            setForm(p => ({ ...p, category: c.name })); setNewCat(null);
        } catch (e) { setFormErr(e.message); }
    };
    const addSizeInline = async () => {
        const name = (newSize || '').trim(); if (!name) return;
        try {
            const updated = await api.updateTenant({ catalog: { sizes: [...sizesVocab, name] } });
            onTenantChange && onTenantChange(updated); setNewSize(null);
        } catch (e) { setFormErr(e.message); }
    };

    // Controlled vocabularies (0042 rule: classification is picked, never
    // free-typed). Categories + tags are admin-defined in Settings; the form
    // offers exactly those lists.
    const [categories, setCategories] = React.useState([]);
    const [allTags, setAllTags]       = React.useState([]);

    // Tags-on-paid: toggleable chips from the Tags vocabulary, max 5.
    const toggleTag = (name) => setForm(p => {
        const cur = p.tags_on_paid || [];
        if (cur.some(t => t.toLowerCase() === name.toLowerCase()))
            return { ...p, tags_on_paid: cur.filter(t => t.toLowerCase() !== name.toLowerCase()) };
        return cur.length >= 5 ? p : { ...p, tags_on_paid: [...cur, name] };
    });

    // tasks_on_paid (0041) — edited by the shared TaskListEditor (reorder +
    // preset apply, 0062). Presets load once per form mount; a failed load just
    // hides the chips (the list itself still works).
    const [taskPresets, setTaskPresets] = React.useState([]);
    React.useEffect(() => { if (!menu) api.getTaskPresets().then(setTaskPresets).catch(() => setTaskPresets([])); }, []);
    const set = k => e => setForm(p => ({ ...p, [k]: e.target.type === 'checkbox' ? e.target.checked : e.target.value }));
    const [importMsg, setImportMsg] = React.useState('');
    const fileRef = React.useRef(null);

    // Item-type vocabulary (0034) — label + icon per type; the server derives
    // is_subscription from this, the form never sends the legacy flag.
    const TYPES = [
        ['physical',     'Physical',     'fa-box'],
        ['service',      'Service',      'fa-wrench'],
        ['subscription', 'Subscription', 'fa-rotate'],
        ['software',     'Software',     'fa-compact-disc'],
    ];
    const typeLabel = t => (TYPES.find(x => x[0] === t) || TYPES[0])[1];

    // A failed fetch must not render as an empty catalog — surface it (swallowed-error rule).
    const [loadError, setLoadError] = React.useState(false);
    // Server-paged + server-searched (IMP-1): search moved off the client so
    // paging and filtering compose — a match on page 4 is still findable.
    const PAGE = 100;
    const [total, setTotal] = React.useState(0);
    const load = (append) => {
        append = append === true; // guard: load is also an onClick callback (first arg = event)
        return api.getProducts({
            paged: 'true', limit: PAGE, offset: append ? products.length : 0,
            ...(search.trim() ? { search: search.trim() } : {}),
        }).then(({ rows, total }) => {
            setProducts(p => append ? [...p, ...rows] : rows);
            setTotal(total); setLoadError(false);
        }).catch(() => setLoadError(true)).finally(() => setLoading(false));
    };
    React.useEffect(() => {
        const t = setTimeout(() => { load(); }, 250); // debounced — no request per keystroke
        return () => clearTimeout(t);
    }, [search]);
    React.useEffect(() => {
        api.getProductCategories().then(setCategories).catch(() => {});
        api.getTags().then(setAllTags).catch(() => {});
    }, []);

    const startEdit = (p) => {
        setForm(p ? { ...p } : emptyForm); setEditing(p || 'new'); setFormErr('');
        setPricing('one'); setSizePrices({}); setFamily(null); setRemoveIds([]); setNewCat(null); setNewSize(null);
        setOptNames([]); setOptGrid({});
    };
    // Open a sizes family as ONE form — the shape it was entered in. Shared
    // fields come from the first row; each row's price is editable; blank
    // sizes can be filled in to add a row; × marks a row for deletion.
    const startEditFamily = (g) => {
        const first = g.rows[0];
        setForm({ ...emptyForm, name: g.base, category: first.category || '', description: first.description || '',
            published: g.rows.every(r => r.published), sort_order: g.sort ?? '', is_active: g.rows.every(r => r.is_active) });
        const prices = {}; for (const r of g.rows) prices[r.size] = String(r.unit_price);
        setSizePrices(prices); setPricing('sizes'); setFamily(g); setRemoveIds([]);
        const names = []; const grid = {};
        for (const r of g.rows) for (const o of (r.options || [])) {
            const k = names.find(n => n.toLowerCase() === o.name.toLowerCase()) || (names.push(o.name), o.name);
            (grid[k] = grid[k] || {})[r.size] = String(o.price);
        }
        setOptNames(names); setOptGrid(grid);
        setEditing('family'); setFormErr(''); setNewCat(null); setNewSize(null);
    };

    const handleSave = async (e) => {
        e.preventDefault();
        if (!form.name) { setFormErr('Name is required.'); return; }
        setSaving(true); setFormErr('');
        try {
            const data = { ...form, unit_price: parseFloat(form.unit_price) || 0, unit_cost: form.unit_cost ? parseFloat(form.unit_cost) : null, stock_qty: parseInt(form.stock_qty) || 0, sku: form.sku || null };
            delete data.is_subscription;   // server derives it from item_type
            delete data.serial_counts;     // detail-payload extra, not a column
            // Serial tracking is physical-only; quantity is derived from
            // serials, so the form never sends one for serialized items.
            data.is_serialized = form.item_type === 'physical' && !!form.is_serialized;
            if (data.is_serialized) data.stock_qty = 0;
            data.reorder_point = form.reorder_point === '' || form.reorder_point == null ? null : parseInt(form.reorder_point);
            data.published  = !!form.published;
            data.sort_order = form.sort_order === '' || form.sort_order == null ? null : parseInt(form.sort_order);
            // Drop fully blank task rows (an added-then-abandoned row shouldn't 400)
            data.tasks_on_paid = (form.tasks_on_paid || []).filter(t => (t.title || '').trim() || (t.notes || '').trim());
            data.options = (form.options || []).filter(o => (o.name || '').trim());
            if (editing === 'family') {
                // Shared fields → every kept row; price per row; renamed base →
                // every row renamed; new sizes minted via the family door.
                const base = form.name.trim();
                if (splitFamily(base)) { setFormErr('The name can\'t contain " – " — that\'s how sizes are joined.'); setSaving(false); return; }
                const shared = { category: data.category, description: data.description, published: data.published, sort_order: data.sort_order, is_active: data.is_active };
                const keep = family.rows.filter(r => !removeIds.includes(r.id));
                // Removals FIRST: DELETE is the only call here with a stricter
                // role gate (admin) — if it's refused, nothing else has landed
                // yet. (The per-row PATCHes below are still sequential, not one
                // transaction — a mid-loop failure leaves the family half-saved.)
                for (const id of removeIds) await api.deleteProduct(id);
                for (const r of keep) {
                    const price = parseFloat(sizePrices[r.size]);
                    if (isNaN(price)) { setFormErr(`Price for "${r.size}" is required (remove the size instead).`); setSaving(false); return; }
                    await api.updateProduct(r.id, { ...shared, name: `${base} – ${r.size}`, unit_price: price, options: optionsFor(r.size) });
                }
                const fresh = sizesVocab.filter(l => !family.rows.some(r => r.size.toLowerCase() === l.toLowerCase()) && String(sizePrices[l] ?? '').trim() !== '')
                    .map(l => ({ label: l, unit_price: parseFloat(sizePrices[l]), options: optionsFor(l) }));
                if (fresh.length) await api.createProductFamily({ ...shared, name: base, item_type: keep[0]?.item_type || 'physical', sizes: fresh });
            }
            else if (editing === 'new' && pricing === 'sizes') {
                // One row per size (server/lib/catalog.js) — blank price = skip that size.
                const sizes = sizesVocab.filter(l => String(sizePrices[l] ?? '').trim() !== '')
                    .map(l => ({ label: l, unit_price: parseFloat(sizePrices[l]), options: optionsFor(l) }));
                if (!sizes.length) { setFormErr('Enter a price for at least one size.'); setSaving(false); return; }
                delete data.unit_price; delete data.sku; delete data.options;
                await api.createProductFamily({ ...data, sizes });
            }
            else if (editing === 'new') await api.createProduct(data);
            else await api.updateProduct(editing.id, data);
            await load();
            setEditing(null);
        } catch(err) { setFormErr(err.message); }
        finally { setSaving(false); }
    };

    // Grid → [{name, price}] for one size (blank cells dropped).
    const optionsFor = (size) => optNames
        .filter(n => String((optGrid[n] || {})[size] ?? '').trim() !== '')
        .map(n => ({ name: n, price: parseFloat(optGrid[n][size]) }));
    const [rowErr, setRowErr] = React.useState('');
    const handleDelete = async (p) => {
        if (!await confirmAction(`Delete "${p.name}"? This cannot be undone.`)) return;
        try { await api.deleteProduct(p.id); await load(); }
        catch(err) { setRowErr(err.message); }
    };
    const handleStockAdj = async (p, delta) => {
        const newQty = Math.max(0, p.stock_qty + delta);
        try { await api.updateProduct(p.id, { stock_qty: newQty }); await load(); }
        catch(err) { setRowErr(err.message); }
    };
    // Publish / hide — a single row, or every row of a family at once.
    const setPublished = async (rows, val) => {
        try { for (const r of rows) await api.updateProduct(r.id, { published: val }); await load(); }
        catch(err) { setRowErr(err.message); }
    };

    // Serial lookup mode (0050): the search box flips to a warranty-lookup —
    // "whose unit is this?" — against /api/products/serials/search.
    const [serialMode, setSerialMode]       = React.useState(false);
    const [serialResults, setSerialResults] = React.useState([]);
    React.useEffect(() => {
        if (!serialMode) return;
        const q = search.trim();
        if (q.length < 2) { setSerialResults([]); return; }
        const t = setTimeout(() => {
            api.searchSerials(q).then(setSerialResults).catch(() => setSerialResults([]));
        }, 250);
        return () => clearTimeout(t);
    }, [serialMode, search]);

    const [open, setOpen] = React.useState({});   // expanded families (menu list)
    const chip = (on, extra = {}) => ({ ...(on ? { background: 'var(--accent, #3b82f6)', color: '#fff' } : {}), ...extra });
    const noun = menu ? 'menu item' : 'product';

    // ── Menu-shaped list: category sections → families/singles ────────────
    const renderMenuList = () => {
        const groups = groupFamilies(products);
        // Category order = Settings → Product Categories (0064); unordered
        // categories after the ordered ones, alphabetically; blanks last.
        const catRank = new Map(categories.map((c, i) => [c.name.toLowerCase(), c.sort_order == null ? 1e6 + i : c.sort_order]));
        const rank = (name) => name ? (catRank.has(name.toLowerCase()) ? catRank.get(name.toLowerCase()) : 5e6) : 9e6;
        const cmp = (a, b) => {
            const ca = a.category || '', cb = b.category || '';
            if (ca !== cb) { const ra = rank(ca), rb = rank(cb); if (ra !== rb) return ra - rb; return ca.localeCompare(cb); }
            const sa = a.sort == null ? 1e9 : a.sort, sb = b.sort == null ? 1e9 : b.sort;
            if (sa !== sb) return sa - sb;
            return a.name.localeCompare(b.name);
        };
        groups.sort(cmp);
        if (!groups.length) return <div style={{ textAlign: 'center', color: 'var(--text-3)', padding: '2rem' }}>No {noun}s yet</div>;
        const pubBadge = (rows) => {
            const n = rows.filter(r => r.published && r.is_active).length;
            if (n === rows.length) return <span className="status-badge active"><i className="fas fa-globe" style={{ marginRight: '0.3rem' }}></i>Published</span>;
            if (n === 0) return <span className="status-badge inactive">Hidden</span>;
            return <span className="status-badge active" style={{ opacity: 0.7 }}>{n} of {rows.length} published</span>;
        };
        let lastCat = Symbol();
        return (
            <table className="data-table">
                <thead><tr><th>Item</th><th>Price</th><th>Website</th>{canEdit && <th></th>}</tr></thead>
                <tbody>
                    {groups.map(g => {
                        const rows = g.kind === 'family' ? g.rows.slice().sort((a, b) => sizesVocab.indexOf(a.size) - sizesVocab.indexOf(b.size)) : [g.row];
                        const prices = rows.map(r => parseFloat(r.unit_price));
                        const lo = Math.min(...prices), hi = Math.max(...prices);
                        const catRow = g.category !== lastCat ? (lastCat = g.category, true) : false;
                        const isOpen = !!open[g.key];
                        return (
                            <React.Fragment key={g.key}>
                                {catRow && <tr><td colSpan={canEdit ? 4 : 3} style={{ fontWeight: 600, fontSize: '0.75rem', textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--text-3)', paddingTop: '1rem' }}>{g.category || 'Uncategorised'}</td></tr>}
                                <tr className={canEdit ? 'row-clickable' : ''} title={canEdit ? 'Click to edit' : undefined}
                                    onClick={(e) => { if (!canEdit || e.target.closest('button, a')) return; g.kind === 'single' ? startEdit(g.row) : startEditFamily(g); }}>
                                    <td style={{ fontWeight: 500 }}>
                                        {g.name}
                                        {g.kind === 'family' && <button type="button" className="btn-link" style={{ marginLeft: '0.5rem', fontSize: '0.8125rem' }} onClick={() => setOpen(o => ({ ...o, [g.key]: !isOpen }))}>
                                            {rows.length} sizes <i className={`fas fa-chevron-${isOpen ? 'up' : 'down'}`} style={{ fontSize: '0.65rem' }}></i></button>}
                                        {rows[0].description && <div style={{ fontSize: '0.75rem', color: 'var(--text-3)' }}>{rows[0].description.slice(0, 80)}{rows[0].description.length > 80 ? '…' : ''}</div>}
                                        {g.kind === 'family' && !isOpen && <div style={{ fontSize: '0.75rem', color: 'var(--text-3)', marginTop: 2 }}>
                                            {rows.map(r => <span key={r.id} style={{ marginRight: '0.6rem' }}><b style={{ fontWeight: 600 }}>{r.size}</b> {money(r.unit_price)}</span>)}</div>}
                                        {(() => { const names = [...new Set(rows.flatMap(r => (r.options || []).map(o => o.name)))];
                                            return names.length ? <div style={{ fontSize: '0.75rem', color: 'var(--text-3)', marginTop: 2 }}><i className="fas fa-plus" style={{ fontSize: '0.6rem', marginRight: 3 }}></i>{names.join(' · ')}</div> : null; })()}
                                    </td>
                                    <td style={{ whiteSpace: 'nowrap' }}>{lo === hi ? money(lo) : `${money(lo)} – ${money(hi)}`}</td>
                                    <td>{pubBadge(rows)}</td>
                                    {canEdit && <td><div style={{ display: 'flex', gap: '0.25rem' }}>
                                        {rows.every(r => r.published)
                                            ? <button className="btn-icon-sm" title={g.kind === 'family' ? 'Hide all sizes from the website' : 'Hide from the website'} onClick={() => setPublished(rows, false)}><i className="fas fa-eye-slash"></i></button>
                                            : <button className="btn-icon-sm" title={g.kind === 'family' ? 'Publish all sizes' : 'Publish to the website'} onClick={() => setPublished(rows, true)}><i className="fas fa-globe"></i></button>}
                                        <button className="btn-icon-sm" onClick={() => g.kind === 'single' ? startEdit(g.row) : startEditFamily(g)} title={g.kind === 'single' ? 'Edit' : 'Edit item + sizes'}><i className="fas fa-pencil-alt"></i></button>
                                        {g.kind === 'single' && <button className="btn-icon-sm danger" onClick={() => handleDelete(g.row)} title="Delete"><i className="fas fa-trash"></i></button>}
                                    </div></td>}
                                </tr>
                                {g.kind === 'family' && isOpen && rows.map(r => (
                                    <tr key={r.id} className={canEdit ? 'row-clickable' : ''} style={{ background: 'var(--surface-2, transparent)' }}
                                        onClick={(e) => { if (!canEdit || e.target.closest('button, a')) return; startEdit(r); }}>
                                        <td style={{ paddingLeft: '2rem', color: 'var(--text-2)' }}>{r.size}{!r.is_active && <span className="status-badge inactive" style={{ marginLeft: '0.5rem' }}>Inactive</span>}</td>
                                        <td>{money(r.unit_price)}</td>
                                        <td>{r.published ? <span className="status-badge active">Published</span> : <span style={{ color: 'var(--text-3)', fontSize: '0.8125rem' }}>Hidden</span>}</td>
                                        {canEdit && <td><div style={{ display: 'flex', gap: '0.25rem' }}>
                                            <button className="btn-icon-sm" onClick={() => startEdit(r)} title="Edit this size"><i className="fas fa-pencil-alt"></i></button>
                                            <button className="btn-icon-sm danger" onClick={() => handleDelete(r)} title="Delete this size"><i className="fas fa-trash"></i></button>
                                        </div></td>}
                                    </tr>
                                ))}
                            </React.Fragment>
                        );
                    })}
                </tbody>
            </table>
        );
    };

    // ── Classic stock list (unchanged shape) ──────────────────────────────
    const renderStockList = () => (
        <table className="data-table">
            <thead>
                <tr>
                    <th>SKU</th><th>Name</th><th>Type</th><th>Category</th><th>Price</th><th>Cost</th><th>Stock</th><th>Status</th><th>Website</th>
                    {canEdit && <th></th>}
                </tr>
            </thead>
            <tbody>
                {products.length === 0 ? (
                    <tr><td colSpan={canEdit ? 10 : 9} style={{ textAlign: 'center', color: 'var(--text-3)', padding: '2rem' }}>No products yet</td></tr>
                ) : products.map(p => (
                    /* Whole-row click opens the edit form (same guard as the menu list:
                       clicks on the row's own buttons — stock +/-, edit, delete — keep their action) */
                    <tr key={p.id} className={canEdit ? 'row-clickable' : ''} title={canEdit ? 'Click to edit' : undefined}
                        onClick={(e) => { if (!canEdit || e.target.closest('button, a')) return; startEdit(p); }}>
                        <td style={{ color: 'var(--text-3)', fontSize: '0.8125rem' }}>{p.sku || '—'}</td>
                        <td style={{ fontWeight: 500 }}>{p.name}
                            {p.description && <div style={{ fontSize: '0.75rem', color: 'var(--text-3)' }}>{p.description.slice(0, 60)}{p.description.length > 60 ? '…' : ''}</div>}</td>
                        <td style={{ color: 'var(--text-3)', fontSize: '0.8125rem' }}>
                            {typeLabel(p.item_type)}
                            {p.item_type === 'subscription' && <span style={{ color: 'var(--accent, #3b82f6)' }}> / {p.billing_interval === 'year' ? 'yr' : 'mo'}</span>}
                        </td>
                        <td style={{ color: 'var(--text-3)' }}>{p.category || '—'}</td>
                        <td>{money(p.unit_price)}</td>
                        <td style={{ color: 'var(--text-3)' }}>{p.unit_cost ? money(p.unit_cost) : '—'}</td>
                        <td>
                            {/* Low-stock red: at/below the reorder point when set; legacy <5 otherwise */}
                            {(() => {
                                const low = p.reorder_point != null ? p.stock_qty <= p.reorder_point : p.stock_qty < 5;
                                if (p.is_serialized) return (
                                    /* Serialized: qty is derived from serials — no +/- here; manage units in Edit */
                                    <div style={{ display: 'flex', alignItems: 'center', gap: '0.375rem' }} title="Serial-tracked — quantity is the count of in-stock serials. Manage units in Edit.">
                                        <i className="fas fa-barcode" style={{ color: 'var(--text-3)', fontSize: '0.75rem' }}></i>
                                        <span style={{ fontWeight: 600, minWidth: '2rem', textAlign: 'center', color: low ? '#ef4444' : 'inherit' }}>{p.stock_qty}</span>
                                    </div>
                                );
                                return (
                                    <div style={{ display: 'flex', alignItems: 'center', gap: '0.375rem' }}>
                                        {canEdit && <button className="btn-icon-sm" onClick={() => handleStockAdj(p, -1)} title="Remove 1"><i className="fas fa-minus"></i></button>}
                                        <span style={{ fontWeight: 600, minWidth: '2rem', textAlign: 'center', color: low ? '#ef4444' : 'inherit' }}>{p.stock_qty}</span>
                                        {canEdit && <button className="btn-icon-sm" onClick={() => handleStockAdj(p, 1)} title="Add 1"><i className="fas fa-plus"></i></button>}
                                    </div>
                                );
                            })()}
                        </td>
                        <td><span className={`status-badge ${p.is_active ? 'active' : 'inactive'}`}>{p.is_active ? 'Active' : 'Inactive'}</span></td>
                        {/* published (0057): on the public products feed — what the tenant's website renders */}
                        <td title={p.published ? 'On your public products feed' : 'Not on the feed'}>
                            {p.published
                                ? <span className="status-badge active"><i className="fas fa-globe" style={{ marginRight: '0.3rem' }}></i>Published</span>
                                : <span style={{ color: 'var(--text-3)', fontSize: '0.8125rem' }}>—</span>}
                        </td>
                        {canEdit && (
                            <td>
                                <div style={{ display: 'flex', gap: '0.25rem' }}>
                                    <button className="btn-icon-sm" onClick={() => startEdit(p)} title="Edit"><i className="fas fa-pencil-alt"></i></button>
                                    <button className="btn-icon-sm danger" onClick={() => handleDelete(p)} title="Delete"><i className="fas fa-trash"></i></button>
                                </div>
                            </td>
                        )}
                    </tr>
                ))}
            </tbody>
        </table>
    );

    const sizeCount = sizesVocab.filter(l => String(sizePrices[l] ?? '').trim() !== '').length;
    const saveLabel = editing === 'family' ? 'Save all sizes' : editing === 'new' && pricing === 'sizes' ? `Save ${sizeCount || ''} item${sizeCount === 1 ? '' : 's'}` : (menu ? 'Save item' : 'Save Product');

    return (
        <div className="view-content">
            {/* No in-view title — the topbar names the view (rail UI rule).
                Search stretches like the Accounts toolbar; actions sit right. */}
            <div className="list-view-header">
                <div className="list-view-actions" style={{ flex: 1 }}>
                    <input className="form-input" style={{ flex: 1, minWidth: 200 }}
                        placeholder={serialMode ? 'Serial number…' : 'Search…'}
                        value={search} onChange={e => setSearch(e.target.value)} />
                    {!menu && <span className="tag-pill tag-filter-chip" title="Look up a serial number — which unit, which invoice, which customer"
                        style={chip(serialMode)}
                        onClick={() => setSerialMode(v => !v)}>
                        <i className="fas fa-barcode" style={{ marginRight: '0.3rem' }}></i>Serial lookup
                    </span>}
                    {menu && tenant?.slug && <a className="btn btn-secondary btn-small" href={`/api/public/${tenant.slug}/products`} target="_blank" rel="noopener" title="The feed your website reads — what's published, as the site sees it">
                        <i className="fas fa-rss"></i> View feed</a>}
                    {canEdit && <button className="btn btn-secondary btn-small" title="Download as CSV"
                        onClick={() => api.exportProductsCsv().catch(err => setRowErr(err.message))}>
                        <i className="fas fa-file-export"></i> Export</button>}
                    {currentUser.role === 'admin' && <>
                        <button className="btn btn-secondary btn-small" title="Import/update items from a CSV file (upserts by SKU)"
                            onClick={() => fileRef.current && fileRef.current.click()}>
                            <i className="fas fa-file-import"></i> Import</button>
                        <input ref={fileRef} type="file" accept=".csv,text/csv" style={{ display: 'none' }}
                            onChange={async (e) => {
                                const f = e.target.files[0]; e.target.value = '';
                                if (!f) return;
                                const lineErrs = (errs) => errs.slice(0, 3).map(x => `line ${x.line} (${x.error})`).join('; ')
                                    + (errs.length > 3 ? `; +${errs.length - 3} more` : '');
                                try {
                                    const out = await api.importProductsCsv(await f.text());
                                    setImportMsg(`Imported: ${out.created} new, ${out.updated} updated` +
                                        ((out.created_categories || []).length ? `. Added categories: ${out.created_categories.join(', ')}` : '') +
                                        ((out.created_tags || []).length ? `. Added tags: ${out.created_tags.join(', ')}` : '') +
                                        (out.errors.length ? `. ${out.errors.length} row error(s): ${lineErrs(out.errors)}` : ''));
                                    await load();
                                } catch (err) {
                                    // Total failure still carries per-line detail — show it, don't eat it.
                                    const errs = err.body?.errors || [];
                                    setImportMsg(`Import failed: ${err.message}` +
                                        (errs.length ? ` — ${lineErrs(errs)}` : ''));
                                }
                            }} />
                    </>}
                    {canEdit && <button className="btn btn-primary btn-small" onClick={() => startEdit(null)}><i className="fas fa-plus"></i> Add Item</button>}
                </div>
            </div>
            {importMsg && <div className="api-success" style={{ margin: '0.5rem 0' }}>{importMsg}</div>}
            {rowErr && <div className="api-error" style={{ margin: '0.5rem 0' }}>{rowErr} <button type="button" className="btn-link" onClick={() => setRowErr('')}>Dismiss</button></div>}

            {loadError && !loading && (
                <LoadErrorBanner what={menu ? 'menu' : 'inventory'} hasData={products.length > 0} onRetry={load} />
            )}

            {serialMode ? (
                <table className="data-table">
                    <thead><tr><th>Serial</th><th>Product</th><th>Status</th><th>Invoice</th><th>Account</th></tr></thead>
                    <tbody>
                        {search.trim().length < 2
                            ? <tr><td colSpan={5} style={{ textAlign: 'center', color: 'var(--text-3)', padding: '2rem' }}>Type a serial number to look it up</td></tr>
                            : serialResults.length === 0
                            ? <tr><td colSpan={5} style={{ textAlign: 'center', color: 'var(--text-3)', padding: '2rem' }}>No matching serials</td></tr>
                            : serialResults.map(s => (
                                <tr key={s.id}>
                                    <td style={{ fontFamily: 'monospace' }}>{s.serial}</td>
                                    <td>{s.product_name}{s.product_sku ? <span style={{ color: 'var(--text-3)', fontSize: '0.75rem' }}> ({s.product_sku})</span> : null}</td>
                                    <td><span className="tag-pill" style={{ fontSize: '0.65rem', background: s.status === 'in_stock' ? 'var(--accent, #3b82f6)' : s.status === 'reserved' ? '#f59e0b' : '#6b7280', color: '#fff' }}>
                                        {s.status === 'in_stock' ? 'In stock' : s.status === 'reserved' ? 'Reserved' : 'Sold'}</span>
                                        {s.sold_at ? <span style={{ color: 'var(--text-3)', fontSize: '0.75rem', marginLeft: '0.4rem' }}>{formatDate(s.sold_at)}</span> : null}
                                    </td>
                                    <td style={{ fontFamily: 'monospace', fontSize: '0.8125rem' }}>{s.invoice_number || (s.restricted ? '—' : '')}</td>
                                    <td>{s.restricted ? <span style={{ color: 'var(--text-3)', fontSize: '0.8125rem' }}>another rep's account</span> : (s.account_name || '—')}</td>
                                </tr>
                            ))}
                    </tbody>
                </table>
            ) : loading ? <div className="loading-state"><i className="fas fa-spinner fa-spin"></i><p>Loading…</p></div>
              : menu ? renderMenuList() : renderStockList()}

            {!loading && !serialMode && products.length < total && (
                <div style={{ textAlign: 'center', padding: '0.75rem' }}>
                    <button className="btn btn-secondary btn-small" onClick={() => load(true)}>
                        <i className="fas fa-angles-down"></i> Load more ({products.length} of {total})
                    </button>
                </div>
            )}

            {editing && (
                <div className="modal-overlay" onClick={(e) => e.target === e.currentTarget && setEditing(null)}>
                    <div className="modal modal-medium">
                        <div className="modal-header">
                            <h2 className="modal-title">{editing === 'new' ? (menu ? 'New Menu Item' : 'New Product') : editing === 'family' ? `Edit: ${family.base}` : `Edit: ${editing.name}`}</h2>
                            <button className="modal-close-btn" onClick={() => setEditing(null)}>&times;</button>
                        </div>
                        <div className="modal-body">
                            <form onSubmit={handleSave}>
                                {formErr && <div className="api-error">{formErr}</div>}
                                {/* Menu-first order (2026-08-24): what every tenant fills in sits on
                                    top; the warehouse half folds away below. */}
                                <div className="form-grid">
                                    {/* Menu style: category comes FIRST and is required — a menu
                                        item without a section renders under "Other" on the site
                                        (found live 2026-08-25). Stock style keeps it optional. */}
                                    <div className="form-group full-width" style={{ order: menu ? -1 : undefined }}><label className="form-label">Name *</label>
                                        <input className="form-input" value={form.name} onChange={set('name')} required
                                               placeholder={editing === 'new' && pricing === 'sizes' ? 'e.g. Pepperoni — sizes are added to the name' : ''} /></div>
                                    <div className={`form-group ${menu ? 'full-width' : ''}`} style={{ order: menu ? -2 : undefined }}>
                                        <label className="form-label">{menu ? 'Menu section (category) *' : 'Category'}</label>
                                        <select className="form-input" value={form.category || ''} onChange={set('category')} required={menu}
                                                style={menu && !form.category ? { borderColor: 'var(--accent, #3b82f6)' } : undefined}>
                                            <option value="" disabled={menu}>{menu ? 'Choose a menu section…' : '—'}</option>
                                            {/* An edited product may carry a label deleted from the vocabulary — keep it selectable so opening the form doesn't silently clear it. */}
                                            {form.category && !categories.some(c => c.name === form.category) && <option value={form.category}>{form.category}</option>}
                                            {categories.map(c => <option key={c.id} value={c.name}>{c.name}</option>)}
                                        </select>
                                        {isAdmin && (newCat === null
                                            ? <button type="button" className="btn-link" style={{ fontSize: '0.75rem', marginTop: '0.35rem' }} onClick={() => setNewCat('')}><i className="fas fa-plus"></i> New category</button>
                                            : <div style={{ display: 'flex', gap: '0.35rem', marginTop: '0.35rem' }}>
                                                <input className="form-input" autoFocus value={newCat} maxLength={50} placeholder="Category name" onChange={e => setNewCat(e.target.value)}
                                                       onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); addCategoryInline(); } if (e.key === 'Escape') setNewCat(null); }} />
                                                <button type="button" className="btn btn-secondary btn-small" onClick={addCategoryInline} disabled={!newCat.trim()}>Add</button>
                                                <button type="button" className="btn btn-secondary btn-small" onClick={() => setNewCat(null)}>×</button>
                                              </div>)}
                                        {categories.length === 0 && !isAdmin && (
                                            <p style={{ fontSize: '0.75rem', color: 'var(--text-3)', marginTop: '0.35rem' }}>
                                                Ask an admin to define categories in Settings → Product Categories.
                                            </p>
                                        )}
                                    </div>
                                    <div className="form-group"><label className="form-label">Description</label>
                                        <input className="form-input" value={form.description || ''} onChange={set('description')} placeholder={menu ? 'Shows on your website' : 'Optional'} /></div>

                                    {/* Pricing: one price, or one row per size (new items, when the tenant has a size list) */}
                                    {editing === 'new' && sizesVocab.length > 0 && (
                                        <div className="form-group full-width">
                                            <label className="form-label">Pricing</label>
                                            <div className="tag-filter-row">
                                                {[['one', 'One price', 'fa-tag'], ['sizes', 'Comes in sizes', 'fa-layer-group']].map(([val, lbl, icon]) => (
                                                    <span key={val} className="tag-pill tag-filter-chip" style={chip(pricing === val)} onClick={() => setPricing(val)}>
                                                        <i className={`fas ${icon}`} style={{ marginRight: '0.3rem' }}></i>{lbl}
                                                    </span>
                                                ))}
                                            </div>
                                        </div>
                                    )}
                                    {(editing === 'new' && pricing === 'sizes') || editing === 'family' ? (
                                        <div className="form-group full-width">
                                            <label className="form-label">Sizes</label>
                                            <table className="data-table" style={{ margin: 0 }}>
                                                <thead><tr><th>Size</th><th style={{ textAlign: 'right' }}>Price</th>{editing === 'family' && isAdmin && <th></th>}</tr></thead>
                                                <tbody>{[...sizesVocab, ...(family ? family.rows.map(r => r.size).filter(sz => !sizesVocab.some(v => v.toLowerCase() === sz.toLowerCase())) : [])].map(l => {
                                                    const row = family && family.rows.find(r => r.size.toLowerCase() === l.toLowerCase());
                                                    const removed = row && removeIds.includes(row.id);
                                                    return (
                                                    <tr key={l} style={removed ? { opacity: 0.45, textDecoration: 'line-through' } : {}}>
                                                        <td>{l}{row && !row.is_active && <span className="status-badge inactive" style={{ marginLeft: '0.5rem' }}>Inactive</span>}</td>
                                                        <td style={{ textAlign: 'right' }}>
                                                            <input type="number" step="0.01" min="0" className="form-input" style={{ width: 120, textAlign: 'right', display: 'inline-block' }}
                                                                   value={sizePrices[l] ?? ''} placeholder={row ? '' : '—'} disabled={!!removed}
                                                                   onChange={e => setSizePrices(sp => ({ ...sp, [l]: e.target.value }))} />
                                                        </td>
                                                        {/* Deleting a size deletes a product row — admin-only server-side, so only admins get the door */}
                                                        {editing === 'family' && isAdmin && <td style={{ textAlign: 'right' }}>
                                                            {row && (removed
                                                                ? <button type="button" className="btn-link" style={{ fontSize: '0.75rem' }} onClick={() => setRemoveIds(ids => ids.filter(x => x !== row.id))}>Undo</button>
                                                                : <button type="button" className="btn-icon-sm danger" title="Remove this size (deletes it on save)" onClick={() => setRemoveIds(ids => [...ids, row.id])}><i className="fas fa-times"></i></button>)}
                                                        </td>}
                                                    </tr>);
                                                })}</tbody>
                                            </table>
                                            {isAdmin && (newSize === null
                                                ? <button type="button" className="btn-link" style={{ fontSize: '0.75rem', marginTop: '0.35rem' }} onClick={() => setNewSize('')}><i className="fas fa-plus"></i> New size</button>
                                                : <div style={{ display: 'flex', gap: '0.35rem', marginTop: '0.35rem', maxWidth: 360 }}>
                                                    <input className="form-input" autoFocus value={newSize} maxLength={30} placeholder="e.g. Half, Whole, 12”" onChange={e => setNewSize(e.target.value)}
                                                           onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); addSizeInline(); } if (e.key === 'Escape') setNewSize(null); }} />
                                                    <button type="button" className="btn btn-secondary btn-small" onClick={addSizeInline} disabled={!newSize.trim()}>Add</button>
                                                    <button type="button" className="btn btn-secondary btn-small" onClick={() => setNewSize(null)}>×</button>
                                                  </div>)}
                                            <p style={{ fontSize: '0.75rem', color: 'var(--text-3)', marginTop: '0.35rem' }}>
                                                {editing === 'family'
                                                    ? 'Change a price, fill in a blank size to add it, or × to remove one. Saved together.'
                                                    : <>Leave a price blank to skip that size. Saves one item per size — “{form.name || 'Name'} – {sizesVocab[0]}”, …</>}
                                            </p>
                                        </div>
                                    ) : (
                                        <div className="form-group"><label className="form-label">{menu ? 'Price' : 'Unit Price'}</label><input type="number" step="0.01" className="form-input" value={form.unit_price} onChange={set('unit_price')} /></div>
                                    )}

                                    {/* ── Add-ons (0060): "Extra Cheese $1.25" — priced per size on a
                                        family (grid), a flat list on a single item. Names are item
                                        content, so free text with suggestions from what's already used. ── */}
                                    {(() => {
                                        const sizesShown = editing === 'family'
                                            ? [...family.rows.filter(r => !removeIds.includes(r.id)).map(r => r.size), ...sizesVocab.filter(l => !family.rows.some(r => r.size.toLowerCase() === l.toLowerCase()) && String(sizePrices[l] ?? '').trim() !== '')]
                                            : (editing === 'new' && pricing === 'sizes') ? sizesVocab.filter(l => String(sizePrices[l] ?? '').trim() !== '') : null;
                                        const known = [...new Set(products.flatMap(p => (p.options || []).map(o => o.name)))];
                                        const cell = (v, onCh) => <input type="number" step="0.01" min="0" className="form-input" style={{ width: 90, textAlign: 'right', display: 'inline-block', padding: '0.3rem 0.5rem' }} value={v ?? ''} placeholder="—" onChange={e => onCh(e.target.value)} />;
                                        if (sizesShown) return (
                                            <div className="form-group full-width">
                                                <label className="form-label">Add-ons <span style={{ fontWeight: 400, color: 'var(--text-3)' }}>— price per size, blank = not offered</span></label>
                                                {optNames.length > 0 && (
                                                    <div style={{ overflowX: 'auto' }}><table className="data-table" style={{ margin: 0 }}>
                                                        <thead><tr><th>Add-on</th>{sizesShown.map(sz => <th key={sz} style={{ textAlign: 'right' }}>{sz}</th>)}<th></th></tr></thead>
                                                        <tbody>{optNames.map(n => (
                                                            <tr key={n}>
                                                                <td>{n}</td>
                                                                {sizesShown.map(sz => <td key={sz} style={{ textAlign: 'right' }}>{cell((optGrid[n] || {})[sz], v => setOptGrid(g => ({ ...g, [n]: { ...(g[n] || {}), [sz]: v } })))}</td>)}
                                                                <td style={{ textAlign: 'right' }}><button type="button" className="btn-icon-sm danger" title="Remove add-on" onClick={() => { setOptNames(ns => ns.filter(x => x !== n)); setOptGrid(g => { const c = { ...g }; delete c[n]; return c; }); }}><i className="fas fa-times"></i></button></td>
                                                            </tr>
                                                        ))}</tbody>
                                                    </table></div>
                                                )}
                                                <OptionNameAdd known={known} existing={optNames} onAdd={n => setOptNames(ns => [...ns, n])} menu={menu} />
                                            </div>
                                        );
                                        return (
                                            <div className="form-group full-width">
                                                <label className="form-label">Add-ons <span style={{ fontWeight: 400, color: 'var(--text-3)' }}>— {menu ? 'e.g. Extra Cheese $1.25 · Sour Cream' : 'e.g. Extended Warranty $49'}</span></label>
                                                {(form.options || []).length === 0 && <p className="form-hint" style={{ margin: '0 0 0.4rem' }}>{menu ? 'Extra cheese, meat, sauce… add a name and a price.' : 'Optional extras with their own price.'}</p>}
                                                <OptionChips known={known} existing={(form.options || []).map(o => o.name)} onAdd={n => setForm(p => ({ ...p, options: [...(p.options || []), { name: n, price: '' }] }))} />
                                                {(form.options || []).map((o, i) => (
                                                    <div key={i} style={{ display: 'flex', gap: '0.5rem', marginBottom: '0.4rem', alignItems: 'center' }}>
                                                        <input className="form-input" style={{ flex: 1 }} maxLength={60} value={o.name} placeholder="Add-on name" list="opt-names"
                                                               onChange={e => setForm(p => ({ ...p, options: p.options.map((x, j) => j === i ? { ...x, name: e.target.value } : x) }))} />
                                                        {cell(o.price, v => setForm(p => ({ ...p, options: p.options.map((x, j) => j === i ? { ...x, price: v } : x) })))}
                                                        <button type="button" className="btn-icon-sm danger" title="Remove" onClick={() => setForm(p => ({ ...p, options: p.options.filter((_, j) => j !== i) }))}><i className="fas fa-times"></i></button>
                                                    </div>
                                                ))}
                                                <datalist id="opt-names">{known.map(n => <option key={n} value={n} />)}</datalist>
                                                {(form.options || []).length < 20 && <button type="button" className="btn-link" style={{ fontSize: '0.75rem' }} onClick={() => setForm(p => ({ ...p, options: [...(p.options || []), { name: '', price: '' }] }))}><i className="fas fa-plus"></i> Add-on</button>}
                                            </div>
                                        );
                                    })()}
                                    {/* Website publish (0057): chip pair (no-checkbox rule). Opt-in — a product
                                        reaches the public feed only because someone turned this on. */}
                                    <div className="form-group full-width">
                                        <label className="form-label">On your website</label>
                                        <div className="filter-chips" style={{ display: 'flex', gap: '0.5rem' }}>
                                            {[[true, 'Published'], [false, 'Hidden']].map(([val, lbl]) => (
                                                <button key={String(val)} type="button" className={`filter-chip${!!form.published === val ? ' active' : ''}`}
                                                      style={chip(!!form.published === val)}
                                                      onClick={() => setForm(prev => ({ ...prev, published: val }))}>
                                                    {lbl}
                                                </button>
                                            ))}
                                        </div>
                                        <div style={{ fontSize: '0.75rem', color: 'var(--text-3)', marginTop: '0.25rem' }}>
                                            Published items appear on your public products feed — the one your website reads (Settings → Website Feed). Price, name, description, category, sizes, add-ons, and photos only.
                                        </div>
                                    </div>
                                    <div className="form-group">
                                        <label className="form-label">Website order</label>
                                        <input type="number" min="0" className="form-input" value={form.sort_order ?? ''} onChange={set('sort_order')} placeholder="Position within category" />
                                    </div>
                                    <div className="form-group">
                                        <label className="form-label">Status</label>
                                        <div className="tag-filter-row">
                                            {[[true, 'Active'], [false, 'Inactive']].map(([val, lbl]) => (
                                                <span key={String(val)} className="tag-pill tag-filter-chip" style={chip(!!form.is_active === val)}
                                                      onClick={() => setForm(prev => ({ ...prev, is_active: val }))}>{lbl}</span>
                                            ))}
                                        </div>
                                    </div>

                                    {/* ── Stock & tracking — the warehouse half. Folded by default; absent
                                        entirely in Menu style (columns keep their defaults). ── */}
                                    {!menu && editing !== 'family' && (
                                        <details className="form-fold full-width" open={editing !== 'new' && (!!editing.sku || !!editing.is_serialized || editing.item_type !== 'physical')}>
                                            <summary>Stock &amp; tracking</summary>
                                            <div className="form-grid">
                                                <div className="form-group"><label className="form-label">SKU</label><input className="form-input" value={form.sku || ''} onChange={set('sku')} placeholder={editing === 'new' && pricing === 'sizes' ? 'Not with sizes' : 'Optional'} disabled={editing === 'new' && pricing === 'sizes'} /></div>
                                                <div className="form-group"><label className="form-label">Unit Cost</label><input type="number" step="0.01" className="form-input" value={form.unit_cost || ''} onChange={set('unit_cost')} /></div>
                                                {!(form.item_type === 'physical' && form.is_serialized) ? (
                                                    <div className="form-group"><label className="form-label">Stock Qty</label><input type="number" className="form-input" value={form.stock_qty} onChange={set('stock_qty')} /></div>
                                                ) : (
                                                    <div className="form-group"><label className="form-label">Stock Qty</label>
                                                        <div className="form-input" style={{ color: 'var(--text-3)', display: 'flex', alignItems: 'center' }}>
                                                            {editing === 'new' ? 'Counted from serials' : `${editing.stock_qty ?? 0} in stock (from serials)`}
                                                        </div>
                                                    </div>
                                                )}
                                                {form.item_type === 'physical' && (
                                                    <div className="form-group"><label className="form-label">Reorder point</label>
                                                        <input type="number" min="0" className="form-input" value={form.reorder_point ?? ''} onChange={set('reorder_point')} placeholder="Low-stock alert at…" />
                                                    </div>
                                                )}
                                                <div className="form-group full-width">
                                                    <label className="form-label">Type</label>
                                                    {/* chips, not checkboxes (house rule) — subscription items mark every
                                                        quote/invoice that carries them as a subscription doc */}
                                                    <div className="tag-filter-row">
                                                        {TYPES.map(([val, lbl, icon]) => (
                                                            <span key={val} className="tag-pill tag-filter-chip" style={chip(form.item_type === val)}
                                                                  onClick={() => setForm(prev => ({ ...prev, item_type: val }))}>
                                                                <i className={`fas ${icon}`} style={{ marginRight: '0.3rem' }}></i>{lbl}
                                                            </span>
                                                        ))}
                                                    </div>
                                                </div>
                                                {form.item_type === 'physical' && (
                                                    <div className="form-group full-width">
                                                        <label className="form-label">Quantity tracking</label>
                                                        {/* chips, not checkboxes (house rule). Serialized = every unit has
                                                            its own serial number; quantity is derived from serials on hand. */}
                                                        <div className="tag-filter-row">
                                                            {[[false, 'By quantity', 'fa-hashtag'], [true, 'By serial number', 'fa-barcode']].map(([val, lbl, icon]) => (
                                                                <span key={String(val)} className="tag-pill tag-filter-chip" style={chip(!!form.is_serialized === val)}
                                                                      onClick={() => setForm(prev => ({ ...prev, is_serialized: val }))}>
                                                                    <i className={`fas ${icon}`} style={{ marginRight: '0.3rem' }}></i>{lbl}
                                                                </span>
                                                            ))}
                                                        </div>
                                                        {form.is_serialized && (
                                                            <p style={{ fontSize: '0.75rem', color: 'var(--text-3)', marginTop: '0.35rem' }}>
                                                                Stock is counted from serial numbers on hand — add units below{editing === 'new' ? ' after saving' : ''}.
                                                                Each sale picks the exact units, and they print on the invoice.
                                                            </p>
                                                        )}
                                                    </div>
                                                )}
                                                {form.item_type === 'subscription' && (
                                                    <div className="form-group">
                                                        <label className="form-label">Billing interval</label>
                                                        <div className="tag-filter-row">
                                                            {[['month', 'Monthly'], ['year', 'Yearly']].map(([val, lbl]) => (
                                                                <span key={val} className="tag-pill tag-filter-chip" style={chip((form.billing_interval || 'month') === val)}
                                                                      onClick={() => setForm(prev => ({ ...prev, billing_interval: val }))}>
                                                                    {lbl}
                                                                </span>
                                                            ))}
                                                        </div>
                                                    </div>
                                                )}
                                                {editing !== 'new' && form.item_type === 'physical' && form.is_serialized && editing.is_serialized && (
                                                    <SerialsPanel productId={editing.id} onStockChange={load} />
                                                )}
                                            </div>
                                        </details>
                                    )}

                                    {/* ── When paid — tags/tasks a sale confers (0038/0041). Hidden in Menu style. ── */}
                                    {!menu && editing !== 'family' && (
                                        <details className="form-fold full-width" open={editing !== 'new' && ((editing.tags_on_paid || []).length > 0 || (editing.tasks_on_paid || []).length > 0)}>
                                            <summary>When a customer pays</summary>
                                            <div className="form-grid">
                                                <div className="form-group full-width">
                                                    <label className="form-label">Tags applied when paid</label>
                                                    <div className="tag-filter-row" style={{ alignItems: 'center' }}>
                                                        {/* Vocabulary chips (0042 rule): pick from Settings → Tags, no free typing. A selected tag since deleted from the vocabulary still shows (form.tags_on_paid drives the render) so it can be unpicked. */}
                                                        {[...allTags.map(t => t.name),
                                                          ...(form.tags_on_paid || []).filter(t => !allTags.some(v => v.name.toLowerCase() === t.toLowerCase()))]
                                                          .map(name => {
                                                            const on = (form.tags_on_paid || []).some(t => t.toLowerCase() === name.toLowerCase());
                                                            return (
                                                                <span key={name} className="tag-pill tag-filter-chip"
                                                                      style={on ? { background: 'var(--accent, #3b82f6)', color: '#fff', cursor: 'pointer' }
                                                                                : { cursor: 'pointer', opacity: 0.75 }}
                                                                      title={on ? 'Remove' : 'Apply on first payment'}
                                                                      onClick={() => toggleTag(name)}>
                                                                    {name}{on && <i className="fas fa-check" style={{ marginLeft: '0.3rem', fontSize: '0.65rem' }}></i>}
                                                                </span>
                                                            );
                                                        })}
                                                        {allTags.length === 0 && (
                                                            <span style={{ fontSize: '0.8125rem', color: 'var(--text-3)' }}>
                                                                No tags defined yet — create them in Settings → Tags.
                                                            </span>
                                                        )}
                                                    </div>
                                                    <p style={{ fontSize: '0.75rem', color: 'var(--text-3)', marginTop: '0.35rem' }}>
                                                        When a customer pays for this item — including through a quote's accept
                                                        link — the selected tags land on their account automatically. Up to 5.
                                                    </p>
                                                </div>
                                                <div className="form-group full-width">
                                                    <label className="form-label">Tasks opened on first payment</label>
                                                    <TaskListEditor tasks={form.tasks_on_paid || []} presets={taskPresets}
                                                                    onChange={tasks => setForm(p => ({ ...p, tasks_on_paid: tasks }))} />
                                                    <p style={{ fontSize: '0.75rem', color: 'var(--text-3)', marginTop: '0.35rem' }}>
                                                        The follow-up work a sale of this item creates, in this order. Each opens as an
                                                        urgent, sign-off-gated task on the account's rep when the first payment lands.
                                                        Apply a preset to add its tasks, combine several, then reorder.
                                                        No tasks declared anywhere on the sale — a generic follow-up task opens instead.
                                                    </p>
                                                </div>
                                            </div>
                                        </details>
                                    )}
                                    {/* Photos (2026-08-25): attachments on the product row, published
                                        to the website feed as `image` + `photos[]`. A sizes family shares
                                        ONE photo set — the feed lets every size borrow the first row's
                                        photos, so the gallery hangs off that row. Needs an id: new
                                        items get it after the first save. Rep-visible, manager+ writes
                                        (server-enforced). */}
                                    {editing !== 'new' && canEdit && (
                                        <div className="form-group full-width" style={{ marginTop: "0.5rem" }}>
                                            <PhotoGallery entityType="product"
                                                          entityId={editing === 'family' ? family.rows[0].id : editing.id}
                                                          title={menu ? 'Photos (shown on your website)' : 'Photos'} />
                                            <p style={{ fontSize: '0.75rem', color: 'var(--text-3)', marginTop: '0.25rem' }}>
                                                The first photo is the cover. Published items show their photos on your website; large photos are resized automatically.
                                            </p>
                                        </div>
                                    )}
                                    {editing === 'new' && (
                                        <p className="form-group full-width" style={{ fontSize: "0.75rem", color: "var(--text-3)" }}>
                                            <i className="fas fa-camera"></i> Photos can be added after the first save.
                                        </p>
                                    )}
                                </div>
                                <div className="btn-group">
                                    <button type="button" className="btn btn-secondary" onClick={() => setEditing(null)} disabled={saving}>Cancel</button>
                                    <button type="submit" className="btn btn-primary" disabled={saving}>{saving ? <><i className="fas fa-spinner fa-spin"></i> Saving…</> : <><i className="fas fa-check"></i> {saveLabel}</>}</button>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            )}
        </div>
    );
};
