/* Draft orders — Shopify drafts (quotes). List with Open/Completed filter + search,
   and an editor to amend items/customer/shipping/note, then either complete as an
   unpaid order (pay later) or mark paid, plus proforma-invoice + Revolut pay link. */

const DRAFT_STATUS_META = {
  OPEN:         { label: "Open",         cls: "badge--amber" },
  INVOICE_SENT: { label: "Invoice sent", cls: "badge--mint" },
  COMPLETED:    { label: "Completed",    cls: "badge--ghost" },
};
function DraftBadge({ status }) {
  const m = DRAFT_STATUS_META[status] || { label: status || "—", cls: "badge--ghost" };
  return <span className={"badge " + m.cls}>{m.label}</span>;
}

// Artwork on a draft (quote). Lean list + upload + delete, plus the gang-sheet
// builder — the same store as orders, keyed "draft-<id>". On completion the
// server re-homes these files onto the real order (see server/shopify.js).
function DraftArtworkCard({ draftId, customerId, customerName }) {
  const owner = useMemo(() => ({ type: "draft", id: draftId }), [draftId]);
  const [files, setFiles] = useState(null); // null = loading
  const [busy, setBusy] = useState(false);
  const [prog, setProg] = useState(0);
  const [err, setErr] = useState("");
  const [drag, setDrag] = useState(false);
  const [sel, setSel] = useState(() => new Set());
  const [gsOpen, setGsOpen] = useState(false);
  const [gsEdit, setGsEdit] = useState(null); // { files, rows } — edit an existing gang sheet pre-filled
  const [libOpen, setLibOpen] = useState(false);
  const [savingLib, setSavingLib] = useState(false);
  async function addSelToLibrary() {
    if (!customerId || !sel.size) return;
    setSavingLib(true);
    try { for (const id of sel) await DTF.libFromArtwork(customerId, owner, id); setSel(new Set()); }
    catch (e) { alert("Couldn't add to library: " + e.message); }
    finally { setSavingLib(false); }
  }
  const [textOpen, setTextOpen] = useState(false);
  const [menuFor, setMenuFor] = useState(null);   // file id with an open services menu
  const [copiedFor, setCopiedFor] = useState(null); // file id showing a "public link copied" tick
  const [pickerFor, setPickerFor] = useState(null); // colour-key picker file
  const [recolorFor, setRecolorFor] = useState(null); // recolour picker file
  const [vecFor, setVecFor] = useState(null);     // vectorise / recolour modal file
  const [adjVals, setAdjVals] = useState({ erode: 1, harden: 128, outline: 16, scale: 10, colortol: 15, highres: "1K", highresPrompt: "Upscale to high resolution, keep the design identical - do not change anything" });
  const [procById, setProcById] = useState({});   // fileId -> { step } | { error }
  const inputRef = React.useRef(null);
  const menuBtnRef = React.useRef(null); // open menu's trigger (for floating placement)
  const menuElRef = React.useRef(null);  // open menu element
  const toggleSel = (id) => setSel(s => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n; });
  const refresh = () => DTF.artworkForOwner(owner).then(setFiles);
  useEffect(() => { refresh(); }, [draftId]);
  // Float the services menu at its natural size, shifting up to respect the window
  // bottom (only scrolls if taller than the screen).
  const positionMenu = () => {
    if (!menuBtnRef.current || !menuElRef.current) return;
    const btn = menuBtnRef.current.getBoundingClientRect();
    const el = menuElRef.current, st = el.style;
    const vw = window.innerWidth, vh = window.innerHeight, pad = 8, topMin = 64; // topMin clears the fixed top bar
    st.maxHeight = ""; st.overflowY = ""; st.position = "fixed"; st.right = "auto";
    const w = Math.min(el.offsetWidth || 220, vw - 2 * pad);
    st.left = Math.max(pad, Math.min(btn.right - w, vw - w - pad)) + "px";
    const h = el.offsetHeight;
    let top = btn.bottom + 4;
    if (top + h > vh - pad) top = vh - pad - h;
    if (top < topMin) { top = topMin; st.maxHeight = (vh - topMin - pad) + "px"; st.overflowY = "auto"; }
    st.top = top + "px"; st.bottom = "auto";
  };
  React.useLayoutEffect(() => { if (menuFor) positionMenu(); }, [menuFor]);
  // Close on outside click / Escape; reposition (don't close) on page scroll.
  useEffect(() => {
    if (!menuFor) return;
    const close = () => setMenuFor(null);
    const key = e => { if (e.key === "Escape") setMenuFor(null); };
    const onScroll = e => { if (menuElRef.current && menuElRef.current.contains(e.target)) return; positionMenu(); };
    window.addEventListener("click", close); window.addEventListener("keydown", key);
    window.addEventListener("scroll", onScroll, true); window.addEventListener("resize", positionMenu);
    return () => { window.removeEventListener("click", close); window.removeEventListener("keydown", key); window.removeEventListener("scroll", onScroll, true); window.removeEventListener("resize", positionMenu); };
  }, [menuFor]);

  async function upload(list) {
    list = [...(list || [])].filter(Boolean);
    if (!list.length) return;
    setErr(""); setBusy(true);
    try { for (const f of list) { setProg(0); await DTF.uploadArtworkOwner(owner, f, p => setProg(p)); } await refresh(); }
    catch (ex) { setErr(ex.message || "Upload failed"); }
    setBusy(false); setProg(0);
  }
  async function del(id) { setFiles(await DTF.deleteArtworkOwner(owner, id)); setSel(s => { const n = new Set(s); n.delete(id); return n; }); }
  // Re-open the gang-sheet builder pre-filled with an existing sheet's layout.
  const isGang = (f) => /gang.?sheet/i.test(f.filename || "");
  async function editGang(f) {
    try {
      const comp = await DTF.gangComposition(owner, f.id);
      if (!comp || !comp.found || !(comp.images || []).length) { alert("Couldn't load this gang sheet's layout to edit it."); return; }
      const have = new Set((files || []).map(ff => ff.id));
      const rows = comp.images.filter(im => have.has(im.id)).map(im => ({ fid: im.id, dim: "w", mm: im.wMm, qty: im.qty }));
      if (!rows.length) { alert("The source images for this gang sheet aren't on the draft anymore, so it can't be edited."); return; }
      const compFiles = (files || []).filter(ff => rows.some(r => r.fid === ff.id));
      setGsEdit({ files: compFiles, rows, replaceId: f.id });
    } catch (e) { alert(e.message || "Couldn't open the gang sheet for editing."); }
  }
  // Artwork services — identical pipeline to orders, just scoped to this draft owner.
  async function runService(f, opts) {
    setMenuFor(null);
    const cur = procById[f.id];
    if (cur && !cur.error) return;
    setProcById(m => ({ ...m, [f.id]: { step: "starting" } }));
    try {
      await DTF.processArtwork(owner, null, { fileId: f.id, ...opts, onStep: s => setProcById(m => ({ ...m, [f.id]: { step: s } })) });
      setProcById(m => { const n = { ...m }; delete n[f.id]; return n; });
      await refresh();
    } catch (e) { setProcById(m => ({ ...m, [f.id]: { error: e.message || "Processing failed" } })); }
  }
  async function convertPdf(f) {
    const cur = procById[f.id];
    if (cur && !cur.error) return;
    setProcById(m => ({ ...m, [f.id]: { step: "starting" } }));
    try {
      await DTF.pdfToPng(owner, { fileId: f.id }, s => setProcById(m => ({ ...m, [f.id]: { step: s } })));
      setProcById(m => { const n = { ...m }; delete n[f.id]; return n; });
      await refresh();
    } catch (e) { setProcById(m => ({ ...m, [f.id]: { error: e.message || "Conversion failed" } })); }
  }

  return (
    <div className="card">
      <div className="card__head"><Icon name="image" /><h3>Artwork</h3>
        <div className="right" style={{ display: "flex", alignItems: "center", gap: 10 }}>
          {files && files.length > 0 && <span className="kick">{files.length} file{files.length === 1 ? "" : "s"}</span>}
          <button className="btn btn--ghost btn--sm" disabled={!customerId} title={customerId ? "Browse this customer's artwork library" : "Link a customer to this draft first"} onClick={() => setLibOpen(true)}><Icon name="image" /> Customer library</button>
        </div></div>
      <div className="card__body">
        <div className="dim" style={{ fontSize: 12.5, marginBottom: 10 }}>Print files for this quote. They move to the order automatically when the draft is completed.</div>
        {files === null ? <div className="dim" style={{ fontSize: 12.5 }}>Loading…</div>
          : files.length === 0 ? null
          : <div className="art-list">{files.map(f => {
              const isImg = /^image\//.test(f.contentType);
              const isPdf = /pdf/i.test(f.contentType || "") || /\.pdf$/i.test(f.filename || "");
              const isSvg = /svg/i.test(f.contentType || "") || /\.svg$/i.test(f.filename || "");
              const p = procById[f.id];
              const running = p && !p.error;
              const failed = p && p.error;
              const openMenu = e => { e.preventDefault(); e.stopPropagation(); if (!running) setMenuFor(m => m === f.id ? null : f.id); };
              return (
              <React.Fragment key={f.id}>
              <div className={"art-row" + (sel.has(f.id) ? " is-sel" : "")} onContextMenu={isImg ? openMenu : undefined}>
                {isImg && <label className="art-check" onClick={e => e.stopPropagation()} title="Select for gang sheet"><input type="checkbox" checked={sel.has(f.id)} onChange={() => toggleSel(f.id)} /></label>}
                <a className="art-thumb" href={DTF.artworkUrlOwner(owner, f.id)} target="_blank" rel="noopener" title="Open">
                  {isImg ? <img src={DTF.artThumbUrlOwner(owner, f.id, 96)} alt="" /> : <Icon name="doc" />}
                </a>
                <div className="art-meta">
                  <a className="art-name" href={DTF.artworkUrlOwner(owner, f.id)} target="_blank" rel="noopener" title={"Open " + f.filename}>{f.filename}</a>
                  <div className="mono dim art-sub">{f.width ? f.width + "×" + f.height + "px · " : ""}{window.fmtBytes ? window.fmtBytes(f.size) : f.size + "B"}</div>
                  {running && <div className="art-proc"><span className="spin spin--dark" /> {p.step || "working…"} · ~1 min</div>}
                  {failed && <div className="art-proc art-proc--err">{p.error}</div>}
                </div>
                <div className="art-actions">
                  {copiedFor === f.id && <span className="kick" style={{ color: "var(--mint-ink)", alignSelf: "center" }}>Public link copied ✓</span>}
                  {isImg && (
                    <div className="art-menu-wrap" onClick={e => e.stopPropagation()}>
                      <button ref={menuFor === f.id ? menuBtnRef : null} className="btn btn--ghost btn--sm" disabled={!!running} onClick={openMenu} title="Artwork services (or right-click the row)"><Icon name="spark" /> Services ▾</button>
                      {menuFor === f.id && (
                        <div className="art-menu" ref={menuElRef}>
                          {ART_SERVICES.map(s => s.adjust ? (() => {
                            const val = adjVals[s.adjust.opt];
                            return (
                            <div key={s.key} className="art-menu__adj">
                              <div className="art-menu__adj-label">{s.label}</div>
                              <div className="art-menu__adj-row">
                                <input type="range" min={s.adjust.min} max={s.adjust.max} value={val} onChange={e => setAdjVals(m => ({ ...m, [s.adjust.opt]: Number(e.target.value) }))} />
                                <span className="art-menu__adj-val mono">{val}</span>
                                <button className="btn btn--sm" onClick={() => runService(f, { ...s.opts, [s.adjust.opt]: val })}>Apply</button>
                              </div>
                              <div className="art-menu__adj-hint">{s.adjust.hint(val)}</div>
                            </div>
                            );
                          })() : s.choices ? (() => {
                            const val = adjVals[s.choices.opt];
                            return (
                            <div key={s.key} className="art-menu__adj">
                              <div className="art-menu__adj-label">{s.label}</div>
                              {s.promptOpt && (
                                <input className="rm-in" style={{ width: "100%", boxSizing: "border-box", marginBottom: 6, fontSize: 12 }}
                                  value={adjVals[s.promptOpt] ?? ""} placeholder="AI prompt" title="Prompt sent to the AI — edit to change the result"
                                  onChange={e => setAdjVals(m => ({ ...m, [s.promptOpt]: e.target.value }))} />
                              )}
                              <div className="art-menu__adj-row">
                                <div className="seg art-menu__seg">
                                  {s.choices.values.map(v => (
                                    <button key={v} className={val === v ? "is-active" : ""} onClick={() => setAdjVals(m => ({ ...m, [s.choices.opt]: v }))}>{s.choices.fmt ? s.choices.fmt(v) : "×" + v}</button>
                                  ))}
                                </div>
                                <button className="btn btn--sm" onClick={() => runService(f, { ...s.opts, [s.choices.opt]: val, ...(s.promptOpt ? { [s.promptOpt]: adjVals[s.promptOpt] } : {}) })}>Apply</button>
                              </div>
                              <div className="art-menu__adj-hint">{s.choices.hint(val)}</div>
                            </div>
                            );
                          })() : s.action === "pick" ? (
                            <button key={s.key} className="art-menu__item" onClick={() => { setMenuFor(null); setPickerFor(f); }}>{s.label}</button>
                          ) : s.action === "recolor" ? (
                            <button key={s.key} className="art-menu__item" onClick={() => { setMenuFor(null); setRecolorFor(f); }}>{s.label}</button>
                          ) : s.action === "vectorize" ? (
                            <button key={s.key} className="art-menu__item" onClick={() => { setMenuFor(null); setVecFor(f); }}>{s.label}</button>
                          ) : s.action === "copylink" ? (
                            <button key={s.key} className="art-menu__item" onClick={async () => { setMenuFor(null); const u = await copyArtworkPublicUrl("draft-" + String(draftId).replace(/\D/g, ""), f.id); if (u) { setCopiedFor(f.id); setTimeout(() => setCopiedFor(c => c === f.id ? null : c), 1800); } }}>{s.label}</button>
                          ) : (
                            <button key={s.key} className="art-menu__item" onClick={() => runService(f, s.opts)}>{s.label}</button>
                          ))}
                        </div>
                      )}
                    </div>
                  )}
                  {isGang(f) && <button className="btn btn--ghost btn--sm" disabled={!!running} onClick={() => editGang(f)} title="Re-open the gang-sheet builder pre-filled with this sheet's images, sizes and quantities"><Icon name="image" /> Edit gang sheet</button>}
                  {isPdf && <button className="btn btn--ghost btn--sm" disabled={!!running} onClick={() => convertPdf(f)} title="Rasterise the PDF's first page to a print-ready transparent PNG @300 DPI and attach"><Icon name="image" /> Convert to PNG</button>}
                  {isSvg && <button className="btn btn--ghost btn--sm" disabled={!!running} onClick={() => setVecFor(f)} title="Recolour this vector's palette and save as PNG"><Icon name="spark" /> Recolour</button>}
                  <a className="btn btn--ghost btn--sm" href={DTF.artworkUrlOwner(owner, f.id, true)}><Icon name="download" /> Download</a>
                  <button className="btn btn--ghost btn--sm" onClick={() => del(f.id)}>Delete</button>
                </div>
              </div>
              {isGang(f) && <GangSheetInfo owner={owner} fileId={f.id} />}
              </React.Fragment>
              );
            })}</div>}
        {pickerFor && <ColorKeyPicker owner={owner} file={pickerFor}
          onCancel={() => setPickerFor(null)}
          onApply={(payload) => {
            const f = pickerFor; setPickerFor(null);
            if (payload.mode === "all") runService(f, { removeBg: false, upscale: false, colorkey: payload.hex, colortol: payload.tol });
            else runService(f, { removeBg: false, upscale: false, wandmode: payload.mode, wandseeds: payload.seeds, colortol: payload.tol });
          }} />}
        {recolorFor && <RecolorPicker owner={owner} file={recolorFor}
          onCancel={() => setRecolorFor(null)}
          onApply={({ hex }) => { const f = recolorFor; setRecolorFor(null); runService(f, { removeBg: false, upscale: false, recolor: hex }); }} />}
        {vecFor && <VectorizeRecolorModal owner={owner} file={vecFor}
          onCancel={() => setVecFor(null)}
          onDone={async () => { setVecFor(null); await refresh(); }} />}
        {files && files.some(f => /^image\//.test(f.contentType) && f.width) && (() => {
          const gsFiles = files.filter(f => /^image\//.test(f.contentType) && f.width);
          const allSel = gsFiles.length > 0 && gsFiles.every(f => sel.has(f.id));
          return (
          <div className="gs-bar" style={{ display: "flex", gap: 10, alignItems: "center", marginTop: 10 }}>
            <button className="btn btn--ghost btn--sm" disabled={sel.size < 1} onClick={() => setGsOpen(true)}><Icon name="image" /> Make gang sheet{sel.size ? ` (${sel.size})` : ""}</button>
            {customerId && <button className="btn btn--ghost btn--sm" disabled={sel.size < 1 || savingLib} onClick={addSelToLibrary} title="Copy the ticked artwork into this customer's library">{savingLib ? <span className="spin spin--dark" /> : <Icon name="image" />} Add to library{sel.size ? ` (${sel.size})` : ""}</button>}
            <button className="linklike" onClick={() => setSel(allSel ? new Set() : new Set(gsFiles.map(f => f.id)))}>{allSel ? "deselect all" : `select all (${gsFiles.length})`}</button>
            {sel.size > 0 && !allSel && <button className="linklike" onClick={() => setSel(new Set())}>clear</button>}
            <span className="dim" style={{ fontSize: 11.5 }}>tick artworks to place them on a 57 cm roll</span>
          </div>
          );
        })()}
        {gsOpen && <GangSheetBuilder owner={owner} files={(files || []).filter(f => sel.has(f.id))}
          onCancel={() => setGsOpen(false)}
          onDone={async () => { setGsOpen(false); setSel(new Set()); await refresh(); }} />}
        {gsEdit && <GangSheetBuilder owner={owner} customerId={customerId} files={gsEdit.files} initialRows={gsEdit.rows}
          onCancel={() => setGsEdit(null)}
          onDone={async (newFile) => { const rid = gsEdit && gsEdit.replaceId; setGsEdit(null); try { if (newFile && rid) await DTF.deleteArtworkOwner(owner, rid); } catch {} await refresh(); }} />}
        {libOpen && <CustomerLibraryModal customerId={customerId} customerName={customerName} owner={owner}
          onClose={() => setLibOpen(false)}
          onAssigned={(fs) => { if (fs && fs.length) setFiles(fs); refresh(); }} />}
        <div style={{ marginTop: 12 }}>
          <button className="btn btn--ghost btn--sm" onClick={() => setTextOpen(true)} title="Type text, pick a font & colour, set the print size → renders a print-ready PNG"><Icon name="spark" /> Add text artwork</button>
        </div>
        {textOpen && <TextArtworkBuilder owner={owner}
          onCancel={() => setTextOpen(false)}
          onDone={async () => { setTextOpen(false); await refresh(); }} />}
        {err && <div style={{ color: "var(--c-red,#c0392b)", fontSize: 12, marginTop: 8 }}>{err}</div>}
        <div className={"art-drop" + (drag ? " is-drag" : "")}
          onDragOver={e => { e.preventDefault(); setDrag(true); }} onDragLeave={() => setDrag(false)}
          onDrop={e => { e.preventDefault(); setDrag(false); upload(e.dataTransfer.files); }}
          onClick={() => inputRef.current && inputRef.current.click()} style={{ marginTop: 12 }}>
          {busy ? <span>Uploading… {Math.round(prog * 100)}%</span> : <span><Icon name="plus" /> Drop files or click to upload artwork</span>}
        </div>
        <input ref={inputRef} type="file" multiple style={{ display: "none" }} onChange={e => { const fs = [...e.target.files]; e.target.value = ""; upload(fs); }} />
      </div>
    </div>
  );
}

function DraftOrders({ go }) {
  const [status, setStatus] = useState("open");     // open | completed | all
  const [q, setQ] = useState("");
  const [res, setRes] = useState(null);             // null = loading; else { drafts, scopeMissing?, error? }
  useEffect(() => {
    let alive = true; setRes(null);
    DTF.fetchDraftOrders(status).then(r => { if (alive) setRes(r); }).catch(e => { if (alive) setRes({ drafts: [], error: e.message }); });
    return () => { alive = false; };
  }, [status]);

  const rows = res && res.drafts;
  const needle = q.trim().toLowerCase();
  const list = (rows || []).filter(d => !needle
    || (d.name || "").toLowerCase().includes(needle)
    || ((d.customer && d.customer.name) || (d.ship && d.ship.name) || "").toLowerCase().includes(needle)
    || String(d.total).includes(needle));

  const Tab = ({ id, label }) => (
    <button className={"btn btn--sm " + (status === id ? "btn--primary" : "btn--ghost")} onClick={() => setStatus(id)}>{label}</button>
  );

  return (
    <div className="board">
      <div className="toolbar" style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
        <Tab id="open" label="Open" />
        <Tab id="completed" label="Completed" />
        <Tab id="all" label="All" />
        <input className="rm-in" style={{ marginLeft: "auto", minWidth: 220 }} placeholder="Search draft # / customer / total…" value={q} onChange={e => setQ(e.target.value)} />
      </div>
      {res && res.scopeMissing
        ? <div className="card"><div className="card__body">
            <div style={{ fontWeight: 700, marginBottom: 6 }}>Draft orders need extra Shopify permissions</div>
            <div className="dim" style={{ fontSize: 13, lineHeight: 1.6 }}>
              The CRM's Shopify app token can't read drafts yet. In Shopify admin → <b>Settings → Apps and sales channels → Develop apps → [your CRM app] → Configuration → Admin API access scopes</b>, enable <b>read_draft_orders</b>, <b>write_draft_orders</b> and <b>write_orders</b>, then <b>reinstall</b> the app, paste the new <span className="mono">shpat_…</span> token into the server <span className="mono">.env</span> (<span className="mono">SHOPIFY_ADMIN_TOKEN</span>) and restart.
            </div>
          </div></div>
        : res && res.error
          ? <div className="dim" style={{ color: "var(--c-red)", padding: "8px 0" }}>{res.error}</div>
          : res === null
        ? <div className="empty"><span className="spin spin--dark" /><div className="t">Loading draft orders…</div></div>
        : list.length === 0
          ? <div className="empty"><Icon name="doc" /><div className="t">No draft orders</div><div className="m">Drafts you create in Shopify appear here.</div></div>
          : (
            <table className="rm-svc" style={{ width: "100%" }}>
              <thead><tr><th>Draft</th><th>Date</th><th>Customer</th><th>Items</th><th className="num">Total</th><th>Status</th></tr></thead>
              <tbody>
                {list.map(d => (
                  <tr key={d.legacyId} style={{ cursor: "pointer" }} onClick={() => go({ view: "draft", id: d.legacyId })}>
                    <td className="mono">{d.name}</td>
                    <td>{d.date}</td>
                    <td>{(d.customer && d.customer.name) || (d.ship && d.ship.name) || "—"}</td>
                    <td>{(d.items || []).reduce((s, i) => s + (i.qty || 0), 0)} units · {(d.items || []).length} lines</td>
                    <td className="num">{DTF.fmtGBP(d.total)}</td>
                    <td><DraftBadge status={d.status} /></td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
    </div>
  );
}
window.DraftOrders = DraftOrders;

function DraftOrderDetail({ id, go }) {
  const [d, setD] = useState(undefined);            // undefined=loading, null=not found
  const [items, setItems] = useState([]);
  const [email, setEmail] = useState("");
  const [note, setNote] = useState("");
  const [ship, setShip] = useState({});
  const [shipLine, setShipLine] = useState({ title: "", price: "" });
  const [discount, setDiscount] = useState("");
  const [discountType, setDiscountType] = useState("fixed"); // "fixed" (£) | "pct" (%)
  const [customer, setCustomer] = useState(null);   // { id, name, email, phone } | null
  const [saving, setSaving] = useState(false);
  const [msg, setMsg] = useState("");
  const [completing, setCompleting] = useState("");
  const [emailOpen, setEmailOpen] = useState(false);
  const emailRef = React.useRef(null);
  useEffect(() => { if (emailOpen && emailRef.current) emailRef.current.scrollIntoView({ behavior: "smooth", block: "start" }); }, [emailOpen]);
  const [rvUrl, setRvUrl] = useState(""); const [rvBusy, setRvBusy] = useState(false); const [rvCopied, setRvCopied] = useState(false);
  const [rvStatus, setRvStatus] = useState(null); // { hasLink, url, state, paid } | null
  const [rvChecking, setRvChecking] = useState(false);

  const hydrate = (dr) => {
    setD(dr);
    if (!dr) return;
    setItems((dr.items || []).map(i => ({ ...i })));
    setEmail(dr.email || (dr.customer && dr.customer.email) || "");
    setNote(dr.note || "");
    setShip(dr.ship ? { ...dr.ship } : {});
    setShipLine({ title: dr.shippingTitle || (dr.shipping ? "Shipping" : ""), price: dr.shipping || "" });
    // Show a percentage discount as the % entered (not the resolved £).
    if (dr.discountValueType === "PERCENTAGE" && dr.discountValue) {
      setDiscountType("pct"); setDiscount(String(dr.discountValue));
    } else {
      setDiscountType("fixed"); setDiscount(dr.discount ? String(dr.discount) : "");
    }
    setCustomer(dr.customer && dr.customer.id ? { ...dr.customer } : null);
  };
  useEffect(() => {
    let alive = true; setD(undefined);
    DTF.fetchDraftOrder(id).then(dr => { if (alive) hydrate(dr || null); }).catch(() => alive && setD(null));
    return () => { alive = false; };
  }, [id]);

  // On open, discover any existing Revolut link + its payment state. MUST stay
  // above the early returns below so hook order is stable across renders.
  useEffect(() => {
    if (d && d.legacyId && d.status !== "COMPLETED") checkRevolut(true);
  }, [d && d.legacyId, d && d.status]);

  if (d === undefined) return <div className="empty"><span className="spin spin--dark" /><div className="t">Loading draft…</div></div>;
  if (d === null) return <div className="empty"><div className="t">Draft order not found</div></div>;

  const completed = d.status === "COMPLETED";
  const setItem = (i, patch) => setItems(list => list.map((it, j) => j === i ? { ...it, ...patch } : it));
  const addCustomItem = () => setItems(list => [...list, { name: "", qty: 1, price: 0, variantId: null, _new: true }]);
  const addVariantItem = (v) => setItems(list => [...list, { name: v.name, qty: 1, price: v.price, variantId: v.variantId, sku: v.sku, variant: v.variant, _new: true }]);
  const removeItem = (i) => setItems(list => list.filter((_, j) => j !== i));
  const setShipF = (k, v) => setShip(s => ({ ...s, [k]: v }));
  const subtotal = items.reduce((s, it) => s + (Number(it.qty) || 0) * (Number(it.price) || 0), 0);
  const shipAmt = Number(shipLine.price) || 0;
  // Resolve the discount to £: a percentage is applied to the goods subtotal.
  const discPct = discountType === "pct" ? Math.min(Number(discount) || 0, 100) : 0;
  const discAmt = Math.min(
    discountType === "pct" ? +(subtotal * discPct / 100).toFixed(2) : (Number(discount) || 0),
    subtotal); // never discount below zero goods
  const total = +(subtotal - discAmt + shipAmt).toFixed(2);

  // Build a client draft object for the invoice/email helpers (order-like shape).
  // total carries the discount so the proforma's Subtotal − Discount + Shipping reconciles.
  const asOrder = () => ({ ...d, items, note, ship,
    customer: customer || (email ? { name: (ship && ship.name) || email, email } : null),
    total, subtotal, shipping: shipAmt, discount: discAmt, pay: "pending",
    customerId: customer ? customer.id : d.customerId });

  async function save() {
    setSaving(true); setMsg("");
    try {
      const patch = { items, email, note,
        // Percentage → send the % + type; fixed → send the £ amount.
        discount: discountType === "pct" ? discPct : discAmt,
        discountValueType: discountType === "pct" ? "PERCENTAGE" : "FIXED_AMOUNT",
        shippingAddress: cleanAddr(ship),
        shippingLine: shipLine.title || shipLine.price ? { title: shipLine.title || "Shipping", price: shipAmt } : null };
      const updated = await DTF.saveDraftOrder(d.legacyId, patch);
      hydrate(updated); setMsg("Saved.");
      // A discount/edit changes the total → any existing unpaid Revolut link is now
      // for the wrong amount. Regenerate it so the checkout total matches (the new
      // amount comes from the freshly-saved Shopify draft total, discount included).
      try {
        const st = await fetch("/api/draft-orders/" + d.legacyId + "/revolut-status").then(r => r.json());
        if (st && st.hasLink && !st.paid && st.stale) { await getRevolut(true); setMsg("Saved — Revolut link updated to the new total."); }
        else setRvStatus(st);
      } catch (e2) { /* non-fatal — link just stays as flagged */ }
    } catch (e) { setMsg("Error: " + e.message); }
    finally { setSaving(false); }
  }

  // Link/unlink an existing-or-new customer, persisting immediately. When linking,
  // pull the customer's default address and fill any empty delivery fields.
  async function linkCustomer(cust) {
    setMsg("");
    try {
      const patch = { customerId: cust ? cust.id : null, email: cust ? (cust.email || email) : email };
      let merged = ship;
      if (cust && cust.id) {
        const addr = await DTF.fetchCustomerAddress(cust.id);
        if (addr) {
          // fill only blanks so a hand-typed address isn't clobbered
          merged = { ...ship };
          ["name", "company", "address1", "address2", "city", "province", "provinceCode", "zip", "country", "countryCodeV2", "phone"]
            .forEach(k => { if (!merged[k] && addr[k]) merged[k] = addr[k]; });
          if (!merged.name && cust.name) merged.name = cust.name;
          setShip(merged);
          patch.shippingAddress = cleanAddr(merged);
        }
      }
      const updated = await DTF.saveDraftOrder(d.legacyId, patch);
      hydrate(updated);
      // hydrate reads dr.customer/ship from Shopify; keep the picked ones if the round-trip lags.
      if (cust) { setCustomer(cust); setShip(merged); }
    } catch (e) { setMsg("Error: " + e.message); }
  }
  async function complete(paymentPending) {
    const label = paymentPending ? "create an UNPAID order (payment due later)" : "mark this as PAID";
    if (!confirm(`Complete draft ${d.name} — ${label}? This creates a real Shopify order.`)) return;
    setCompleting(paymentPending ? "later" : "paid"); setMsg("");
    try {
      const r = await DTF.completeDraftOrder(d.legacyId, paymentPending);
      setMsg("Order created: " + (r.order && r.order.name || "done"));
      const dr = await DTF.fetchDraftOrder(id); hydrate(dr);
      if (r.order && r.order.legacyId) setTimeout(() => go({ view: "order", id: "#" + r.order.legacyId.replace(/\D/g, "") || r.order.name }), 800);
    } catch (e) { setMsg("Error: " + e.message); }
    finally { setCompleting(""); }
  }
  // fresh=true forces a NEW Revolut order for the CURRENT total (used after the
  // draft is edited so the checkout amount matches). Without it the server reuses
  // an existing not-yet-paid link only when the amount still matches.
  const getRevolut = async (fresh) => {
    setRvBusy(true);
    try { const j = await fetch("/api/draft-orders/" + d.legacyId + "/revolut-link" + (fresh ? "?fresh=1" : "")).then(r => r.json()); if (j && j.url) { setRvUrl(j.url); checkRevolut(true); } }
    catch (e) {} finally { setRvBusy(false); }
  };
  const copyRevolut = () => navigator.clipboard.writeText(rvUrl).then(() => { setRvCopied(true); setTimeout(() => setRvCopied(false), 1500); });
  // Live Revolut status for this draft's link — lets us reconcile a payment even
  // if the webhook didn't land. Surfaces a "paid → create order" action.
  const checkRevolut = async (silent) => {
    if (!silent) setRvChecking(true);
    try {
      const j = await fetch("/api/draft-orders/" + d.legacyId + "/revolut-status").then(r => r.json());
      setRvStatus(j);
      if (j && j.url && !rvUrl) setRvUrl(j.url);
    } catch (e) {} finally { if (!silent) setRvChecking(false); }
  };
  const completeFromRevolut = async () => {
    setCompleting("paid"); setMsg("");
    try {
      const j = await fetch("/api/draft-orders/" + d.legacyId + "/revolut-complete", { method: "POST" }).then(r => r.json());
      if (!j.paid) { setMsg(j.hasLink ? ("Revolut payment not completed yet (status: " + (j.state || "pending") + ")") : "No Revolut link on this draft yet."); await checkRevolut(true); return; }
      setMsg("Order created: " + (j.order && j.order.name || "done"));
      const dr = await DTF.fetchDraftOrder(id); hydrate(dr);
      if (j.order && j.order.legacyId) setTimeout(() => go({ view: "order", id: "#" + j.order.legacyId.replace(/\D/g, "") }), 800);
    } catch (e) { setMsg("Error: " + e.message); }
    finally { setCompleting(""); }
  };

  return (
    <div className="detail">
      <div className="detail__main">
        <div className="crumbs"><a onClick={() => go({ view: "drafts" })}>Draft orders</a><span className="sep">/</span><span style={{ color: "var(--txt)" }}>{d.name}</span></div>
        <div className="pagehead">
          <button className="icon-btn" onClick={() => go({ view: "drafts" })} title="Back"><Icon name="back" /></button>
          <h2>{d.name}</h2>
          <DraftBadge status={d.status} />
          <span className="meta">{d.date}</span>
          <div className="actions">
            <button className="btn btn--ghost btn--sm" onClick={() => downloadInvoice(asOrder(), { draft: true })}><Icon name="doc" /> Proforma invoice</button>
            <button className="btn btn--ghost btn--sm" onClick={() => setEmailOpen(true)}><Icon name="mail" /> Email customer</button>
            {d.legacyId && DTF.shop && DTF.shop.adminUrl &&
              <a className="btn btn--ghost btn--sm" href={DTF.shop.adminUrl + "/draft_orders/" + String(d.legacyId).split("/").pop()} target="_blank" rel="noopener noreferrer"><Icon name="external" /> Shopify admin</a>}
          </div>
        </div>

        {completed && d.completedOrder && (
          <div className="order-note"><Icon name="check" /><div><div className="order-note__lbl">Completed</div>
            <div className="order-note__body">This draft became order <a onClick={() => go({ view: "order", id: d.completedOrder.name })}>{d.completedOrder.name}</a>.</div></div></div>
        )}

        {/* Items */}
        <div className="card">
          <div className="card__head"><h3>Items</h3><span className="right kick">{items.reduce((s, i) => s + (Number(i.qty) || 0), 0)} units · {items.length} {items.length === 1 ? "line" : "lines"}</span></div>
          <div className="card__body">
            <table className="dft-items">
              <thead><tr>
                <th>Description</th>
                <th className="num" style={{ width: 72 }}>Qty</th>
                <th className="num" style={{ width: 116 }}>Unit price</th>
                <th className="num" style={{ width: 104 }}>Amount</th>
                <th style={{ width: 28 }}></th>
              </tr></thead>
              <tbody>
                {items.length === 0 && (
                  <tr><td colSpan={5} className="dim" style={{ padding: "16px 10px", textAlign: "center" }}>No items yet — add a product or a custom line below.</td></tr>
                )}
                {items.map((it, i) => {
                  const locked = completed || !!it.variantId; // variant rows use catalogue name/price
                  return (
                  <tr key={i}>
                    <td>
                      <input className="rm-in rm-in--name" value={it.name || ""} disabled={locked} onChange={e => setItem(i, { name: e.target.value })} placeholder="Item description" />
                      {it.variantId
                        ? <span className="dft-tag dft-tag--sku" title="Catalogue product"><Icon name="check" />{it.sku ? "SKU " + it.sku : "Catalogue item"}{it.variant ? " · " + it.variant : ""}</span>
                        : <span className="dft-tag dft-tag--custom">Custom item · no SKU</span>}
                    </td>
                    <td className="num"><input className="rm-in num" type="number" min="1" value={it.qty} disabled={completed} onChange={e => setItem(i, { qty: e.target.value })} /></td>
                    <td className="num"><input className="rm-in num" type="number" step="0.01" value={it.price} disabled={locked} onChange={e => setItem(i, { price: e.target.value })} /></td>
                    <td className="num dft-amt">{DTF.fmtGBP((Number(it.qty) || 0) * (Number(it.price) || 0))}</td>
                    <td className="num">{!completed && <button className="rm-del" title="Remove" onClick={() => removeItem(i)}>×</button>}</td>
                  </tr>
                  );
                })}
              </tbody>
            </table>
            {!completed && <DraftItemPicker onPickVariant={addVariantItem} onAddCustom={addCustomItem} />}
            <div className="totals dft-totals">
              <div className="row"><span className="dim">Subtotal</span><span>{DTF.fmtGBP(subtotal)}</span></div>
              <div className="row" style={{ alignItems: "center" }}>
                <span className="dim">Discount{discountType === "pct" && discAmt ? <span className="kick" style={{ marginLeft: 6 }}>= −{DTF.fmtGBP(discAmt)}</span> : null}</span>
                {completed
                  ? <span>{discAmt ? "−" + DTF.fmtGBP(discAmt) + (d.discountValueType === "PERCENTAGE" && d.discountValue ? " (" + d.discountValue + "%)" : "") : "—"}</span>
                  : <span style={{ display: "inline-flex", alignItems: "center", gap: 4 }}>
                      <span className="seg seg--xs">
                        <button className={discountType === "fixed" ? "is-active" : ""} onClick={() => setDiscountType("fixed")}>£</button>
                        <button className={discountType === "pct" ? "is-active" : ""} onClick={() => setDiscountType("pct")}>%</button>
                      </span>
                      {discountType === "fixed" ? "−£" : "−"}
                      <input className="rm-in dft-disc-in" type="number" step={discountType === "pct" ? "1" : "0.01"} min="0" max={discountType === "pct" ? "100" : undefined} value={discount} onChange={e => setDiscount(e.target.value)} placeholder={discountType === "pct" ? "0" : "0.00"} />
                      {discountType === "pct" ? "%" : null}
                    </span>}
              </div>
              <div className="row"><span className="dim">Shipping</span><span>{DTF.fmtGBP(shipAmt)}</span></div>
              <div className="row grand"><span>Total</span><span>{DTF.fmtGBP(total)}</span></div>
            </div>
          </div>
        </div>

        {/* Artwork — attach print files at the quote stage; they follow the
            draft onto the real order when it's completed. */}
        <DraftArtworkCard draftId={d.legacyId} customerId={customer ? customer.id : d.customerId} customerName={(customer && customer.name) || (d.customer && d.customer.name) || ""} />

        {/* Printing instructions — also follow the draft onto the order on completion. */}
        <PrintingInstructionsCard draftId={d.legacyId} />

        {/* Delivery + note */}
        <div className="card">
          <div className="card__head"><h3>Delivery</h3></div>
          <div className="card__body" style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
              <label className="out-field" style={{ flex: 1, minWidth: 160 }}><span>Recipient name</span><input className="rm-in" value={ship.name || ""} disabled={completed} onChange={e => setShipF("name", e.target.value)} /></label>
              <label className="out-field" style={{ flex: 1, minWidth: 160 }}><span>Company</span><input className="rm-in" value={ship.company || ""} disabled={completed} onChange={e => setShipF("company", e.target.value)} /></label>
            </div>
            <label className="out-field"><span>Address line 1</span><input className="rm-in" value={ship.address1 || ""} disabled={completed} onChange={e => setShipF("address1", e.target.value)} /></label>
            <label className="out-field"><span>Address line 2</span><input className="rm-in" value={ship.address2 || ""} disabled={completed} onChange={e => setShipF("address2", e.target.value)} /></label>
            <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
              <label className="out-field" style={{ flex: 1, minWidth: 120 }}><span>City</span><input className="rm-in" value={ship.city || ""} disabled={completed} onChange={e => setShipF("city", e.target.value)} /></label>
              <label className="out-field" style={{ flex: 1, minWidth: 100 }}><span>Postcode</span><input className="rm-in" value={ship.zip || ""} disabled={completed} onChange={e => setShipF("zip", e.target.value)} /></label>
              <label className="out-field" style={{ flex: 1, minWidth: 90 }}><span>Country code</span><input className="rm-in" value={ship.countryCodeV2 || ""} disabled={completed} onChange={e => setShipF("countryCodeV2", e.target.value)} placeholder="GB" /></label>
            </div>
            <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
              <label className="out-field" style={{ flex: 2, minWidth: 160 }}><span>Shipping / delivery method</span><input className="rm-in" value={shipLine.title} disabled={completed} onChange={e => setShipLine(s => ({ ...s, title: e.target.value }))} placeholder="e.g. Royal Mail 48" /></label>
              <label className="out-field" style={{ flex: 1, minWidth: 100 }}><span>Shipping £</span><input className="rm-in num" type="number" step="0.01" value={shipLine.price} disabled={completed} onChange={e => setShipLine(s => ({ ...s, price: e.target.value }))} /></label>
            </div>
            <label className="out-field"><span>Note</span><textarea className="out-html" style={{ height: 70 }} value={note} disabled={completed} onChange={e => setNote(e.target.value)} /></label>
            {!completed && (
              <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
                <button className="btn btn--primary btn--sm" disabled={saving} onClick={save}>{saving ? "Saving…" : "Save changes"}</button>
                {msg && <span className="kick" style={{ color: "var(--mint-ink)" }}>{msg}</span>}
              </div>
            )}
          </div>
        </div>

        {/* Payment */}
        {!completed && (
          <div className="card">
            <div className="card__head"><Icon name="doc" /><h3>Payment</h3></div>
            <div className="card__body" style={{ display: "flex", flexDirection: "column", gap: 12 }}>
              <div className="dim" style={{ fontSize: 12.5 }}>Save your changes first, then convert this draft into a real order.</div>
              <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
                <button className="btn btn--ghost btn--sm" disabled={!!completing} onClick={() => complete(true)}>{completing === "later" ? "Creating…" : "Payment due later (create unpaid order)"}</button>
                <button className="btn btn--primary btn--sm" disabled={!!completing} onClick={() => complete(false)}>{completing === "paid" ? "Marking…" : "Collect payment — mark as paid"}</button>
              </div>
              {/* Branded pay page (pay.dtf.uk) — customer pays into the Revolut business account */}
              <div style={{ borderTop: "1px solid var(--rule)", paddingTop: 12 }}>
                <div style={{ fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: ".04em", color: "#8a929b", marginBottom: 6 }}>Payment page</div>
                <div className="dim" style={{ fontSize: 12.5, marginBottom: 8 }}>Branded pay.dtf.uk page showing the proforma; customer pays into the Revolut business account.</div>
                {rvUrl ? (
                  <>
                    <div style={{ display: "flex", gap: 6, alignItems: "center", marginBottom: 10 }}>
                      <input className="rm-in mono" style={{ flex: 1, minWidth: 0 }} readOnly value={rvUrl} onFocus={e => e.target.select()} />
                      <a className="btn btn--ghost btn--sm" href={rvUrl} target="_blank" rel="noopener noreferrer" title="Open payment page"><Icon name="external" /></a>
                    </div>
                    <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
                      <button className="btn btn--ghost btn--sm" onClick={copyRevolut}>{rvCopied ? "Copied ✓" : "Copy pay link"}</button>
                      <button className="btn btn--ghost btn--sm" disabled={rvChecking} onClick={() => checkRevolut(false)}><Icon name="refresh" /> {rvChecking ? "Checking…" : "Check payment"}</button>
                    </div>
                    {rvStatus && rvStatus.hasLink && (
                      rvStatus.paid
                        ? <div style={{ marginTop: 10, display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
                            <span className="badge badge--green">Paid on Revolut ✓</span>
                            <button className="btn btn--primary btn--sm" disabled={!!completing} onClick={completeFromRevolut}>{completing === "paid" ? "Creating…" : "Create paid order"}</button>
                          </div>
                        : rvStatus.stale
                          ? <div className="warn-banner" style={{ marginTop: 10, background: "var(--c-amber-w)", borderColor: "transparent", color: "#9a6300", display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
                              <Icon name="spark" /> This link charges {DTF.fmtGBP((rvStatus.amount || 0) / 100)}, but the order total is now {DTF.fmtGBP((rvStatus.currentAmount || 0) / 100)}.
                              <button className="btn btn--primary btn--sm" disabled={rvBusy} onClick={() => getRevolut(true)}>{rvBusy ? "Regenerating…" : "Regenerate for " + DTF.fmtGBP((rvStatus.currentAmount || 0) / 100)}</button>
                            </div>
                          : <span className="kick" style={{ marginLeft: 2, marginTop: 8, display: "inline-block" }}>Awaiting payment ({rvStatus.state || "pending"}){rvStatus.amount != null ? " · " + DTF.fmtGBP(rvStatus.amount / 100) : ""}. Paying it completes this draft into a paid order automatically.</span>
                    )}
                    {!rvStatus && <span className="kick" style={{ marginLeft: 10 }}>Paying it completes this draft into a paid order automatically.</span>}
                  </>
                ) : (
                  <button className="btn btn--ghost btn--sm" disabled={rvBusy} onClick={() => getRevolut()}><Icon name="external" /> {rvBusy ? "Generating…" : "Get payment link"}</button>
                )}
              </div>
            </div>
          </div>
        )}

      </div>

      {/* side — customer reference (select existing / create new) */}
      <div className="detail__side">
        <DraftCustomerCard customer={customer} email={email} setEmail={setEmail} completed={completed} onLink={linkCustomer} go={go} />
      </div>

      <div ref={emailRef}>{emailOpen && <OrderEmailComposer o={asOrder()} c={customer} draft={true} onClose={() => setEmailOpen(false)} />}</div>
    </div>
  );
}
window.DraftOrderDetail = DraftOrderDetail;

/* Add-item picker: search the catalogue for a real SKU-backed variant, or add a
   free-text custom row. Both land in the draft's line-item list. */
function DraftItemPicker({ onPickVariant, onAddCustom }) {
  const [open, setOpen] = useState(false);
  const [q, setQ] = useState("");
  const [res, setRes] = useState(null);   // null=idle, []=no matches, [..]=hits
  const [busy, setBusy] = useState(false);
  useEffect(() => {
    const term = q.trim();
    if (!term) { setRes(null); return; }
    let alive = true; setBusy(true);
    const t = setTimeout(() => {
      DTF.searchProductVariants(term).then(v => { if (alive) { setRes(v); setBusy(false); } }).catch(() => alive && (setRes([]), setBusy(false)));
    }, 250);
    return () => { alive = false; clearTimeout(t); };
  }, [q]);

  return (
    <div style={{ marginTop: 10 }}>
      {!open ? (
        <div className="dft-addrow">
          <button className="btn btn--ghost btn--sm" onClick={() => setOpen(true)}><Icon name="search" /> Add product (SKU)</button>
          <button className="btn btn--ghost btn--sm" onClick={onAddCustom}><Icon name="plus" /> Add custom item</button>
        </div>
      ) : (
        <div className="pick-panel">
          <div style={{ display: "flex", gap: 6, alignItems: "center", marginBottom: 8 }}>
            <input className="rm-in" style={{ flex: 1 }} autoFocus placeholder="Search product title or SKU…" value={q} onChange={e => setQ(e.target.value)} />
            <button className="btn btn--ghost btn--sm" onClick={() => { setOpen(false); setQ(""); setRes(null); }}>Close</button>
          </div>
          {busy && <div className="kick dim">Searching…</div>}
          {res && res.length === 0 && !busy && <div className="kick dim">No matching products. Use “Add custom item” for a one-off.</div>}
          {res && res.length > 0 && (
            <div style={{ maxHeight: 260, overflow: "auto", display: "flex", flexDirection: "column", gap: 2 }}>
              {res.map(v => (
                <button key={v.variantId} className="pick-row" style={{ display: "flex", gap: 10, alignItems: "center", textAlign: "left", padding: "6px 8px", border: "none", background: "none", cursor: "pointer", borderRadius: 6 }}
                  onClick={() => { onPickVariant(v); setOpen(false); setQ(""); setRes(null); }}>
                  {v.image ? <img src={v.image} alt="" style={{ width: 34, height: 34, objectFit: "cover", borderRadius: 4, flexShrink: 0 }} /> : <span style={{ width: 34, height: 34, borderRadius: 4, background: "var(--rule)", flexShrink: 0 }} />}
                  <span style={{ minWidth: 0, flex: 1 }}>
                    <div style={{ fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{v.name}{v.status && v.status !== "ACTIVE" ? <span className="kick" style={{ marginLeft: 6, color: "var(--c-amber, #b7791f)" }}>{v.status}</span> : null}</div>
                    <div className="kick mono dim">{v.sku ? "SKU " + v.sku : "no SKU"}</div>
                  </span>
                  <span className="num" style={{ fontWeight: 600 }}>{DTF.fmtGBP(v.price)}</span>
                </button>
              ))}
            </div>
          )}
          <div style={{ borderTop: "1px solid var(--rule)", marginTop: 8, paddingTop: 8 }}>
            <button className="btn btn--ghost btn--sm" onClick={() => { onAddCustom(); setOpen(false); setQ(""); setRes(null); }}><Icon name="plus" /> Add custom item instead</button>
          </div>
        </div>
      )}
    </div>
  );
}

/* Customer reference card for the draft's right sidebar. Shows the linked customer,
   or a picker to search an existing one / create a new one. */
function DraftCustomerCard({ customer, email, setEmail, completed, onLink, go }) {
  const [mode, setMode] = useState("");        // "" | "search" | "create"
  const [q, setQ] = useState("");
  const [res, setRes] = useState(null);
  const [busy, setBusy] = useState(false);
  const [form, setForm] = useState({ firstName: "", lastName: "", email: "", phone: "" });
  const [err, setErr] = useState("");

  useEffect(() => {
    if (mode !== "search") return;
    const term = q.trim();
    if (!term) { setRes(null); return; }
    let alive = true; setBusy(true);
    const t = setTimeout(() => {
      DTF.searchShopifyCustomers(term).then(r => { if (alive) { setRes(r); setBusy(false); } }).catch(() => alive && (setRes([]), setBusy(false)));
    }, 250);
    return () => { alive = false; clearTimeout(t); };
  }, [q, mode]);

  const pick = (c) => { setMode(""); setQ(""); setRes(null); onLink({ id: c.id, name: c.name, email: c.email, phone: c.phone }); };
  const create = async () => {
    setErr("");
    if (!form.email && !form.firstName && !form.lastName) { setErr("Enter a name or email"); return; }
    setBusy(true);
    try {
      const c = await DTF.createShopifyCustomer(form);
      if (c && c.id) { setMode(""); setForm({ firstName: "", lastName: "", email: "", phone: "" }); onLink(c); }
      else setErr("Could not create customer");
    } catch (e) { setErr(e.message || "Could not create customer"); }
    finally { setBusy(false); }
  };

  return (
    <div className="card">
      <div className="card__head">
        <h3>Customer</h3>
        {customer && customer.id && <a className="right kick" style={{ cursor: "pointer" }} onClick={() => go({ view: "customer", id: customer.id })}>Open profile →</a>}
      </div>
      <div className="card__body">
        {customer && customer.id ? (
          <>
            <div style={{ display: "flex", gap: 12, alignItems: "center", marginBottom: 12, cursor: "pointer" }} onClick={() => go({ view: "customer", id: customer.id })}>
              <Avatar name={customer.name} lg />
              <div style={{ minWidth: 0 }}>
                <div style={{ fontWeight: 700, fontSize: 15, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", color: "var(--c-blue)" }}>{customer.name}</div>
                <div className="mono dim" style={{ fontSize: 11 }}>{customer.email || "—"}</div>
              </div>
            </div>
            {!completed && <button className="btn btn--ghost btn--sm" onClick={() => onLink(null)}>Remove customer</button>}
          </>
        ) : completed ? (
          <div className="dim" style={{ fontSize: 12.5 }}>No customer linked.</div>
        ) : mode === "" ? (
          <>
            <div className="dim" style={{ fontSize: 12.5, marginBottom: 10 }}>No customer linked yet.</div>
            <div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 12 }}>
              <button className="btn btn--ghost btn--sm" onClick={() => setMode("search")}><Icon name="search" /> Select existing</button>
              <button className="btn btn--primary btn--sm" onClick={() => setMode("create")}><Icon name="plus" /> Create new</button>
            </div>
            <label className="out-field"><span>Or guest email (no account)</span>
              <input className="rm-in" value={email} onChange={e => setEmail(e.target.value)} placeholder="customer@email.com" />
            </label>
          </>
        ) : mode === "search" ? (
          <>
            <div style={{ display: "flex", gap: 6, alignItems: "center", marginBottom: 8 }}>
              <input className="rm-in" style={{ flex: 1 }} autoFocus placeholder="Name, email or phone…" value={q} onChange={e => setQ(e.target.value)} />
              <button className="btn btn--ghost btn--sm" onClick={() => { setMode(""); setQ(""); setRes(null); }}>Cancel</button>
            </div>
            {busy && <div className="kick dim">Searching…</div>}
            {res && res.length === 0 && !busy && <div className="kick dim">No matches. Try “Create new”.</div>}
            {res && res.length > 0 && (
              <div style={{ maxHeight: 260, overflow: "auto", display: "flex", flexDirection: "column", gap: 2 }}>
                {res.map(c => (
                  <button key={c.id} className="pick-row" style={{ display: "flex", gap: 10, alignItems: "center", textAlign: "left", padding: "6px 8px", border: "none", background: "none", cursor: "pointer", borderRadius: 6 }} onClick={() => pick(c)}>
                    <Avatar name={c.name} />
                    <span style={{ minWidth: 0, flex: 1 }}>
                      <div style={{ fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{c.name}</div>
                      <div className="kick mono dim">{c.email || "—"}</div>
                    </span>
                  </button>
                ))}
              </div>
            )}
          </>
        ) : (
          <>
            <div style={{ display: "flex", gap: 8, marginBottom: 8 }}>
              <label className="out-field" style={{ flex: 1 }}><span>First name</span><input className="rm-in" value={form.firstName} onChange={e => setForm(f => ({ ...f, firstName: e.target.value }))} /></label>
              <label className="out-field" style={{ flex: 1 }}><span>Last name</span><input className="rm-in" value={form.lastName} onChange={e => setForm(f => ({ ...f, lastName: e.target.value }))} /></label>
            </div>
            <label className="out-field" style={{ marginBottom: 8 }}><span>Email</span><input className="rm-in" value={form.email} onChange={e => setForm(f => ({ ...f, email: e.target.value }))} /></label>
            <label className="out-field" style={{ marginBottom: 10 }}><span>Phone</span><input className="rm-in" value={form.phone} onChange={e => setForm(f => ({ ...f, phone: e.target.value }))} /></label>
            {err && <div className="kick" style={{ color: "var(--c-red)", marginBottom: 8 }}>{err}</div>}
            <div style={{ display: "flex", gap: 8 }}>
              <button className="btn btn--primary btn--sm" disabled={busy} onClick={create}>{busy ? "Creating…" : "Create & link"}</button>
              <button className="btn btn--ghost btn--sm" onClick={() => { setMode(""); setErr(""); }}>Cancel</button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// Strip empty fields + Shopify read-only keys before sending an address back.
function cleanAddr(a) {
  if (!a) return null;
  const out = {};
  ["name", "company", "address1", "address2", "city", "province", "provinceCode", "zip", "country", "countryCodeV2", "phone"].forEach(k => { if (a[k]) out[k] = a[k]; });
  return Object.keys(out).length ? out : null;
}
