/* ===========================================================
   DTF.uk · Ops — shared components & icons
   =========================================================== */
const { useState, useEffect, useRef, useMemo } = React;

/* ---------------- Icons ---------------- */
const ICONS = {
  orders:   <path d="M5 7h14l-1.2 11a2 2 0 0 1-2 1.8H8.2a2 2 0 0 1-2-1.8L5 7Z M9 7V5a3 3 0 0 1 6 0v2"/>,
  customers:<path d="M16 19c0-2.8-2-4.5-4-4.5S8 16.2 8 19 M12 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6 M19.5 19c0-2-1.2-3.3-2.7-3.8 M16.8 6.3A2.6 2.6 0 0 1 18 11"/>,
  inbox:    <path d="M4 13h4l1.5 2.5h5L16 13h4 M4 13 6 6h12l2 7v5a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 4 18v-5Z"/>,
  search:   <path d="M11 18a7 7 0 1 0 0-14 7 7 0 0 0 0 14Z M20 20l-3.5-3.5"/>,
  bell:     <path d="M18 9a6 6 0 1 0-12 0c0 6-2 7-2 7h16s-2-1-2-7 M10.5 20a1.8 1.8 0 0 0 3 0"/>,
  filter:   <path d="M4 6h16 M7 12h10 M10 18h4"/>,
  plus:     <path d="M12 5v14 M5 12h14"/>,
  print:    <path d="M7 8V4h10v4 M6 18H5a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2h-1 M7 14h10v6H7z"/>,
  truck:    <path d="M3 7h11v9H3z M14 10h4l3 3v3h-7 M7 19a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z M18 19a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/>,
  box:      <path d="M3.5 8 12 4l8.5 4-8.5 4-8.5-4Z M3.5 8v8L12 20l8.5-4V8 M12 12v8"/>,
  back:     <path d="M15 5l-7 7 7 7"/>,
  chevron:  <path d="M9 6l6 6-6 6"/>,
  spark:    <path d="M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8L12 3Z"/>,
  scissors: <path d="M7 8.5A2.5 2.5 0 1 0 7 3.5a2.5 2.5 0 0 0 0 5Z M7 20.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z M9 7l11 10 M9 17 20 7 M9.2 9.2l3.3 2.8"/>,
  crop: <path d="M6 2v14a2 2 0 0 0 2 2h14 M2 6h14a2 2 0 0 1 2 2v14"/>,
  paperclip:<path d="M20 11l-8.5 8.5a4 4 0 0 1-5.7-5.7L14 5.4a2.6 2.6 0 0 1 3.7 3.7l-8.3 8.3a1.2 1.2 0 0 1-1.7-1.7l7.7-7.7"/>,
  send:     <path d="M4 12l16-7-7 16-2.2-6.8L4 12Z"/>,
  tag:      <path d="M4 13 11 6h6l1 1v6l-7 7-7-7Z M14.5 9.5h.01"/>,
  link:     <path d="M10 14a3.5 3.5 0 0 0 5 0l3-3a3.5 3.5 0 0 0-5-5l-1.5 1.5 M14 10a3.5 3.5 0 0 0-5 0l-3 3a3.5 3.5 0 0 0 5 5l1.5-1.5"/>,
  external: <path d="M14 5h5v5 M19 5l-8 8 M18 14v4a1.5 1.5 0 0 1-1.5 1.5h-10A1.5 1.5 0 0 1 5 18V8a1.5 1.5 0 0 1 1.5-1.5H11"/>,
  doc:      <path d="M7 3h7l4 4v14H7z M14 3v4h4 M9.5 12h6 M9.5 15.5h6"/>,
  image:    <path d="M4 5h16v14H4z M8 11a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3 M5 17l4.5-4.5 3 3L16 12l3 3.5"/>,
  download: <path d="M12 4v10 M8 11l4 4 4-4 M5 19h14"/>,
  shirt:    <path d="M8 4 5 7l2 2 1-1v11h8V8l1 1 2-2-3-3-2 1a2.5 2.5 0 0 1-4 0L8 4Z"/>,
  mail:     <path d="M4 6h16v12H4z M4 7l8 6 8-6"/>,
  clock:    <path d="M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z M12 7v5l3 2"/>,
  pin:      <path d="M12 21s7-6.2 7-11a7 7 0 1 0-14 0c0 4.8 7 11 7 11Z M12 12.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z"/>,
  card:     <path d="M3 7h18v10H3z M3 10h18 M7 14h4"/>,
  check:    <path d="M5 12l4.5 4.5L19 7"/>,
  x:        <path d="M6 6l12 12 M18 6 6 18"/>,
  dots:     <path d="M5 12h.01 M12 12h.01 M19 12h.01"/>,
  copy:     <path d="M8 8h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1Z M5 15a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1"/>,
  refresh:  <path d="M20 11a8 8 0 1 0-2.3 5.7 M20 5v6h-6"/>,
  cog:      <path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z M19 12a7 7 0 0 0-.1-1l2-1.5-2-3.4-2.3 1a7 7 0 0 0-1.7-1l-.3-2.6h-4l-.3 2.6a7 7 0 0 0-1.7 1l-2.3-1-2 3.4L5 11a7 7 0 0 0 0 2l-2 1.5 2 3.4 2.3-1a7 7 0 0 0 1.7 1l.3 2.6h4l.3-2.6a7 7 0 0 0 1.7-1l2.3 1 2-3.4L19 13a7 7 0 0 0 0-1Z"/>,
  reply:    <path d="M9 7 4 12l5 5 M4 12h10a6 6 0 0 1 6 6v1"/>,
  google:   <path d="M21 12.2c0-.7-.1-1.4-.2-2H12v3.8h5.1a4.4 4.4 0 0 1-1.9 2.9v2.4h3.1c1.8-1.7 2.7-4.1 2.7-7.1Z M12 21c2.4 0 4.5-.8 6-2.2l-3-2.4c-.8.6-1.9.9-3 .9-2.3 0-4.3-1.6-5-3.7H3.9v2.4A9 9 0 0 0 12 21Z M7 13.6a5.4 5.4 0 0 1 0-3.4V7.8H3.9a9 9 0 0 0 0 8.1L7 13.6Z M12 6.6c1.3 0 2.5.5 3.4 1.3l2.6-2.6A9 9 0 0 0 3.9 7.8L7 10.2C7.7 8.1 9.7 6.6 12 6.6Z"/>,
  chart:    <path d="M4 20V4 M4 20h16 M8 20v-6 M12 20v-9 M16 20v-4 M20 20V8"/>,
  star:     <path d="M12 2l2.9 6.3 6.9.9-5 4.8 1.2 6.8L12 17.8l-6 3 1.2-6.8L2 9.2l6.9-.9L12 2Z"/>,
  upload:   <path d="M12 14V4 M8 8l4-4 4 4 M5 18h14"/>,
  trash:    <path d="M5 7h14 M8 7V5a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2 M10 12v5 M14 12v5 M6 7l1 12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1l1-12"/>,
};
function Icon({ name, fill }) {
  const node = ICONS[name];
  return (
    <svg viewBox="0 0 24 24" fill={fill ? "currentColor" : "none"} stroke="currentColor"
         strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      {node}
    </svg>
  );
}

/* ---------------- Badges ---------------- */
function StatusBadge({ status }) {
  const m = DTF.statusMeta[status] || DTF.statusMeta["new"];
  return <span className={"badge " + m.cls}><span className="dot" style={{ background: m.dot }} />{m.label}</span>;
}
function MetaBadge({ map, value }) {
  const m = map[value]; if (!m) return null;
  return <span className={"badge " + m.cls}>{m.label}</span>;
}
// Completed-print details for an order (from the printer's telemetry log):
// mode · metres · duration · area rate · linear rate · printer. `rec` is a
// DTF.printResultFor(id) record; renders nothing if the order never printed.
function PrintResult({ rec, compact }) {
  if (!rec) return null;
  const bits = [];
  if (rec.mode) bits.push(rec.mode);
  if (rec.metres != null) bits.push(Number(rec.metres).toFixed(2) + " m");
  if (rec.durationSec != null) bits.push(DTF.fmtMMSS(rec.durationSec));
  if (rec.areaRateM2h != null) bits.push(rec.areaRateM2h + " m²/h");
  if (rec.speedMh != null) bits.push(rec.speedMh + " m/h");
  return (
    <div className={"print-result" + (compact ? " print-result--sm" : "")}
         title={"Printed" + (rec.printer ? " on " + rec.printer : "") + (rec.at ? " · " + new Date(rec.at).toLocaleString("en-GB") : "")}>
      <span className="print-result__tick">✅</span>
      <span className="print-result__bits">{bits.join(" · ")}</span>
      {rec.printer && <span className="print-result__printer">{rec.printer}</span>}
    </div>
  );
}
function ChannelBadge({ channel }) { return <MetaBadge map={DTF.channelMeta} value={channel} />; }
function Flags({ flags }) {
  if (!flags || !flags.length) return null;
  return (
    <span className="cellflags">
      {flags.map(f => {
        const m = DTF.flagMeta[f]; if (!m) return null;
        return <span key={f} className={"flag " + m.cls} title={m.title}>{m.glyph}</span>;
      })}
    </span>
  );
}
function Tags({ tags }) {
  if (!tags || !tags.length) return null;
  return <span className="tags-cell">{tags.map(t => <span key={t} className="tag">{t}</span>)}</span>;
}
function MethodCell({ method }) {
  const m = DTF.methodMeta[method] || { urg: "", short: method || "—" };
  return (
    <span style={{ display:"inline-flex", alignItems:"center", gap:8 }}>
      <span className={"urg-rail urg--" + m.urg} />
      <span style={{ fontFamily:"var(--font-mono)", fontSize:12 }}>{m.short}</span>
    </span>
  );
}
function Avatar({ name, lg }) {
  return <span className={lg ? "avatar-lg" : "av"}>{DTF.initials(name)}</span>;
}
// Browsers block top-level navigation to data: URLs (PDFs show nothing), so
// convert to a Blob URL for both view and download.
function _dataUrlToBlob(dataUrl) {
  const c = dataUrl.indexOf(",");
  const mime = (dataUrl.slice(0, c).match(/data:([^;]+)/) || [])[1] || "application/octet-stream";
  const bin = atob(dataUrl.slice(c + 1));
  const arr = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
  return new Blob([arr], { type: mime });
}
function openAttachment(att) {
  if (!att.dataUrl) return;
  try { const url = URL.createObjectURL(_dataUrlToBlob(att.dataUrl)); window.open(url, "_blank"); setTimeout(() => URL.revokeObjectURL(url), 60000); } catch (e) {}
}
function downloadAttachment(att) {
  if (!att.dataUrl) return;
  try {
    const url = URL.createObjectURL(_dataUrlToBlob(att.dataUrl));
    const a = document.createElement("a"); a.href = url; a.download = att.filename || "file";
    document.body.appendChild(a); a.click(); a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 60000);
  } catch (e) {}
}
function AttachmentTile({ att, meta }) {
  const isImg = (att.contentType || "").startsWith("image/");
  const kb = att.size ? Math.round(att.size / 1024) : null;
  // For files too large to embed, fetch on demand from the server.
  const apiUrl = att.uid != null
    ? `/api/attachment?uid=${encodeURIComponent(att.uid)}&box=${encodeURIComponent(att.box || "INBOX")}&name=${encodeURIComponent(att.filename || "")}`
    : null;
  const gmail = att.mid ? `https://mail.google.com/mail/u/0/#search/rfc822msgid:${encodeURIComponent(att.mid)}` : null;
  const thumb = att.dataUrl || (isImg && apiUrl ? apiUrl : null);
  return (
    <div className="att-tile" title={att.filename}>
      {thumb && isImg
        ? (att.dataUrl
            ? <button type="button" className="att-tile__thumb" onClick={() => openAttachment(att)}><img src={thumb} alt={att.filename} /></button>
            : <a className="att-tile__thumb" href={apiUrl} target="_blank" rel="noopener noreferrer"><img src={thumb} alt={att.filename} /></a>)
        : <div className="att-tile__thumb att-tile__thumb--file"><Icon name="doc" /></div>}
      <div className="att-tile__body">
        <div className="att-tile__name">{att.filename}</div>
        <div className="att-tile__meta">{meta || (kb ? kb + " KB" : "")}</div>
        <div className="att-tile__links">
          {att.dataUrl
            ? <><button type="button" onClick={() => openAttachment(att)}>View</button><button type="button" onClick={() => downloadAttachment(att)}>Download</button>{gmail && <a href={gmail} target="_blank" rel="noopener noreferrer">Gmail</a>}</>
            : apiUrl
              ? <><a href={apiUrl} target="_blank" rel="noopener noreferrer">View</a><a href={apiUrl + "&download=1"}>Download</a>{gmail && <a href={gmail} target="_blank" rel="noopener noreferrer">Gmail</a>}</>
              : gmail
                ? <a href={gmail} target="_blank" rel="noopener noreferrer">Open in Gmail</a>
                : <span className="dim">{kb ? kb + " KB" : ""}</span>}
        </div>
      </div>
    </div>
  );
}
function CopyBtn({ text, title }) {
  const [done, setDone] = useState(false);
  if (!text) return null;
  return (
    <button type="button" title={title || "Copy to clipboard"} className="copybtn" onClick={(e) => {
      e.stopPropagation();
      try { navigator.clipboard.writeText(text); setDone(true); setTimeout(() => setDone(false), 1100); } catch (err) {}
    }}><Icon name={done ? "check" : "copy"} /></button>
  );
}

/* ---------------- Sidebar ---------------- */
function Sidebar({ route, go, counts, userEmail, shops, open, onClose }) {
  const email = userEmail || "";
  const local = (email.split("@")[0] || "user");
  const domain = (email.split("@")[1] || "");
  const name = local.replace(/[._-]+/g, " ").replace(/\b\w/g, c => c.toUpperCase()) || "Signed in";
  const initials = ((local[0] || "U") + (domain[0] || local[1] || "")).toUpperCase();
  const items = [
    { key:"orders",      label:"Orders",      icon:"orders",    cnt:counts.orders },
    { key:"drafts",      label:"Draft orders", icon:"doc",      cnt:counts.drafts, sub:true },
    { key:"gangqueue",   label:"Gang sheets", icon:"image",     sub:true, gang:true, view:"gangqueue", hash:"#/gang-sheets" },
    { key:"designs",     label:"Designs",     icon:"doc",       sub:true, view:"designs", hash:"#/designs" },
    { key:"transfers",   label:"Transfers",   icon:"upload",    sub:true, view:"transfers", hash:"#/transfers" },
    { key:"customers",   label:"Customers",   icon:"customers", cnt:counts.customers },
    { key:"pricing",     label:"Price groups", icon:"tag",      sub:true },
    { key:"inbox",       label:"Inbox",       icon:"inbox",     cnt:counts.inbox },
    { key:"browsing",    label:"Browsing",    icon:"spark" },
    { key:"attachments", label:"Attachments", icon:"paperclip", cnt:counts.attachments },
  ];
  const active = route.view;
  // Plain left-click navigates in-app; cmd/ctrl/shift/middle-click falls through
  // to the real href so the browser can open it in a new tab.
  const nav = (view) => (e) => {
    if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
    e.preventDefault();
    go({ view });
    onClose && onClose(); // close the off-canvas drawer on mobile
  };
  const navR = (r) => (e) => {
    if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
    e.preventDefault();
    go(r);
    onClose && onClose();
  };
  return (
    <>
    {open && <div className="sidebar__backdrop" onClick={onClose} />}
    <aside className={"sidebar" + (open ? " is-open" : "")}>
      <a className="sb__brand" href="#/dispatch" onClick={navR({ view:"orders", board:"board" })}>
        <div className="sb__mark">DTF</div>
        <div className="sb__name">DTF.uk<small>OPS · BACKOFFICE</small></div>
      </a>
      <nav className="sb__nav">
        <div className="sb__sec">Operations</div>
        {items.map(it => {
          // "Orders" opens the New-orders board (the triage queue you work from);
          // its badge is the count of new orders still to triage.
          const isOrders = it.key === "orders";
          const href = isOrders ? "#/neworders" : (it.hash || "#/" + it.key);
          const onClk = isOrders ? navR({ view: "orders", board: "new" }) : nav(it.view || it.key);
          const isActive = isOrders ? (route.view === "orders" && route.board === "new") : (active === (it.view || it.key));
          const cnt = isOrders ? counts.newOrders : it.cnt;
          const showCnt = isOrders ? (cnt != null && cnt > 0) : (cnt != null);
          return (
            <a key={it.key} className={"sb__item" + (it.sub ? " sb__item--sub" : "") + (isActive ? " is-active" : "")} href={href} onClick={onClk} style={it.sub ? { paddingLeft: 34 } : undefined}>
              <Icon name={it.icon} />
              <span>{it.label}</span>
              {it.gang ? (<>
                {counts.gangActive > 0 && <span className="spin spin--dark" style={{marginLeft:"auto",flex:"none"}} title={counts.gangActive + " generating"} />}
                {counts.gang != null && counts.gang > 0 && <span className="cnt" style={counts.gangFailed > 0 ? {background:"#b91c1c",color:"#fff"} : null} title={counts.gangFailed > 0 ? counts.gangFailed + " failed" : counts.gang + " finished"}>{counts.gang}</span>}
              </>) : (showCnt && <span className="cnt">{cnt}</span>)}
            </a>
          );
        })}
        <div className="sb__sec">Today</div>
        <a className={"sb__item" + (route.view==="orders" && route.board==="board" ? " is-active" : "")} href="#/dispatch" onClick={navR({ view:"orders", board:"board" })}>
          <Icon name="truck" /><span>Dispatch board</span>
          {counts.orders != null && counts.orders > 0 && <span className="cnt">{counts.orders}</span>}
        </a>
        <a className={"sb__item sb__item--sub" + (active==="shipments" ? " is-active" : "")} href="#/shipments" onClick={nav("shipments")} style={{ paddingLeft: 34 }}>
          <Icon name="box" /><span>Shipments</span>
        </a>
        <a className={"sb__item" + (active==="production" && !route.sub ? " is-active" : "")} href="#/production" onClick={navR({ view:"production" })}>
          <Icon name="print" /><span>Production</span>
          {counts.production != null && counts.production > 0 && <span className="cnt">{counts.production}</span>}
        </a>
        {(() => {
          const st = s => (DTF.orders || []).filter(o => s.includes(o.status)).length;
          const subs = [
            { key: "design",   label: "Design",   icon: "spark",    n: st(["awaiting_artwork","design_queue"]) },
            { key: "rip",      label: "RIP",      icon: "download", n: st(["rip_queue"]) },
            { key: "printing", label: "Printing", icon: "print",    n: st(["print_queue","printing","printed"]) },
            { key: "pressing", label: "Pressing", icon: "shirt",    n: st(["press_queue"]) },
          ];
          return subs.map(su => (
            <a key={su.key} className={"sb__item sb__item--sub" + (active==="production" && route.sub===su.key ? " is-active" : "")}
               href={"#/production/" + su.key} onClick={navR({ view:"production", sub:su.key })} style={{ paddingLeft: 34 }}>
              <Icon name={su.icon} /><span>{su.label}</span>
              {su.n > 0 && <span className="cnt">{su.n}</span>}
            </a>
          ));
        })()}
        <a className={"sb__item" + (active==="stock" && !route.sub ? " is-active" : "")} href="#/stock" onClick={navR({ view:"stock" })}>
          <Icon name="tag" /><span>Stock</span>
          {counts.stockLow != null && counts.stockLow > 0 && <span className="cnt">{counts.stockLow}</span>}
        </a>
        <a className={"sb__item sb__item--sub" + (active==="stock" && route.sub==="forecast" ? " is-active" : "")} href="#/stock/forecast" onClick={navR({ view:"stock", sub:"forecast" })} style={{ paddingLeft: 34 }}>
          <Icon name="chart" /><span>Re-Stock forecast</span>
        </a>
        <a className={"sb__item" + (active==="outreach" ? " is-active" : "")} href="#/outreach" onClick={nav("outreach")}>
          <Icon name="send" /><span>Outreach</span>
        </a>
        {(shops && shops.length > 0) && <>
          <div className="sb__sec">Partner shops</div>
          {shops.map(s => (
            <a key={s.slug} className={"sb__item" + (route.view==="shop" && route.slug===s.slug ? " is-active" : "")}
               href={"#/shop/" + s.slug} onClick={navR({ view:"shop", slug:s.slug })}>
              <Icon name="box" /><span>{s.name}</span>
            </a>
          ))}
        </>}
        <div className="sb__sec">Insights</div>
        <a className={"sb__item" + (active==="conversions" ? " is-active" : "")} href="#/conversions" onClick={nav("conversions")}>
          <Icon name="chart" /><span>Conversions</span>
        </a>
        <a className={"sb__item" + (active==="analytics" ? " is-active" : "")} href="#/analytics" onClick={nav("analytics")}>
          <Icon name="chart" /><span>Analytics</span>
        </a>
        <a className={"sb__item" + (active==="email" ? " is-active" : "")} href="#/email" onClick={nav("email")}>
          <Icon name="mail" /><span>Email</span>
        </a>
        <a className={"sb__item" + (active==="marketing" ? " is-active" : "")} href="#/marketing" onClick={nav("marketing")}>
          <Icon name="send" /><span>Marketing</span>
        </a>
        <a className={"sb__item sb__item--sub" + (active==="reviews" ? " is-active" : "")} href="#/reviews" onClick={nav("reviews")} style={{ paddingLeft: 34 }}>
          <Icon name="star" /><span>Google Reviews</span>
        </a>
      </nav>
      <div className="sb__foot">
        <div className="sb__user">
          <span className="av">{initials}</span>
          <div style={{minWidth:0}}>
            <div className="nm" style={{whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"}}>{name}</div>
            <div className="rl" title={email} style={{whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"}}>{email || "Studio · Admin"}</div>
          </div>
          <button className="icon-btn" title="Sign out" style={{marginLeft:"auto",flex:"none"}}
                  onClick={()=>{ fetch("/api/logout",{method:"POST"}).finally(()=>location.reload()); }}>
            <Icon name="x" />
          </button>
        </div>
      </div>
    </aside>
    </>
  );
}

/* -------- Global search (⌘K) — orders + customers, with live status -------- */
function GlobalSearch({ go }) {
  const [q, setQ] = useState("");
  const [open, setOpen] = useState(false);
  const [hi, setHi] = useState(0);
  const inputRef = useRef(null);
  const boxRef = useRef(null);
  // ⌘K / Ctrl-K focuses the search from anywhere.
  useEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K")) { e.preventDefault(); inputRef.current && inputRef.current.focus(); }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);
  // Close when clicking outside.
  useEffect(() => {
    const onDoc = (e) => { if (boxRef.current && !boxRef.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, []);

  const qq = q.trim().toLowerCase().replace(/^#/, "");
  const results = useMemo(() => {
    if (!qq) return [];
    const out = [];
    const orders = (DTF.orders || []).filter(o => {
      const cust = DTF.getCustomer ? DTF.getCustomer(o.customerId) : null;
      return String(o.id).replace(/^#/, "").toLowerCase().includes(qq) || (cust && (cust.name || "").toLowerCase().includes(qq));
    });
    orders.slice(0, 8).forEach(o => out.push({ kind: "order", o }));
    if (out.length < 8) {
      const custs = (DTF.customers || []).filter(c => (c.name || "").toLowerCase().includes(qq) || (c.email || "").toLowerCase().includes(qq));
      custs.slice(0, 8 - out.length).forEach(c => out.push({ kind: "customer", c }));
    }
    // Fallback: a bare order number not in the loaded set — offer to open it directly
    // (the order page fetches it on demand).
    if (/^\d+$/.test(qq) && !out.some(r => r.kind === "order" && String(r.o.id).replace(/^#/, "") === qq)) {
      out.push({ kind: "openOrder", id: "#" + qq });
    }
    return out;
  }, [qq]);

  const pick = (r) => {
    setOpen(false); setQ("");
    if (r.kind === "order") go({ view: "order", id: r.o.id });
    else if (r.kind === "openOrder") go({ view: "order", id: r.id });
    else if (r.kind === "customer") go({ view: "customer", id: r.c.id });
  };
  const onKeyDown = (e) => {
    if (!open || !results.length) { if (e.key === "Enter" && results.length) pick(results[0]); return; }
    if (e.key === "ArrowDown") { e.preventDefault(); setHi(h => Math.min(h + 1, results.length - 1)); }
    else if (e.key === "ArrowUp") { e.preventDefault(); setHi(h => Math.max(h - 1, 0)); }
    else if (e.key === "Enter") { e.preventDefault(); pick(results[hi] || results[0]); }
    else if (e.key === "Escape") { setOpen(false); }
  };

  return (
    <div className="topbar__search" ref={boxRef} style={{ position: "relative" }}>
      <Icon name="search" />
      <input ref={inputRef} placeholder="Search orders, customers…" value={q}
        onChange={e => { setQ(e.target.value); setOpen(true); setHi(0); }}
        onFocus={() => q && setOpen(true)} onKeyDown={onKeyDown} />
      <kbd>⌘K</kbd>
      {open && results.length > 0 && (
        <div className="gsearch__pop">
          {results.map((r, i) => {
            if (r.kind === "openOrder") return (
              <button key={"o" + i} className={"gsearch__row" + (i === hi ? " is-hi" : "")} onMouseEnter={() => setHi(i)} onMouseDown={e => { e.preventDefault(); pick(r); }}>
                <span className="gsearch__main">Open order {r.id}</span>
                <span className="muted" style={{ fontSize: 11 }}>not in the loaded list — fetch it</span>
              </button>
            );
            if (r.kind === "customer") return (
              <button key={"c" + i} className={"gsearch__row" + (i === hi ? " is-hi" : "")} onMouseEnter={() => setHi(i)} onMouseDown={e => { e.preventDefault(); pick(r); }}>
                <span className="gsearch__main"><Icon name="customers" /> {r.c.name || r.c.email}</span>
                <span className="muted" style={{ fontSize: 11 }}>{r.c.email || ""}</span>
              </button>
            );
            const o = r.o; const cust = DTF.getCustomer ? DTF.getCustomer(o.customerId) : null;
            const st = (DTF.statusMeta && DTF.statusMeta[o.status]) || null;
            return (
              <button key={"o" + i} className={"gsearch__row" + (i === hi ? " is-hi" : "")} onMouseEnter={() => setHi(i)} onMouseDown={e => { e.preventDefault(); pick(r); }}>
                <span className="gsearch__main"><b>{o.id}</b> <span className="muted">{cust ? cust.name : ""}</span></span>
                {st ? <span className={"badge " + st.cls}>{st.label}</span> : <span className="muted" style={{ fontSize: 11 }}>{o.status}</span>}
              </button>
            );
          })}
        </div>
      )}
    </div>
  );
}

/* ---------------- Topbar ---------------- */
const notifAgo = (ts) => {
  if (!ts) return "";
  const s = Math.max(0, Math.round((Date.now() - ts) / 1000));
  if (s < 60) return "just now";
  const m = Math.round(s / 60); if (m < 60) return m + "m ago";
  const h = Math.round(m / 60); if (h < 48) return h + "h ago";
  return Math.round(h / 24) + "d ago";
};
// Top-bar bell: polls the notification centre; unread badge; a dropdown of past
// notifications (gang sheet done/failed, and anything else we push). Marks all read
// when opened; each links to the relevant page.
function NotificationBell({ go }) {
  const [open, setOpen] = useState(false);
  const [data, setData] = useState({ notifications: [], unread: 0 });
  const ref = useRef(null);
  useEffect(() => {
    let alive = true;
    const load = () => { if (DTF.notifications) DTF.notifications().then(d => { if (alive) setData(d || { notifications: [], unread: 0 }); }).catch(() => {}); };
    load(); const t = setInterval(load, 15000);
    return () => { alive = false; clearInterval(t); };
  }, []);
  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [open]);
  const toggle = async () => {
    const willOpen = !open; setOpen(willOpen);
    if (willOpen && data.unread) { try { await DTF.notificationsRead("all"); setData(d => ({ ...d, unread: 0, notifications: d.notifications.map(n => n.readAt ? n : { ...n, readAt: Date.now() }) })); } catch {} }
  };
  const openNotif = (n) => {
    setOpen(false);
    if (!n.link) return;
    if (n.link.charAt(0) === "#") location.hash = n.link.slice(1);
    else window.open(n.link, "_blank", "noopener");
  };
  const clearAll = async (e) => { e.stopPropagation(); try { await DTF.notificationsClear(); setData({ notifications: [], unread: 0 }); } catch {} };
  const runAction = async (e, a) => {
    e.stopPropagation();
    if (!a || !a.href) return;
    if (a.method === "POST") {
      try {
        const r = await fetch(a.href, { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}" });
        if (!r.ok) { const j = await r.json().catch(() => ({})); alert(j.error || "Couldn't re-print — the job may have expired."); return; }
        if (DTF.notifications) DTF.notifications().then(d => setData(d || { notifications: [], unread: 0 })).catch(() => {});
      } catch { alert("Re-print failed — is the print agent connected?"); }
    } else {
      window.open(a.href, "_blank", "noopener");
    }
  };
  const dot = { success: "#16a34a", error: "#b91c1c", warn: "#b45309", info: "#6b7280" };
  return (
    <div className="notif" ref={ref} style={{ position: "relative" }}>
      <button className="icon-btn" title="Notifications" onClick={toggle}><Icon name="bell" />{data.unread > 0 && <span className="ping">{data.unread}</span>}</button>
      {open && (
        <div className="notif__panel">
          <div className="notif__head"><b>Notifications</b>{data.notifications.length > 0 && <button className="linklike" onClick={clearAll}>Clear all</button>}</div>
          <div className="notif__list">
            {data.notifications.length === 0
              ? <div className="notif__empty">You're all caught up.</div>
              : data.notifications.map(n => (
                  <div key={n.id} className={"notif__item" + (n.readAt ? "" : " is-unread") + (n.link ? " is-link" : "")} onClick={() => openNotif(n)}>
                    <span className="notif__dot" style={{ background: dot[n.level] || dot.info }} />
                    <div style={{ minWidth: 0, flex: 1 }}>
                      <div className="notif__title">{n.title}</div>
                      {n.body && <div className="notif__body">{n.body}</div>}
                      <div className="notif__time">{notifAgo(n.updatedAt || n.createdAt)}</div>
                      {Array.isArray(n.actions) && n.actions.length > 0 && (
                        <div className="notif__acts">
                          {n.actions.map((a, i) => (
                            <button key={i} className={"notif__act notif__act--" + a.kind} onClick={(e) => runAction(e, a)}>
                              <Icon name={a.kind === "download" ? "download" : (a.kind === "reprint" ? "print" : "refresh")} />{a.label}
                            </button>
                          ))}
                        </div>
                      )}
                    </div>
                    {n.link && <span className="notif__arrow">↗</span>}
                  </div>
                ))}
          </div>
        </div>
      )}
    </div>
  );
}

function Topbar({ title, sub, actions, route, go, onReload, reloading, onMenu }) {
  // Prev/next through the list an order was opened from (board/all/search/…),
  // live-filtered so orders that have left the source list stop being counted.
  const [, bump] = useState(0);
  useEffect(() => {
    const h = () => bump(x => x + 1);
    window.addEventListener("dtf:workflow", h);      // an order changed stage
    window.addEventListener("dtf:refreshed", h);     // data reloaded
    return () => { window.removeEventListener("dtf:workflow", h); window.removeEventListener("dtf:refreshed", h); };
  }, []);
  const ordNav = (route && route.view === "order" && DTF.navStepper) ? DTF.navStepper(route.id) : null;
  // [ / ] step to the previous / next order (ignored while typing in a field).
  const prevId = ordNav && ordNav.prevId, nextId = ordNav && ordNav.nextId;
  useEffect(() => {
    if (!prevId && !nextId) return;
    const onKey = (e) => {
      if (e.metaKey || e.ctrlKey || e.altKey) return;
      const t = e.target;
      if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable)) return;
      if (e.key === "[" && prevId) { e.preventDefault(); go({ view: "order", id: prevId }); }
      else if (e.key === "]" && nextId) { e.preventDefault(); go({ view: "order", id: nextId }); }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [prevId, nextId]);
  return (
    <header className="topbar">
      <button className="icon-btn topbar__menu" title="Menu" onClick={onMenu}><Icon name="dots" /></button>
      <div className="topbar__title">
        {ordNav ? (
          <div className="topbar__ordnav" title={ordNav.src ? "From " + ordNav.src : ""}>
            <button className="icon-btn" disabled={!ordNav.prevId} onClick={() => ordNav.prevId && go({ view: "order", id: ordNav.prevId })} title="Previous order  ( [ )"><Icon name="back" /></button>
            <h1>{title}</h1>
            <button className="icon-btn" disabled={!ordNav.nextId} onClick={() => ordNav.nextId && go({ view: "order", id: ordNav.nextId })} title="Next order  ( ] )"><span style={{ transform: "scaleX(-1)", display: "grid" }}><Icon name="back" /></span></button>
            <span className="topbar__ordpos">{ordNav.pos} / {ordNav.len}</span>
          </div>
        ) : (<>
          <h1>{title}</h1>
          {sub && <span className="sub">{sub}</span>}
        </>)}
      </div>
      <GlobalSearch go={go} />
      {actions}
      {onReload && <button className="icon-btn" title="Re-fetch all data from Shopify & Gmail" onClick={onReload} disabled={reloading}>{reloading ? <span className="spin spin--dark" /> : <Icon name="refresh" />}</button>}
      <NotificationBell go={go} />
      <button className="icon-btn" title="Settings" onClick={() => go && go({ view:"settings" })}><Icon name="cog" /></button>
    </header>
  );
}

/* loading skeletons */
function SkeletonRows({ rows = 8 }) {
  return (
    <div className="skel-list">
      {Array.from({ length: rows }).map((_, i) => (
        <div key={i} className="skel skel-row" style={{ opacity: Math.max(0.35, 1 - i * 0.07) }} />
      ))}
    </div>
  );
}
// Full-screen skeleton for the initial app load (faux topbar + content rows).
function SkeletonScreen() {
  return (
    <div className="app">
      <div className="main" style={{ gridColumn: "1 / -1" }}>
        <div className="content skel-screen">
          <div className="skel skel-head" />
          <SkeletonRows rows={10} />
        </div>
      </div>
    </div>
  );
}

/* expose */
Object.assign(window, {
  Icon, StatusBadge, MetaBadge, ChannelBadge, Flags, Tags, MethodCell, Avatar, CopyBtn,
  Sidebar, Topbar, SkeletonRows, SkeletonScreen,
});
