/* NutriDMS, Product Specification tabs (Master PRD §11–§13)
   Mounted inside OfDetail for product/combo/catering offerings.
   Tabs: Product Details · Packaging · Storage · Manufacturing · Claims ·
   GS1 / Barcode · QR · Compliance · Documents. Live completion + calcs. */

const { useState: usePsState, useEffect: usePsEffect, useMemo: usePsMemo } = React;

function psCanEdit(role) { return role === "admin" || role === "super-admin" || role === "manager" || role === "compliance" || role === "dietitian"; }
function psWho(role) { try { return window.currentUser ? window.currentUser(role).name : "You"; } catch (e) { return "You"; } }

/* ───────── Completion ring ───────── */
function PsRing({ pct, size = 54 }) {
  const r = (size - 8) / 2, c = 2 * Math.PI * r;
  const tone = pct >= 90 ? "#2f9e44" : pct >= 60 ? "#1971c2" : pct >= 30 ? "#e8a700" : "#e5484d";
  return (
    <svg width={size} height={size} className="ps-ring">
      <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke="var(--gray-150,#eee)" strokeWidth="6" />
      <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke={tone} strokeWidth="6" strokeLinecap="round"
        strokeDasharray={c} strokeDashoffset={c * (1 - pct / 100)} transform={`rotate(-90 ${size / 2} ${size / 2})`} style={{ transition: "stroke-dashoffset .5s cubic-bezier(.32,.72,.26,1)" }} />
      <text x="50%" y="52%" dominantBaseline="middle" textAnchor="middle" fontSize="13" fontWeight="800" fill={tone}>{pct}%</text>
    </svg>
  );
}

/* ───────── Reusable field controls ───────── */
function PsText({ label, value, onChange, placeholder, suffix, type = "text", disabled, hint }) {
  return (
    <label className="ps-field">
      <span>{label}</span>
      <div className={`ps-input-wrap ${suffix ? "has-suffix" : ""}`}>
        <input type={type} value={value == null ? "" : value} disabled={disabled} placeholder={placeholder}
          onChange={(e) => onChange(type === "number" ? (e.target.value === "" ? "" : Number(e.target.value)) : e.target.value)} />
        {suffix && <span className="ps-suffix">{suffix}</span>}
      </div>
      {hint && <span className="ps-hint">{hint}</span>}
    </label>
  );
}
function PsSelect({ label, value, onChange, options, disabled, placeholder }) {
  return (
    <label className="ps-field">
      <span>{label}</span>
      <select value={value || ""} disabled={disabled} onChange={(e) => onChange(e.target.value)}>
        <option value="">{placeholder || "Select…"}</option>
        {options.map((o) => typeof o === "string" ? <option key={o} value={o}>{o}</option> : <option key={o.v} value={o.v}>{o.t}</option>)}
      </select>
    </label>
  );
}

/* ───────── Cost, price & margin (internal only — never public) ───────── */
function PsCosting({ offering, canEdit, onChange }) {
  const spec = psGet(offering.id);
  const c = spec.costing || {};
  const cur = (window.NutriMoney ? window.NutriMoney.orgCurrency() : "USD");
  const money = (n) => (window.NutriMoney ? window.NutriMoney.money(n, cur) : "$" + (n || 0).toFixed(2));
  const num = (v) => Number(v) || 0;
  const set = (k, v) => { psPatch(offering.id, "costing", { [k]: v }); onChange && onChange(); };
  const totalCost = num(c.ingredient) + num(c.packaging) + num(c.labour) + num(c.overhead) + num(c.delivery) + num(c.other);
  const priceExTax = num(c.sellingPrice) - num(c.discount);
  const grossProfit = priceExTax - totalCost;
  const margin = priceExTax > 0 ? (grossProfit / priceExTax * 100) : 0;
  const marginTone = margin >= 60 ? "#2f9e44" : margin >= 35 ? "#1971c2" : margin >= 15 ? "#e8a700" : "#e5484d";
  const COSTS = [["ingredient", "Ingredient cost"], ["packaging", "Packaging cost"], ["labour", "Labour cost"], ["overhead", "Overhead cost"], ["delivery", "Delivery cost"], ["other", "Other cost"]];
  return (
    <div className="ps-costing">
      <div className="ps-empty-note" style={{ marginBottom: 14 }}><Icon name="lock" size={18} /><div><b>Internal only</b><span>Cost and margin never appear on customer pages or public endpoints — for your team's pricing decisions.</span></div></div>
      <div className="ps-cost-grid">
        <div className="ps-cost-inputs">
          <div className="ps-sec-h">Unit costs ({cur})</div>
          {COSTS.map(([k, l]) => (
            <PsText key={k} label={l} type="number" value={c[k] || ""} disabled={!canEdit} onChange={(v) => set(k, v)} suffix={cur} />
          ))}
          <div className="ps-sec-h" style={{ marginTop: 14 }}>Selling</div>
          <PsText label="Base selling price (ex tax)" type="number" value={c.sellingPrice || ""} disabled={!canEdit} onChange={(v) => set("sellingPrice", v)} suffix={cur} />
          <PsText label="Discount" type="number" value={c.discount || ""} disabled={!canEdit} onChange={(v) => set("discount", v)} suffix={cur} />
          <PsText label="Tax amount" type="number" value={c.tax || ""} disabled={!canEdit} onChange={(v) => set("tax", v)} suffix={cur} />
        </div>
        <div className="ps-cost-summary">
          <div className="ps-cost-card"><span>Total unit cost</span><b>{money(totalCost)}</b></div>
          <div className="ps-cost-card"><span>Price ex tax</span><b>{money(priceExTax)}</b></div>
          <div className="ps-cost-card"><span>Final price (inc tax)</span><b>{money(priceExTax + num(c.tax))}</b></div>
          <div className="ps-cost-card accent"><span>Gross profit</span><b style={{ color: grossProfit >= 0 ? "#2f9e44" : "#e5484d" }}>{money(grossProfit)}</b></div>
          <div className="ps-cost-card accent"><span>Gross margin</span><b style={{ color: marginTone }}>{margin.toFixed(1)}%</b></div>
          <div className="ps-cost-bar"><span style={{ width: Math.max(0, Math.min(100, margin)) + "%", background: marginTone }} /></div>
          {canEdit && window.NutriCosting && <button className="btn secondary sm" style={{ marginTop: 12, width: "100%", justifyContent: "center" }} onClick={() => { window.NutriCosting.snapshot(offering.id, { ingredient: num(c.ingredient), packaging: num(c.packaging), labour: num(c.labour), overhead: num(c.overhead), delivery: num(c.delivery), other: num(c.other), sellingPrice: num(c.sellingPrice), discount: num(c.discount), sellableUnits: 1, servingsPerUnit: 1 }, "You"); onChange && onChange(); }}><Icon name="camera" size={14} /> Snapshot this cost</button>}
        </div>
      </div>
      {window.NutriCosting && <CostScenarios offering={offering} c={c} num={num} money={money} canEdit={canEdit} />}
    </div>
  );
}

/* ───────── What-if cost scenarios + snapshot history (Module 3 §16) ───────── */
function CostScenarios({ offering, c, num, money, canEdit }) {
  const NC = window.NutriCosting;
  const [tick, setTick] = React.useState(0);
  const bump = () => setTick((t) => t + 1);
  const [draft, setDraft] = React.useState(null); // {name, ingredient, labour, sellingPrice, sellableUnits}
  const snaps = NC.snapshots(offering.id);
  const scens = NC.scenarios(offering.id);
  const baseInputs = { ingredient: num(c.ingredient), packaging: num(c.packaging), labour: num(c.labour), overhead: num(c.overhead), freight: num(c.delivery), other: num(c.other), sellingPrice: num(c.sellingPrice), discount: num(c.discount), sellableUnits: 1, servingsPerUnit: 1 };
  const base = NC.computeBatch(baseInputs);
  const startWhatIf = () => setDraft({ name: "New scenario", ingredient: baseInputs.ingredient, labour: baseInputs.labour, sellingPrice: baseInputs.sellingPrice, sellableUnits: 1 });
  const preview = draft ? NC.computeBatch(Object.assign({}, baseInputs, { ingredient: num(draft.ingredient), labour: num(draft.labour), sellingPrice: num(draft.sellingPrice), sellableUnits: Math.max(1, num(draft.sellableUnits)) })) : null;
  const delta = (a, b) => { const d = a - b; return (d >= 0 ? "+" : "") + d.toFixed(1); };
  return (
    <div className="ps-scenarios">
      <div className="ps-cx-head" style={{ background: "var(--gray-50)", borderColor: "var(--gray-200)", marginTop: 18 }}>
        <div><b>What-if scenarios</b><span>Model a different supplier, batch size, labour rate or price without touching the approved cost.</span></div>
        {canEdit && <button className="btn secondary sm" onClick={startWhatIf}><Icon name="git-branch" size={14} /> New scenario</button>}
      </div>
      {draft && (
        <div className="ps-scen-editor">
          <div className="ps-scen-grid">
            <label>Name<input value={draft.name} onChange={(e) => setDraft(Object.assign({}, draft, { name: e.target.value }))} /></label>
            <label>Ingredient cost<input type="number" value={draft.ingredient} onChange={(e) => setDraft(Object.assign({}, draft, { ingredient: e.target.value }))} /></label>
            <label>Labour cost<input type="number" value={draft.labour} onChange={(e) => setDraft(Object.assign({}, draft, { labour: e.target.value }))} /></label>
            <label>Selling price<input type="number" value={draft.sellingPrice} onChange={(e) => setDraft(Object.assign({}, draft, { sellingPrice: e.target.value }))} /></label>
            <label>Sellable units<input type="number" value={draft.sellableUnits} onChange={(e) => setDraft(Object.assign({}, draft, { sellableUnits: e.target.value }))} /></label>
          </div>
          <div className="ps-scen-preview">
            <span>Unit cost <b>{money(preview.unitCost)}</b></span>
            <span>Margin <b style={{ color: preview.margin >= 35 ? "#2f9e44" : "#e8a700" }}>{preview.margin.toFixed(1)}% <small>({delta(preview.margin, base.margin)}pt)</small></b></span>
            <div style={{ display: "flex", gap: 8 }}>
              <button className="btn primary sm" onClick={() => { NC.saveScenario(offering.id, draft.name, Object.assign({}, baseInputs, { ingredient: num(draft.ingredient), labour: num(draft.labour), sellingPrice: num(draft.sellingPrice), sellableUnits: Math.max(1, num(draft.sellableUnits)) }), "You"); setDraft(null); bump(); }}>Save scenario</button>
              <button className="btn ghost sm" onClick={() => setDraft(null)}>Cancel</button>
            </div>
          </div>
        </div>
      )}
      {scens.length > 0 && (
        <div className="ps-scen-list">
          {scens.map((s) => (
            <div key={s.id} className="ps-scen-row">
              <span className="ps-scen-name">{s.name}</span>
              <span>Unit {money(s.result.unitCost)}</span>
              <span style={{ color: s.result.margin >= base.margin ? "#2f9e44" : "#e5484d" }}>Margin {s.result.margin.toFixed(1)}% ({delta(s.result.margin, base.margin)}pt)</span>
              {canEdit && <button className="ps-price-del" onClick={() => { NC.deleteScenario(offering.id, s.id); bump(); }} title="Remove"><Icon name="trash-2" size={13} /></button>}
            </div>
          ))}
        </div>
      )}
      {snaps.length > 0 && (
        <div className="ps-scen-snaps">
          <div className="ps-sec-h" style={{ marginTop: 14 }}>Cost history (locked)</div>
          {snaps.slice(0, 5).map((s) => (
            <div key={s.id} className="ps-scen-row locked">
              <span className="ps-scen-name"><Icon name="lock" size={11} /> v{s.version}</span>
              <span>Unit {money(s.result.unitCost)}</span>
              <span>Margin {s.result.margin.toFixed(1)}%</span>
              <span className="ps-scen-when">{new Date(s.at).toLocaleDateString()}</span>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

/* ───────── Per-country pricing matrix (public-facing prices) ───────── */
function PsPricing({ offering, canEdit, onChange }) {
  const spec = psGet(offering.id);
  const rows = (spec.pricing && spec.pricing.rows) || [];
  const fmt = (amt, code) => (window.NutriMoney ? window.NutriMoney.format(amt, code) : "$" + (Number(amt) || 0).toFixed(2));
  const curList = (window.NutriMoney ? window.NutriMoney.list : [{ code: "USD", name: "US Dollar" }]);
  const save = (next) => { psPatch(offering.id, "pricing", { rows: next }); onChange && onChange(); };
  const add = () => save(rows.concat([{ id: "p" + Date.now(), country: "", currency: "USD", amount: "", taxIncluded: false, effFrom: "", effTo: "" }]));
  const upd = (i, k, v) => { const n = rows.slice(); n[i] = Object.assign({}, n[i], { [k]: v }); save(n); };
  const del = (i) => save(rows.filter((_, j) => j !== i));
  return (
    <div className="ps-pricing">
      <div className="ps-cx-head" style={{ background: "var(--gray-50)", borderColor: "var(--gray-200)" }}>
        <div><b>Per-country pricing</b><span>Set the customer-facing price per market. The public page shows the row matching the location/customer locale, else the org default currency.</span></div>
        {canEdit && <button className="btn primary sm" onClick={add}><Icon name="plus" size={14} /> Add market</button>}
      </div>
      {rows.length === 0
        ? <div className="ps-empty-note"><Icon name="globe" size={20} /><div><b>No market prices yet</b><span>Add a market to publish a localized price (USD, CAD, GBP, EUR, NGN…).</span></div></div>
        : <div className="ps-price-table">
            <div className="ps-price-th"><span>Country / market</span><span>Currency</span><span>Amount</span><span>Tax</span><span>Preview</span><span /></div>
            {rows.map((r, i) => (
              <div key={r.id} className="ps-price-row">
                <input className="ps-price-in" value={r.country} placeholder="e.g. Canada" disabled={!canEdit} onChange={(e) => upd(i, "country", e.target.value)} />
                <select className="ps-price-in" value={r.currency} disabled={!canEdit} onChange={(e) => upd(i, "currency", e.target.value)}>{curList.map((c) => <option key={c.code} value={c.code}>{c.code}</option>)}</select>
                <input className="ps-price-in" type="number" value={r.amount} placeholder="0.00" disabled={!canEdit} onChange={(e) => upd(i, "amount", e.target.value)} />
                <label className="ps-price-tax"><input type="checkbox" checked={!!r.taxIncluded} disabled={!canEdit} onChange={(e) => upd(i, "taxIncluded", e.target.checked)} /> incl</label>
                <span className="ps-price-prev">{fmt(r.amount, r.currency)}<small>{r.taxIncluded ? " tax in" : " + tax"}</small></span>
                {canEdit ? <button className="ps-price-del" onClick={() => del(i)} title="Remove"><Icon name="trash-2" size={14} /></button> : <span />}
              </div>
            ))}
          </div>}
    </div>
  );
}

/* ───────── Product Details ───────── */
function PsDetails({ offering, canEdit, onChange }) {
  const spec = psGet(offering.id);
  const d = spec.details || {};
  const set = (k, v) => { psPatch(offering.id, "details", { [k]: v }); onChange(); };
  return (
    <div className="ps-form-grid">
      <PsText label="Brand name" value={d.brandName} onChange={(v) => set("brandName", v)} placeholder="e.g. Harvest Table" disabled={!canEdit} />
      <PsText label="SKU" value={d.sku} onChange={(v) => set("sku", v)} placeholder="e.g. HT-MPB-001" disabled={!canEdit} />
      <PsSelect label="Category" value={d.category} onChange={(v) => set("category", v)} options={PS_CATEGORIES} disabled={!canEdit} />
      <PsText label="Country of origin" value={d.countryOfOrigin} onChange={(v) => set("countryOfOrigin", v)} placeholder="e.g. Canada" disabled={!canEdit} />
      <PsText label="Net weight" type="number" value={d.netWeightG} onChange={(v) => set("netWeightG", v)} suffix="g" disabled={!canEdit} />
      <PsText label="Gross weight" type="number" value={d.grossWeightG} onChange={(v) => set("grossWeightG", v)} suffix="g" disabled={!canEdit} />
      <PsText label="Serving size" type="number" value={d.servingSizeG} onChange={(v) => set("servingSizeG", v)} suffix="g" disabled={!canEdit} hint="Drives NFt %DV and FOP thresholds" />
      <PsText label="Servings per container" type="number" value={d.servingsPerContainer} onChange={(v) => set("servingsPerContainer", v)} disabled={!canEdit} />
    </div>
  );
}

/* ───────── Packaging (with live case/pallet weight) ───────── */
function PsPackaging({ offering, canEdit, onChange }) {
  const spec = psGet(offering.id);
  const p = spec.packaging || {};
  const set = (k, v) => { psPatch(offering.id, "packaging", { [k]: v }); onChange(); };
  const caseWeight = psCaseWeight(p.grossUnitWeightG, p.casePackQty, p.casePackagingWeightG);
  const palletWeight = psPalletWeight(caseWeight, p.palletCases, p.palletWeightG);
  return (
    <>
      <div className="ps-form-grid">
        <PsSelect label="Package type" value={p.packageType} onChange={(v) => set("packageType", v)} options={PS_PACKAGE_TYPES} disabled={!canEdit} />
        <PsSelect label="Primary material" value={p.material} onChange={(v) => set("material", v)} options={PS_MATERIALS} disabled={!canEdit} />
        <PsText label="Gross unit weight" type="number" value={p.grossUnitWeightG} onChange={(v) => set("grossUnitWeightG", v)} suffix="g" disabled={!canEdit} />
        <PsText label="Units per case" type="number" value={p.casePackQty} onChange={(v) => set("casePackQty", v)} disabled={!canEdit} />
        <PsText label="Case packaging weight" type="number" value={p.casePackagingWeightG} onChange={(v) => set("casePackagingWeightG", v)} suffix="g" disabled={!canEdit} hint="Box / dividers (optional)" />
        <PsText label="Cases per pallet" type="number" value={p.palletCases} onChange={(v) => set("palletCases", v)} disabled={!canEdit} />
        <PsText label="Pallet base weight" type="number" value={p.palletWeightG} onChange={(v) => set("palletWeightG", v)} suffix="g" disabled={!canEdit} hint="Empty pallet (optional)" />
      </div>
      <div className="ps-dims">
        <span className="ps-dims-lbl">Package dimensions (mm)</span>
        <div className="ps-dims-row">
          <PsText label="W" type="number" value={p.widthMm} onChange={(v) => set("widthMm", v)} disabled={!canEdit} />
          <PsText label="H" type="number" value={p.heightMm} onChange={(v) => set("heightMm", v)} disabled={!canEdit} />
          <PsText label="D" type="number" value={p.depthMm} onChange={(v) => set("depthMm", v)} disabled={!canEdit} />
        </div>
      </div>
      <div className="ps-calc">
        <div className="ps-calc-card"><span className="ps-calc-k">Case weight</span><span className="ps-calc-v">{(caseWeight / 1000).toFixed(2)} kg</span><span className="ps-calc-f">{(p.grossUnitWeightG || 0)}g × {(p.casePackQty || 0)} + {(p.casePackagingWeightG || 0)}g</span></div>
        <div className="ps-calc-card"><span className="ps-calc-k">Pallet weight</span><span className="ps-calc-v">{(palletWeight / 1000).toFixed(1)} kg</span><span className="ps-calc-f">{(caseWeight / 1000).toFixed(2)}kg × {(p.palletCases || 0)} + {(p.palletWeightG || 0)}g</span></div>
        <div className="ps-calc-card"><span className="ps-calc-k">Units per pallet</span><span className="ps-calc-v">{((p.casePackQty || 0) * (p.palletCases || 0)).toLocaleString()}</span><span className="ps-calc-f">{(p.casePackQty || 0)} × {(p.palletCases || 0)} cases</span></div>
      </div>
    </>
  );
}

/* ───────── Storage & Shelf Life ───────── */
function PsStorage({ offering, canEdit, onChange }) {
  const spec = psGet(offering.id);
  const s = spec.storage || {};
  const set = (k, v) => { psPatch(offering.id, "storage", { [k]: v }); onChange(); };
  const bestBefore = (() => {
    if (!s.shelfLifeDays || !s.productionDate) return null;
    const d = new Date(s.productionDate); if (isNaN(d)) return null;
    d.setDate(d.getDate() + Number(s.shelfLifeDays)); return d.toISOString().slice(0, 10);
  })();
  return (
    <>
      <div className="ps-form-grid">
        <PsSelect label="Storage type" value={s.storageType} onChange={(v) => set("storageType", v)} options={Object.keys(PS_STORAGE_TYPES).map((k) => ({ v: k, t: PS_STORAGE_TYPES[k] }))} disabled={!canEdit} />
        <PsText label="Shelf life" type="number" value={s.shelfLifeDays} onChange={(v) => set("shelfLifeDays", v)} suffix="days" disabled={!canEdit} />
        <PsText label="Min temperature" type="number" value={s.minTempC} onChange={(v) => set("minTempC", v)} suffix="°C" disabled={!canEdit} />
        <PsText label="Max temperature" type="number" value={s.maxTempC} onChange={(v) => set("maxTempC", v)} suffix="°C" disabled={!canEdit} />
        <PsText label="Production date" type="date" value={s.productionDate} onChange={(v) => set("productionDate", v)} disabled={!canEdit} />
      </div>
      <label className="ps-field full">
        <span>Storage instructions (label copy)</span>
        <textarea value={s.instructions || ""} disabled={!canEdit} placeholder="e.g. Keep refrigerated at 4°C. Consume within 3 days of opening." onChange={(e) => set("instructions", e.target.value)} />
      </label>
      {bestBefore && <div className="ps-note-row"><Icon name="calendar-clock" size={14} /> Best-before date computes to <b>{bestBefore}</b> from production + {s.shelfLifeDays} days.</div>}
    </>
  );
}

/* ───────── Manufacturing ───────── */
function PsManufacturing({ offering, canEdit, onChange }) {
  const spec = psGet(offering.id);
  const m = spec.manufacturing || {};
  const set = (k, v) => { psPatch(offering.id, "manufacturing", { [k]: v }); onChange(); };
  return (
    <>
      <div className="ps-form-grid">
        <PsText label="Facility name" value={m.facilityName} onChange={(v) => set("facilityName", v)} placeholder="e.g. Harvest Kitchen, Plant 2" disabled={!canEdit} />
        <PsText label="Facility ID / license" value={m.facilityId} onChange={(v) => set("facilityId", v)} placeholder="CFIA-00000" disabled={!canEdit} />
        <PsText label="Country of manufacture" value={m.countryOfManufacture} onChange={(v) => set("countryOfManufacture", v)} placeholder="Canada" disabled={!canEdit} />
        <PsText label="Production line" value={m.productionLine} onChange={(v) => set("productionLine", v)} placeholder="Line A" disabled={!canEdit} />
        <PsText label="Batch / lot code format" value={m.lotFormat} onChange={(v) => set("lotFormat", v)} placeholder="YYJJJ-LL" disabled={!canEdit} />
        <PsText label="Daily capacity (units)" type="number" value={m.dailyCapacity} onChange={(v) => set("dailyCapacity", v)} disabled={!canEdit} />
      </div>
      <label className="ps-field full">
        <span>Allergen control / cross-contact notes</span>
        <textarea value={m.allergenControl || ""} disabled={!canEdit} placeholder="e.g. Shared line with tree-nut products; full sanitation between runs." onChange={(e) => set("allergenControl", e.target.value)} />
      </label>
    </>
  );
}

/* ───────── GS1 / Barcode (in-context summary + inline assign slider) ───────── */
function PsGs1({ offering, setPage }) {
  const [, pcBump] = usePsState(0);
  const [slide, setSlide] = usePsState(false);
  const g = (typeof gs1ForOffering === "function") ? gs1ForOffering(offering.id) : { gtins: [], barcodes: [] };
  const s = (typeof gs1State === "function") ? gs1State() : { prefixes: [] };
  const prefixes = s.prefixes || [];
  const [prefixId, setPrefixId] = usePsState(prefixes[0] ? prefixes[0].id : "");
  const [gtinType, setGtinType] = usePsState("gtin_12");
  const pfx = prefixes.find((p) => p.id === prefixId);
  const refLen = pfx && typeof gs1ItemRefLen === "function" ? gs1ItemRefLen(gtinType, pfx.prefixLength) : 0;
  const [itemRef, setItemRef] = usePsState("");
  const [err, setErr] = usePsState(null);
  const Barc = window.Barcode;

  usePsEffect(() => { if (slide && pfx && typeof gs1NextItemRef === "function") setItemRef(gs1NextItemRef(gtinType, pfx.prefixLength)); }, [gtinType, prefixId, slide]);

  const preview = (() => {
    if (!pfx || itemRef.length !== refLen) return null;
    try {
      if (gtinType === "gtin_12") return gs1GenerateGTIN12(pfx.prefix, itemRef);
      if (gtinType === "gtin_13") return gs1GenerateGTIN13(pfx.prefix, itemRef);
      return gs1GenerateGTIN14("1", pfx.prefix, itemRef);
    } catch (e) { return null; }
  })();

  const assign = () => {
    const r = gs1AssignGTIN({ offeringId: offering.id, offeringName: offering.name, prefixId, gtinType, itemReference: itemRef, indicator: "1", assignedBy: psWho(window.__role) });
    if (!r.ok) { setErr(r.reason); return; }
    // immediately generate the matching barcode so the step completes in one slide
    try { gs1GenerateBarcode({ gtinId: r.rec.id, barcodeType: GTIN_TYPES[gtinType].barcode, who: psWho(window.__role) }); } catch (e) {}
    setErr(null); setSlide(false); pcBump((n) => n + 1);
  };

  return (
    <div className="ps-link-card">
      {g.gtins.length === 0
        ? <div className="ps-empty-note"><Icon name="scan-barcode" size={20} /><div><b>No GTIN assigned</b><span>Assign a GS1 GTIN and generate a scannable barcode for this product.</span></div></div>
        : (
          <div className="ps-gtin-list">
            {g.gtins.map((gt) => (
              <div key={gt.id} className="ps-gtin-row">
                <span className="pill brand" style={{ fontSize: 10 }}>{(GTIN_TYPES[gt.gtinType] || {}).label}</span>
                <span className="gs-mono" style={{ fontSize: 13 }}>{gt.gtin}</span>
                {g.barcodes.some((b) => b.gtinId === gt.id) ? <span className="gs-done"><Icon name="check" size={13} /> Barcode</span> : <span className="ps-pending">Barcode pending</span>}
              </div>
            ))}
          </div>
        )}
      <div className="ps-gs1-actions">
        <button className="btn primary sm" onClick={() => { try { window.gs1SetEnabled && window.gs1SetEnabled(true); } catch (e) {} setSlide(true); }}><Icon name="plus" size={14} /> Assign GTIN &amp; barcode</button>
        <button className="btn ghost sm" onClick={() => { try { window.gs1SetEnabled && window.gs1SetEnabled(true); } catch (e) {} setPage("gs1"); }}><Icon name="external-link" size={14} /> Open GS1 module</button>
      </div>

      {slide && (
        <div className="psg-scrim" onClick={() => setSlide(false)}>
          <div className="psg-drawer" onClick={(e) => e.stopPropagation()} role="dialog">
            <div className="ps-slide-h"><div><b>Assign GTIN</b><span>{offering.name}</span></div><button className="fop-drawer-close" onClick={() => setSlide(false)}><Icon name="x" size={18} /></button></div>
            <div className="ps-slide-body">
              {prefixes.length === 0 && <div className="ps-empty-note"><Icon name="alert-triangle" size={18} /><div><b>No company prefix</b><span>Add a GS1 company prefix in the GS1 module first.</span></div></div>}
              {prefixes.length > 0 && <>
                <label className="ps-field"><span>Company prefix</span>
                  <select value={prefixId} onChange={(e) => setPrefixId(e.target.value)}>{prefixes.map((p) => <option key={p.id} value={p.id}>{p.prefix} · {p.prefixLength}-digit</option>)}</select>
                </label>
                <div className="ps-field"><span>GTIN type</span>
                  <div className="gs-seg">{Object.keys(GTIN_TYPES).map((k) => <button key={k} className={`gs-seg-btn ${gtinType === k ? "on" : ""}`} onClick={() => setGtinType(k)}>{GTIN_TYPES[k].label}</button>)}</div>
                  <div className="gs-hint">{GTIN_TYPES[gtinType].desc}</div>
                </div>
                <label className="ps-field"><span>Item reference ({refLen} digit{refLen === 1 ? "" : "s"})</span>
                  <input value={itemRef} onChange={(e) => { setItemRef(e.target.value.replace(/\D/g, "").slice(0, refLen)); setErr(null); }} inputMode="numeric" placeholder={"0".repeat(Math.max(refLen, 1))} />
                  <span className="ps-hint">Check digit is added automatically.</span>
                </label>
                <div className="gs-preview">
                  <div className="gs-preview-lbl">Generated GTIN + barcode</div>
                  {preview ? <><div className="gs-preview-gtin">{preview}<span className="gs-preview-cd">{preview.slice(-1)}</span></div>{Barc && <Barc type={GTIN_TYPES[gtinType].barcode} value={preview} />}</> : <div className="gs-preview-empty">Complete the item reference to preview.</div>}
                </div>
                {err && <div className="gs-error"><Icon name="alert-circle" size={14} /> {err}</div>}
              </>}
            </div>
            <div className="ps-slide-foot"><button className="btn ghost" onClick={() => setSlide(false)}>Cancel</button><button className="btn primary" disabled={!preview} onClick={assign}><Icon name="check" size={15} /> Assign &amp; generate</button></div>
          </div>
        </div>
      )}
    </div>
  );
}

/* ───────── QR Code (inline generator slider) ───────── */
function PsQr({ offering }) {
  const [, pcBump] = usePsState(0);
  const [slide, setSlide] = usePsState(false);
  const [qrType, setQrType] = usePsState("nutrition_panel");
  const g = (typeof gs1ForOffering === "function") ? gs1ForOffering(offering.id) : { qrs: [] };
  const QRc = window.QR;
  const [publicLink, setPublicLink] = React.useState(true);
  const [menuPreview, setMenuPreview] = React.useState(null);
  // Resolve the org's first PUBLISHED restaurant menu (source of truth for the QR).
  const publishedMenu = React.useMemo(() => {
    try { return (window.RestaurantMenus ? window.RestaurantMenus.load() : []).find((m) => String(m.status).toUpperCase() === "PUBLISHED") || null; } catch (e) { return null; }
  }, [slide, qrType]);
  const previewUrl = React.useMemo(() => {
    if (qrType === "restaurant_menu") return `https://nutridms.com/m/${(offering.orgSlug || "your-business")}`;
    if (qrType === "meal_direct") return `https://nutridms.com/meal/${offering.id}`;
    const base = publicLink ? "https://nutridms.com/public/offerings/" : "https://go.nutridms.com/r/";
    return `${base}${offering.id}?view=${qrType}`;
  }, [qrType, publicLink, offering.id, offering.orgSlug]);
  const generate = () => {
    try { gs1AddQR({ offeringId: offering.id, offeringName: offering.name, qrType, who: psWho(window.__role) }); } catch (e) {}
    setSlide(false); pcBump((n) => n + 1);
  };
  return (
    <div className="ps-qr-wrap">
      <div className="ps-cx-head">
        <div><b>Customer Experience</b><span>Publish this offering as a live customer page, then reach it via QR, web link, table, or receipt. QR codes are one launch method below.</span></div>
        <button className="btn ghost sm" onClick={() => { try { window.__setPage && window.__setPage("customer-portal"); } catch (e) {} }}><Icon name="external-link" size={14} /> Open Customer Experience</button>
      </div>
      <div className="ps-cx-sub">QR codes</div>
      <div className="ps-qr-grid">
        {(g.qrs || []).length === 0
          ? <div className="ps-empty-note"><Icon name="qr-code" size={20} /><div><b>No QR codes</b><span>Generate QR codes (nutrition panel, digital menu, product page) on the right.</span></div></div>
          : (g.qrs || []).map((q) => (
            <div key={q.id} className="gs-qr-card">
              {QRc ? <QRc value={q.encodedUrl} size={120} /> : null}
              <span className="pill amber" style={{ fontSize: 10 }}>{(QR_TYPES[q.qrType] || {}).label}</span>
            </div>
          ))}
      </div>
      <button className="btn primary sm" onClick={() => { try { window.gs1SetEnabled && window.gs1SetEnabled(true); } catch (e) {} setSlide(true); }}><Icon name="plus" size={14} /> Generate QR code</button>

      {slide && (
        <div className="psg-scrim" onClick={() => setSlide(false)}>
          <div className="psg-drawer" onClick={(e) => e.stopPropagation()} role="dialog">
            <div className="ps-slide-h"><div><b>Generate QR code</b><span>{offering.name}</span></div><button className="fop-drawer-close" onClick={() => setSlide(false)}><Icon name="x" size={18} /></button></div>
            <div className="ps-slide-body">
              <div className="ps-field"><span>QR type</span>
                <div className="ps-qr-types">
                  {Object.keys(QR_TYPES).map((k) => (
                    <button key={k} className={`ps-qr-type ${qrType === k ? "on" : ""}`} onClick={() => setQrType(k)}>
                      <Icon name={QR_TYPES[k].icon} size={15} /> <span><b>{QR_TYPES[k].label}</b><small>{QR_TYPES[k].desc}</small></span>
                    </button>
                  ))}
                </div>
              </div>
              {(qrType !== "restaurant_menu" && qrType !== "meal_direct") && (
                <label className="ps-field" style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer" }}>
                  <input type="checkbox" checked={publicLink} onChange={(e) => setPublicLink(e.target.checked)} />
                  <span style={{ margin: 0 }}>Public link (no login) — off routes through a NutriDMS redirect</span>
                </label>
              )}
              <div className="gs-preview">
                <div className="gs-preview-lbl">Preview</div>
                {QRc ? <QRc value={previewUrl} size={150} /> : null}
                <div className="ps-qr-url">{previewUrl}</div>
              </div>
              {qrType === "restaurant_menu" && (
                publishedMenu
                  ? <button className="btn ghost sm" style={{ marginTop: 10 }} onClick={() => setMenuPreview(publishedMenu)}><Icon name="smartphone" size={14} /> Preview customer menu ({publishedMenu.name})</button>
                  : <div className="ps-empty-note" style={{ marginTop: 10 }}><Icon name="book-open" size={18} /><div><b>No published menu yet</b><span>Publish a menu in Restaurant Menus, then this QR resolves to it.</span></div></div>
              )}
            </div>
            <div className="ps-slide-foot"><button className="btn ghost" onClick={() => setSlide(false)}>Cancel</button><button className="btn primary" onClick={generate}><Icon name="check" size={15} /> Generate QR</button></div>
          </div>
        </div>
      )}
      {menuPreview && window.RmMobilePreview && React.createElement(window.RmMobilePreview, { menu: menuPreview, onClose: () => setMenuPreview(null) })}
    </div>
  );
}

/* ───────── Claims (reuse Claims validator engine) ───────── */
function PsClaims({ offering }) {
  const recipe = (typeof RECIPES !== "undefined") ? RECIPES.find((r) => offering.single && offering.single.recipeId === r.id) : null;
  let results = null;
  try {
    if (recipe && window.lblNutrientProfile && window.claimsValidate) {
      const profile = window.lblNutrientProfile(recipe, (offering.single && offering.single.servingG) || 250);
      results = window.claimsValidate(profile, null);
    }
  } catch (e) {}
  if (!results) return <div className="ps-empty-note"><Icon name="badge-check" size={20} /><div><b>Claims validate against the linked recipe</b><span>Open this product in Label Studio to test nutrient-content claims live.</span></div></div>;
  return (
    <div className="ps-claims">
      {(results.results || []).slice(0, 8).map((c, i) => (
        <div key={i} className={`ps-claim ${c.status}`}>
          <Icon name={c.status === "eligible" ? "check-circle-2" : c.status === "warning" ? "alert-triangle" : "x-circle"} size={14} />
          <span className="ps-claim-nm">{c.label}</span>
          <span className={`pill ${c.status === "eligible" ? "success" : c.status === "warning" ? "warning" : "danger"}`} style={{ fontSize: 10 }}>{c.status}</span>
        </div>
      ))}
    </div>
  );
}

/* ───────── Documents ───────── */
function PsDocuments({ offering, canEdit, onChange }) {
  const spec = psGet(offering.id);
  const docs = spec.documents || [];
  const [name, setName] = usePsState("");
  const [kind, setKind] = usePsState("Spec sheet");
  const add = () => {
    if (!name.trim()) return;
    const next = [...docs, { id: "doc-" + Date.now(), name: name.trim(), kind, addedAt: new Date().toISOString().slice(0, 10) }];
    spec.documents = next; psSave(offering.id, spec); setName(""); onChange();
  };
  const remove = (id) => { spec.documents = docs.filter((d) => d.id !== id); psSave(offering.id, spec); onChange(); };
  return (
    <>
      {canEdit && (
        <div className="ps-doc-add">
          <select value={kind} onChange={(e) => setKind(e.target.value)}>{["Spec sheet", "COA", "Allergen statement", "Supplier doc", "Photo", "Other"].map((k) => <option key={k}>{k}</option>)}</select>
          <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Document name…" onKeyDown={(e) => { if (e.key === "Enter") add(); }} />
          <button className="btn secondary sm" onClick={add}><Icon name="paperclip" size={14} /> Attach</button>
        </div>
      )}
      <div className="ps-doc-list">
        {docs.length === 0 && <div className="gs-empty">No documents attached.</div>}
        {docs.map((d) => (
          <div key={d.id} className="ps-doc-row">
            <Icon name="file-text" size={15} />
            <div className="ps-doc-tx"><b>{d.name}</b><small>{d.kind} · {d.addedAt}</small></div>
            {canEdit && <button className="icon-btn sm" onClick={() => remove(d.id)}><Icon name="trash-2" size={14} /></button>}
          </div>
        ))}
      </div>
    </>
  );
}

/* ───────── Compliance tab (live gate) ───────── */
function PsCompliance({ offering, canEdit, setPage, toast, onChange, onSpecSheet }) {
  const comp = psCompletion(offering);
  const gate = psApprovalGate(offering);
  const spec = psGet(offering.id);
  const reviewAllergens = () => { psPatch(offering.id, null, {}); spec.allergensReviewed = true; psSave(offering.id, spec); onChange(); toast("Allergen declaration marked reviewed"); };
  return (
    <div className="ps-compliance">
      <div className={`ps-gate ${gate.ok ? "ok" : "blocked"}`}>
        <Icon name={gate.ok ? "shield-check" : "shield-alert"} size={18} />
        <div>
          <b>{gate.ok ? "Ready for approval" : "Approval blocked"}</b>
          <span>{gate.ok ? "All organization-required specifications are complete." : `${gate.errors.length} requirement(s) outstanding.`}</span>
        </div>
      </div>
      {!gate.ok && (
        <ul className="ps-gate-list">
          {gate.errors.map((e, i) => <li key={i}><Icon name="circle-dot" size={12} /> {e}</li>)}
        </ul>
      )}
      <div className="ps-check-grid">
        {comp.checks.map((c) => (
          <div key={c.k} className={`ps-check ${c.ok ? "ok" : "todo"}`}>
            <Icon name={c.ok ? "check-circle-2" : "circle"} size={14} /> {c.label}
          </div>
        ))}
      </div>
      {canEdit && !spec.allergensReviewed && (
        <button className="btn secondary sm" onClick={reviewAllergens}><Icon name="shield-check" size={14} /> Mark allergens reviewed</button>
      )}
      <div className="ps-compliance-links">
        <button className="btn ghost sm" onClick={() => { try { window.gs1SetEnabled && window.gs1SetEnabled(true); } catch (e) {} setPage("gs1"); }}><Icon name="scan-barcode" size={14} /> GS1 &amp; Barcodes</button>
        <button className="btn ghost sm" onClick={() => { try { window.labelStudioSetEnabled && window.labelStudioSetEnabled(true); } catch (e) {} setPage("label-studio"); }}><Icon name="tag" size={14} /> Label Studio</button>
        {onSpecSheet && <button className="btn ghost sm" onClick={onSpecSheet}><Icon name="file-text" size={14} /> Generate spec sheet</button>}
      </div>
    </div>
  );
}

/* ───────── Digital Menu Data (Menu Item type, PRD §3) ───────── */
function PsMenuData({ offering }) {
  const rid = offering.single && (typeof offering.single === "object" ? offering.single.recipeId : offering.single);
  const recipe = (typeof RECIPES !== "undefined") ? RECIPES.find((r) => r.id === rid) : null;
  let profile = null;
  try { if (recipe && window.lblNutrientProfile) profile = window.lblNutrientProfile(recipe, (offering.single && typeof offering.single === "object" && offering.single.servingG) || 250); } catch (e) {}
  const g = (typeof gs1ForOffering === "function") ? gs1ForOffering(offering.id) : { qrs: [] };
  const val = (k, u) => profile && profile[k] != null ? `${Math.round(profile[k])}${u || ""}` : "—";
  const rows = [
    { k: "Calories", v: val("energy_kcal", "") },
    { k: "Protein", v: val("protein_g", " g") },
    { k: "Carbohydrate", v: val("carbohydrate_g", " g") },
    { k: "Fat", v: val("fat_g", " g") },
    { k: "Sodium", v: val("sodium_mg", " mg") },
  ];
  const goMealPrograms = () => { try { window.mpEnabled && window.mpEnabled(); } catch (e) {} if (window.__setPage) window.__setPage("meal-programs"); };
  return (
    <div className="ps-menu-data">
      <div className="ps-empty-note" style={{ marginBottom: 14 }}>
        <Icon name="info" size={18} />
        <div><b>Digital menu fields</b><span>Calorie + macro data published to digital menu boards and the consumer-facing nutrition QR. Menu items don't require packaging, GS1, or barcodes.</span></div>
      </div>
      <div className="ps-md-grid">
        {rows.map((r) => <div key={r.k} className="ps-md-cell"><span>{r.k}</span><b>{r.v}</b></div>)}
      </div>
      <div className="ps-md-qr">
        {(g.qrs || []).length
          ? (g.qrs || []).map((q) => {
              const QRc = window.QR;
              return <div key={q.id} className="gs-qr-card">{QRc ? <QRc value={q.encodedUrl} size={110} /> : null}<span className="pill amber" style={{ fontSize: 10 }}>{(QR_TYPES[q.qrType] || {}).label}</span></div>;
            })
          : <div className="ps-empty-note"><Icon name="qr-code" size={18} /><div><b>No menu QR yet</b><span>Generate a nutrition-panel QR from the QR Code step to link the digital menu.</span></div></div>}
      </div>
      <div className="ps-md-link">
        <div><b>Meal programs</b><span>This menu item can be scheduled into meal programs &amp; planners.</span></div>
        <button className="btn secondary sm" onClick={goMealPrograms}><Icon name="calendar-range" size={14} /> Use in Meal Program</button>
      </div>
    </div>
  );
}

/* ───────── Processing & Yield (Master PRD Module 2 §12–§14) ───────── */
function PsProcessing({ offering, canEdit, onChange }) {
  const spec = psGet(offering.id);
  const yr = spec.processing || {};
  const set = (k, v) => { psPatch(offering.id, "processing", Object.assign({}, yr, { [k]: v })); onChange && onChange(); };
  const num = (v) => Number(v) || 0;
  const methods = window.YR_METHODS || [{ id: "raw", label: "Raw / No cooking" }];
  const purchased = num(yr.purchasedG), usableRaw = num(yr.usableRawG) || purchased, cooked = num(yr.cookedG), packaged = num(yr.packagedG) || cooked, servingG = num(yr.servingG);
  const trimLoss = purchased && usableRaw ? purchased - usableRaw : 0;
  const trimPct = purchased ? (trimLoss / purchased * 100) : 0;
  const cookYield = (window.yrYieldFactor && usableRaw && cooked) ? window.yrYieldFactor(usableRaw, cooked) : null;
  const overallYield = purchased && packaged ? (packaged / purchased * 100) : 0;
  const servings = servingG && packaged ? Math.floor(packaged / servingG) : 0;
  const msg = window.yrYieldMessage ? window.yrYieldMessage(usableRaw, cooked) : null;
  const flow = [["Purchased", purchased], ["Trimmed", usableRaw], ["Cooked", cooked], ["Packaged", packaged], ["Serving", servingG]];
  const maxW = Math.max.apply(null, flow.map((f) => f[1]).concat([1]));
  return (
    <div className="ps-proc">
      <div className="ps-empty-note" style={{ marginBottom: 14 }}><Icon name="flame" size={18} /><div><b>Cooking yield & retention</b><span>Records trim, cooking and moisture change so nutrition, servings and cost-per-serving reflect the finished product. Decision-support — authorized users may override.</span></div></div>
      <div className="ps-proc-grid">
        <div className="ps-cost-inputs">
          <div className="ps-sec-h">Weights</div>
          <PsText label="Purchased weight" type="number" value={yr.purchasedG || ""} disabled={!canEdit} onChange={(v) => set("purchasedG", v)} suffix="g" />
          <PsText label="Usable after trim" type="number" value={yr.usableRawG || ""} disabled={!canEdit} onChange={(v) => set("usableRawG", v)} suffix="g" hint="After peeling / trimming waste" />
          <PsText label="Final cooked weight" type="number" value={yr.cookedG || ""} disabled={!canEdit} onChange={(v) => set("cookedG", v)} suffix="g" />
          <PsText label="Packaged weight" type="number" value={yr.packagedG || ""} disabled={!canEdit} onChange={(v) => set("packagedG", v)} suffix="g" hint="Optional — defaults to cooked" />
          <PsText label="Serving weight" type="number" value={yr.servingG || ""} disabled={!canEdit} onChange={(v) => set("servingG", v)} suffix="g" />
          <div className="ps-sec-h" style={{ marginTop: 14 }}>Cooking method</div>
          <PsSelect label="Method" value={yr.methodId || "raw"} disabled={!canEdit} onChange={(v) => set("methodId", v)} options={methods.map((m) => ({ value: m.id, label: m.label }))} />
        </div>
        <div className="ps-cost-summary">
          <div className="ps-proc-flow">
            {flow.map(([l, w]) => (
              <div key={l} className="ps-proc-flowrow"><span className="ps-proc-fl">{l}</span><span className="ps-proc-fbar"><i style={{ width: Math.max(2, (w / maxW * 100)) + "%" }} /></span><span className="ps-proc-fv">{w ? Math.round(w) + "g" : "—"}</span></div>
            ))}
          </div>
          <div className="ps-cost-card"><span>Trim loss</span><b>{trimLoss ? Math.round(trimLoss) + "g (" + trimPct.toFixed(1) + "%)" : "—"}</b></div>
          <div className="ps-cost-card"><span>Cooking yield</span><b>{cookYield ? (cookYield * 100).toFixed(0) + "%" : "—"}</b></div>
          <div className="ps-cost-card accent"><span>Overall yield</span><b>{overallYield ? overallYield.toFixed(0) + "%" : "—"}</b></div>
          <div className="ps-cost-card accent"><span>Servings</span><b>{servings || "—"}</b></div>
          {msg && <div className={"ps-proc-msg " + msg.tone}><Icon name={msg.tone === "pass" ? "check-circle-2" : msg.tone === "warn" ? "alert-triangle" : "x-circle"} size={13} /> {msg.text}</div>}
        </div>
      </div>
    </div>
  );
}

if (typeof window !== "undefined") {
  Object.assign(window, { PsRing, PsDetails, PsPackaging, PsStorage, PsManufacturing, PsGs1, PsQr, PsClaims, PsDocuments, PsCompliance, PsMenuData, PsCosting, PsPricing, PsProcessing, psCanEdit, PsSpecSheet });
}

/* ───────── §17 Product Specification PDF (print-ready spec sheet) ───────── */
function PsSpecSheet({ offering, onClose }) {
  const spec = psGet(offering.id);
  const d = spec.details || {}, p = spec.packaging || {}, s = spec.storage || {}, m = spec.manufacturing || {};
  const recipe = (typeof RECIPES !== "undefined") ? RECIPES.find((r) => offering.single && offering.single.recipeId === r.id) : null;
  const g = (typeof gs1ForOffering === "function") ? gs1ForOffering(offering.id) : { gtins: [], barcodes: [], qrs: [] };
  const comp = psCompletion(offering);
  const gate = psApprovalGate(offering);
  const Barc = window.Barcode, QRc = window.QR;

  // Nutrition declaration
  let profile = null;
  try { if (recipe && window.lblNutrientProfile) profile = window.lblNutrientProfile(recipe, d.servingSizeG || (offering.single && offering.single.servingG) || 250); } catch (e) {}
  const ingStatement = (recipe && window.isIngredientStatement) ? window.isIngredientStatement(recipe, "en") : "";
  const allergenLines = (recipe && window.isAllergenStatement) ? window.isAllergenStatement(recipe, "en", []) : [];
  const containsLine = allergenLines.find((l) => l.kind === "contains");
  const mayLine = allergenLines.find((l) => l.kind === "may");

  const caseWeight = psCaseWeight(p.grossUnitWeightG, p.casePackQty, p.casePackagingWeightG);
  const palletWeight = psPalletWeight(caseWeight, p.palletCases, p.palletWeightG);

  usePsEffect(() => {
    const prev = document.body.style.overflow; document.body.style.overflow = "hidden";
    return () => { document.body.style.overflow = prev; };
  }, []);

  const doPrint = () => {
    window.print();
  };

  const Row = ({ k, v }) => v != null && v !== "" ? <div className="pss-row"><span>{k}</span><b>{v}</b></div> : null;
  const nutr = (key, unit) => profile && profile[key] != null ? `${Math.round(profile[key])}${unit}` : "—";

  return (
    <div className="pss-scrim" onClick={onClose}>
      <div className="pss-modal" onClick={(e) => e.stopPropagation()}>
        <div className="pss-toolbar">
          <span className="pss-toolbar-t"><Icon name="file-text" size={15} /> Product Specification Sheet</span>
          <div className="pss-toolbar-actions">
            <button className="btn primary sm" onClick={doPrint}><Icon name="printer" size={14} /> Print / Save PDF</button>
            <button className="icon-btn" onClick={onClose}><Icon name="x" size={18} /></button>
          </div>
        </div>

        <div className="pss-sheet" id="pss-print">
          {/* Identity */}
          <header className="pss-head">
            <div>
              <div className="pss-brand">{d.brandName || "—"}</div>
              <h1 className="pss-title">{offering.name}</h1>
              <div className="pss-sub">{(typeof ofType === "function" ? ofType(offering.type).label : offering.type)} · {d.category || "Uncategorized"}</div>
            </div>
            <div className="pss-id">
              <div className="pss-id-k">Reference</div>
              <div className="pss-id-v">{offering.id.toUpperCase()}</div>
              <div className="pss-status"><span className={`pill ${(OFFERING_STATUS[offering.status] || {}).tone || "neutral"}`} style={{ fontSize: 10 }}>{(OFFERING_STATUS[offering.status] || {}).label || offering.status}</span></div>
              <div className="pss-completion">Completion {comp.pct}%</div>
            </div>
          </header>

          <section className="pss-sec">
            <h2>1 · Product Identity</h2>
            <div className="pss-grid2">
              <Row k="Brand" v={d.brandName} /><Row k="SKU" v={d.sku} />
              <Row k="Category" v={d.category} /><Row k="Country of origin" v={d.countryOfOrigin} />
              <Row k="Net weight" v={d.netWeightG ? `${d.netWeightG} g` : null} /><Row k="Gross weight" v={d.grossWeightG ? `${d.grossWeightG} g` : null} />
              <Row k="Serving size" v={d.servingSizeG ? `${d.servingSizeG} g` : null} /><Row k="Servings / container" v={d.servingsPerContainer} />
              <Row k="Owner" v={offering.owner} /><Row k="Last updated" v={offering.updated} />
            </div>
          </section>

          <section className="pss-sec">
            <h2>2 · Nutrition Declaration {profile ? `(per ${profile.servingG} g)` : ""}</h2>
            {profile ? (
              <div className="pss-grid3">
                <Row k="Calories" v={nutr("calories", "")} /><Row k="Fat" v={nutr("fat", " g")} /><Row k="Saturated" v={nutr("satFat", " g")} />
                <Row k="Carbohydrate" v={nutr("carb", " g")} /><Row k="Fibre" v={nutr("fibre", " g")} /><Row k="Sugars" v={nutr("sugars", " g")} />
                <Row k="Protein" v={nutr("protein", " g")} /><Row k="Sodium" v={nutr("sodium", " mg")} /><Row k="Potassium" v={nutr("potassium", " mg")} />
              </div>
            ) : <p className="pss-empty">Link an approved recipe to declare nutrition.</p>}
          </section>

          <section className="pss-sec">
            <h2>3 · Ingredient Statement</h2>
            <p className="pss-prose">{ingStatement || "—"}</p>
          </section>

          <section className="pss-sec">
            <h2>4 · Allergen Declaration</h2>
            {containsLine || mayLine ? (
              <>
                <p className="pss-prose">{containsLine ? containsLine.en : "Contains: none declared."}</p>
                {mayLine && <p className="pss-prose">{mayLine.en}</p>}
              </>
            ) : <p className="pss-prose">No priority allergens detected in the ingredient list.</p>}
          </section>

          <section className="pss-sec">
            <h2>5 · Packaging Specification</h2>
            <div className="pss-grid2">
              <Row k="Package type" v={p.packageType} /><Row k="Material" v={p.material} />
              <Row k="Gross unit weight" v={p.grossUnitWeightG ? `${p.grossUnitWeightG} g` : null} /><Row k="Units / case" v={p.casePackQty} />
              <Row k="Case weight" v={caseWeight ? `${(caseWeight / 1000).toFixed(2)} kg` : null} /><Row k="Cases / pallet" v={p.palletCases} />
              <Row k="Pallet weight" v={palletWeight ? `${(palletWeight / 1000).toFixed(1)} kg` : null} /><Row k="Units / pallet" v={(p.casePackQty && p.palletCases) ? (p.casePackQty * p.palletCases).toLocaleString() : null} />
              <Row k="Dimensions (mm)" v={(p.widthMm || p.heightMm || p.depthMm) ? `${p.widthMm || "?"} × ${p.heightMm || "?"} × ${p.depthMm || "?"}` : null} />
            </div>
          </section>

          <section className="pss-sec">
            <h2>6 · Storage &amp; Shelf Life</h2>
            <div className="pss-grid2">
              <Row k="Storage type" v={s.storageType ? (PS_STORAGE_TYPES[s.storageType] || s.storageType) : null} /><Row k="Shelf life" v={s.shelfLifeDays ? `${s.shelfLifeDays} days` : null} />
              <Row k="Temp range" v={(s.minTempC != null || s.maxTempC != null) ? `${s.minTempC ?? "?"}–${s.maxTempC ?? "?"} °C` : null} /><Row k="Production date" v={s.productionDate} />
            </div>
            {s.instructions && <p className="pss-prose"><b>Instructions:</b> {s.instructions}</p>}
          </section>

          <section className="pss-sec">
            <h2>7 · Manufacturing Details</h2>
            <div className="pss-grid2">
              <Row k="Facility" v={m.facilityName} /><Row k="Facility ID" v={m.facilityId} />
              <Row k="Country of manufacture" v={m.countryOfManufacture} /><Row k="Production line" v={m.productionLine} />
              <Row k="Lot format" v={m.lotFormat} /><Row k="Daily capacity" v={m.dailyCapacity ? `${m.dailyCapacity} units` : null} />
            </div>
            {m.allergenControl && <p className="pss-prose"><b>Allergen control:</b> {m.allergenControl}</p>}
          </section>

          <section className="pss-sec pss-codes">
            <div className="pss-code-col">
              <h2>8 · GS1 / Barcode</h2>
              {g.gtins.length ? g.gtins.map((gt) => (
                <div key={gt.id} className="pss-bc">
                  <div className="pss-bc-meta">{(GTIN_TYPES[gt.gtinType] || {}).label} · {gt.gtin}</div>
                  {Barc && g.barcodes.some((b) => b.gtinId === gt.id) ? <Barc type={(GTIN_TYPES[gt.gtinType] || {}).barcode} value={gt.gtin} scale={2} height={11} /> : <span className="pss-empty">Barcode not generated</span>}
                </div>
              )) : <p className="pss-empty">No GTIN assigned.</p>}
            </div>
            <div className="pss-code-col">
              <h2>9 · QR Code</h2>
              {g.qrs && g.qrs.length ? g.qrs.map((q) => (
                <div key={q.id} className="pss-qr">
                  {QRc ? <QRc value={q.encodedUrl} size={96} /> : null}
                  <span className="pss-qr-lbl">{(QR_TYPES[q.qrType] || {}).label}</span>
                </div>
              )) : <p className="pss-empty">No QR codes.</p>}
            </div>
          </section>

          <section className="pss-sec">
            <h2>10 · Compliance Status</h2>
            <div className={`pss-gate ${gate.ok ? "ok" : "blocked"}`}>{gate.ok ? "✓ Cleared for approval, all organization-required specifications complete." : `✗ ${gate.errors.length} requirement(s) outstanding: ${gate.errors.join(" ")}`}</div>
            <div className="pss-check-grid">
              {comp.checks.map((c) => <span key={c.k} className={`pss-check ${c.ok ? "ok" : "todo"}`}>{c.ok ? "✓" : "○"} {c.label}</span>)}
            </div>
          </section>

          <footer className="pss-foot">
            <span>NutriDMS Product Specification · {offering.name} · {offering.id.toUpperCase()}</span>
            <span>Generated {new Date().toISOString().slice(0, 10)} · This document reflects current NutriDMS records and is for internal specification use.</span>
          </footer>
        </div>
      </div>
    </div>
  );
}
