/* =============================================================
   CoreIQ Office — Stock screens
   Overview · Manage · New stock line
   ============================================================= */
const { useState: stkUseState, useMemo: stkUseMemo } = React;

// -----------------------------------------------------------------
// STOCK OVERVIEW
// -----------------------------------------------------------------
const stkMoney = (cents) => "$" + (Number(cents || 0) / 100).toLocaleString("en-AU", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const stkNum = (n) => Number(n || 0).toLocaleString("en-AU");

// -----------------------------------------------------------------
// STOCK OVERVIEW — LIVE · this pharmacy
// The org's own inventory, from Aurora: catalogue counts (/overview) + the
// stocked lines, real category facets and totals (/stock). Categories are the
// pharmacy's real 79 (Z Departments/SubDepartments); products Z never
// categorised fall under "Uncategorised" — honest, not invented.
// -----------------------------------------------------------------
function StockOverview() {
  const { scope, stores, setScreen, setScreenState } = window.useOffice();
  const openProduct = (pid) => { setScreenState((st) => ({ ...st, activeProductId: pid, productFrom: "stockOverview" })); setScreen("productDetail"); };

  const [ov, setOv] = stkUseState(null);
  const [stock, setStock] = stkUseState(null);
  const [loading, setLoading] = stkUseState(true);
  const [err, setErr] = stkUseState(null);

  const [q, setQ] = stkUseState("");
  const [cat, setCat] = stkUseState("all"); // "all" | "__uncat__" | categoryId
  const [sort, setSort] = stkUseState({ key: "valueCents", dir: "desc" });

  React.useEffect(() => {
    let alive = true;
    setLoading(true);
    setErr(null);
    const siteId = scope === "all" ? undefined : scope;
    Promise.all([window.OfficeAPI.getOverview(siteId), window.OfficeAPI.getStockView(siteId)])
      .then(([o, s]) => { if (alive) { setOv(o); setStock(s); setLoading(false); } })
      .catch((e) => { if (alive) { setErr(String((e && e.message) || e)); setLoading(false); } });
    return () => { alive = false; };
  }, [scope]);

  const items = (stock && stock.items) || [];
  const cats = (stock && stock.categories) || [];
  const totals = (stock && stock.totals) || { stockedSkus: 0, unitsOnShelf: 0, stockValueCents: 0 };
  const realCats = cats.filter((c) => c.id).sort((a, b) => b.stockedCount - a.stockedCount);
  const uncat = cats.find((c) => !c.id);

  const filtered = stkUseMemo(() => {
    let rows = items;
    if (cat === "__uncat__") rows = rows.filter((r) => !r.categoryId);
    else if (cat !== "all") rows = rows.filter((r) => r.categoryId === cat);
    if (q.trim()) {
      const needle = q.trim().toLowerCase();
      rows = rows.filter((r) => (r.name + " " + r.sku + " " + (r.categoryName || "")).toLowerCase().includes(needle));
    }
    const dir = sort.dir === "asc" ? 1 : -1;
    return [...rows].sort((a, b) => {
      const ka = a[sort.key], kb = b[sort.key];
      if (typeof ka === "number") return (ka - kb) * dir;
      return String(ka || "").localeCompare(String(kb || "")) * dir;
    });
  }, [items, cat, q, sort]);

  function sortBy(key) { setSort((s) => (s.key === key ? { key, dir: s.dir === "asc" ? "desc" : "asc" } : { key, dir: "asc" })); }

  const COLS = [
    { k: "name", label: "Product", num: false },
    { k: "sku", label: "SKU", num: false },
    { k: "categoryName", label: "Category", num: false },
    { k: "schedule", label: "Sched", num: false },
    { k: "quantityOnHand", label: "On hand", num: true },
    { k: "costPriceCents", label: "Cost", num: true },
    { k: "sellPriceCents", label: "Sell", num: true },
    { k: "valueCents", label: "Value", num: true },
  ];

  const scopeName = scope === "all" ? "all stores" : (stores.find((s) => s.id === scope)?.name || "—");

  return (
    <div className="off-screen">
      <header className="off-screen-header">
        <div className="off-screen-header-meta">
          <span className="off-screen-eyebrow">Inventory · {scopeName} · live from Aurora</span>
          <h2 className="off-screen-title">Stock overview</h2>
        </div>
        <div className="off-screen-actions">
          <button className="btn btn--secondary btn--sm" onClick={() => setScreen("catalogue")}><OIcon name="pill"/> National catalogue</button>
        </div>
      </header>

      <div className="off-screen-body">
        {err && (
          <div className="off-card" style={{borderColor:'var(--void-border)',background:'var(--void-bg)',color:'var(--void-text)',marginBottom:16,padding:'12px 16px',flexDirection:'row',alignItems:'center',gap:10}}>
            <OIcon name="alert"/> <span>Couldn't load stock from the API: {err}</span>
          </div>
        )}

        {/* Catalogue (from /overview) + stock (from /stock) KPIs, together */}
        <div className="off-kpi-grid">
          <KpiCard label="Products in catalogue" value={loading || !ov ? "…" : stkNum(ov.products)} delta={{ text: ov ? stkNum(ov.activeProducts) + " active / sellable" : "" }}/>
          <KpiCard label="Stock value (cost)" value={loading ? "…" : stkMoney(totals.stockValueCents)} delta={{ dir: "up", text: "value on the shelf" }}/>
          <KpiCard label="SKUs in stock" value={loading ? "…" : stkNum(totals.stockedSkus)} delta={{ text: "on-hand > 0" }}/>
          <KpiCard label="Units on shelf" value={loading ? "…" : stkNum(Math.round(totals.unitsOnShelf))} delta={{ text: scope === "all" ? "all stores" : "this store" }}/>
        </div>

        <div style={{display:'grid', gridTemplateColumns:'260px 1fr', gap:14, alignItems:'flex-start', marginTop:16}}>
          {/* Real category rail */}
          <div className="off-card" style={{padding:8}}>
            <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',padding:'8px 10px'}}>
              <span style={{display:'inline-flex',alignItems:'center',gap:6,fontSize:'var(--fs-12)',fontWeight:600,letterSpacing:'0.04em',color:'var(--text-muted)'}}><OIcon name="tag" width={13} height={13}/> CATEGORIES</span>
              <span style={{fontSize:'var(--fs-11)',color:'var(--text-subtle)'}}>{realCats.length} real</span>
            </div>
            <CatRow active={cat === "all"} name="All stocked" count={totals.stockedSkus} onClick={() => setCat("all")} strong/>
            {realCats.map((c) => <CatRow key={c.id} active={cat === c.id} name={c.name} count={c.stockedCount} onClick={() => setCat(c.id)}/>)}
            {uncat && <CatRow active={cat === "__uncat__"} name="Uncategorised" count={uncat.stockedCount} onClick={() => setCat("__uncat__")} muted/>}
          </div>

          {/* Real stocked-lines table */}
          <div className="off-table" style={{minWidth:0}}>
            <div className="off-table-toolbar">
              <div className="input-wrap" style={{flex:1, maxWidth:320}}>
                <OIcon name="search" className="input-wrap-icon"/>
                <input className="input" placeholder="Search product, SKU, category…" value={q} onChange={(e) => setQ(e.target.value)} style={{height:34,fontSize:'var(--fs-13)'}}/>
              </div>
              <div style={{marginLeft:'auto', fontSize:'var(--fs-12)', color:'var(--text-muted)'}}>{loading ? "loading…" : stkNum(filtered.length) + " of " + stkNum(items.length) + " lines"}</div>
            </div>
            <div className="off-grid" style={{gridTemplateColumns:'minmax(220px,1.6fr) 130px 150px 70px 90px 90px 90px 100px'}}>
              {COLS.map((c) => (
                <div key={c.k} className="off-grid-headcell" onClick={() => sortBy(c.k)} style={c.num ? {justifyContent:'flex-end'} : null}>
                  {c.label}
                  {sort.key === c.k && <OIcon className="sort-arrow" name={sort.dir === "asc" ? "arrow-up" : "arrow-down"}/>}
                </div>
              ))}
              {filtered.slice(0, 500).map((r) => (
                <div key={r.productId} className="off-grid-row" style={{cursor:'pointer'}} onClick={() => openProduct(r.productId)}>
                  <div className="off-grid-cell off-cell-strong">{r.name}</div>
                  <div className="off-grid-cell off-cell-mono">{r.sku}</div>
                  <div className="off-grid-cell">{r.categoryName || <span style={{color:'var(--text-subtle)'}}>—</span>}</div>
                  <div className="off-grid-cell">{r.schedule ? <window.SchedBadge schedule={r.schedule} compact/> : ""}</div>
                  <div className="off-grid-cell off-cell-num off-cell-strong">{r.quantityOnHand}</div>
                  <div className="off-grid-cell off-cell-num off-muted">{r.costPriceCents == null ? "—" : stkMoney(r.costPriceCents)}</div>
                  <div className="off-grid-cell off-cell-num">{stkMoney(r.sellPriceCents)}</div>
                  <div className="off-grid-cell off-cell-num off-cell-strong">{stkMoney(r.valueCents)}</div>
                </div>
              ))}
            </div>
            {filtered.length > 500 && <div style={{padding:'10px 14px',fontSize:'var(--fs-12)',color:'var(--text-subtle)'}}>Showing the top 500 of {stkNum(filtered.length)} by value — refine with search or a category.</div>}
            {!loading && filtered.length === 0 && <div className="off-empty" style={{padding:30}}><div className="empty-title">No stocked lines match.</div></div>}
          </div>
        </div>
      </div>
    </div>
  );
}

function CatRow({ active, name, count, onClick, strong, muted }) {
  return (
    <div onClick={onClick} style={{display:'flex',alignItems:'center',justifyContent:'space-between',gap:8,padding:'8px 10px',borderRadius:8,cursor:'pointer',background: active ? 'var(--brand-bg)' : 'transparent', color: active ? 'var(--brand)' : 'inherit'}}>
      <span style={{fontSize:'var(--fs-13)', fontWeight: strong ? 600 : 500, color: muted && !active ? 'var(--text-subtle)' : 'inherit', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'}}>{name}</span>
      <span style={{fontSize:'var(--fs-12)', color: active ? 'var(--brand)' : 'var(--text-subtle)', fontVariantNumeric:'tabular-nums'}}>{Number(count || 0).toLocaleString("en-AU")}</span>
    </div>
  );
}

// Mutates OD.CATEGORIES + rebuilds lookup maps so the new node is
// visible everywhere (rail, tree view, new-stock-line dropdowns).
const LEVEL_LABEL = { dept: "Department", cat: "Category", sub: "Subcategory" };
function slugify(name) {
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 32);
}
function uniqId(prefix, name) {
  let base = slugify(name) || prefix;
  let id = base;
  let n = 2;
  const taken = new Set([
    ...OD.CATEGORIES.map(d => d.id),
    ...Object.keys(OD.CAT_BY_ID),
    ...Object.keys(OD.SUB_BY_ID),
  ]);
  while (taken.has(id)) { id = base + "-" + n; n++; }
  return id;
}
function addCategoryNode({ level, parentId, name, desc }) {
  name = (name || "").trim();
  if (!name) return null;
  if (level === "dept") {
    const id = uniqId("dept", name);
    const node = { id, name, desc: (desc || "").trim() || "Custom department", cats: [] };
    OD.CATEGORIES.push(node);
    OD.DEPT_BY_ID[id] = node;
    return node;
  }
  if (level === "cat") {
    const dept = OD.DEPT_BY_ID[parentId];
    if (!dept) return null;
    const id = uniqId("cat", name);
    const node = { id, name, subs: [] };
    dept.cats.push(node);
    OD.CAT_BY_ID[id] = { ...node, deptId: dept.id };
    return node;
  }
  if (level === "sub") {
    const cat = OD.CAT_BY_ID[parentId];
    if (!cat) return null;
    const dept = OD.DEPT_BY_ID[cat.deptId];
    const liveCat = dept?.cats.find(c => c.id === cat.id);
    if (!liveCat) return null;
    const id = uniqId("sub", name);
    const node = { id, name };
    liveCat.subs.push(node);
    OD.SUB_BY_ID[id] = { ...node, catId: liveCat.id, deptId: dept.id };
    return node;
  }
  return null;
}

// -----------------------------------------------------------------
// NEW CATEGORY MODAL — used by the rail's + buttons. Lets the user
// pick what level (Dept / Cat / Sub) and the parent, then name the
// new node. Defaults to the level/parent the user clicked from.
// -----------------------------------------------------------------
function NewCategoryModal({ initial, onClose, onCreate }) {
  const [level, setLevel] = stkUseState(initial.level);
  const [parentId, setParentId] = stkUseState(initial.parentId || "");
  const [name, setName] = stkUseState("");
  const [desc, setDesc] = stkUseState("");

  // When level changes, reset parent appropriately
  function pickLevel(l) {
    setLevel(l);
    if (l === "dept") setParentId("");
    if (l === "cat")  setParentId(OD.CATEGORIES[0]?.id || "");
    if (l === "sub")  setParentId(OD.CATEGORIES[0]?.cats[0]?.id || "");
  }

  // For sub-level, the parent select needs to show categories grouped
  // by department so users can disambiguate (e.g. "Allergy" exists in
  // both Dispensary and OTC potentially).
  const catOptions = stkUseMemo(() => {
    const out = [];
    OD.CATEGORIES.forEach(d => d.cats.forEach(c => out.push({ id:c.id, label:`${d.name} › ${c.name}`, deptName:d.name, name:c.name })));
    return out;
  }, [initial]);

  const canCreate = !!name.trim() && (level === "dept" || !!parentId);

  // Helper text under the form preview the structural change
  function structurePreview() {
    if (!name.trim()) return <span style={{color:'var(--text-subtle)'}}>Type a name to preview the new node</span>;
    if (level === "dept") return <><strong>{name}</strong> · top-level department</>;
    if (level === "cat") {
      const d = OD.DEPT_BY_ID[parentId];
      return <><span style={{color:'var(--text-subtle)'}}>{d?.name} ›</span> <strong>{name}</strong></>;
    }
    if (level === "sub") {
      const c = OD.CAT_BY_ID[parentId];
      const d = c ? OD.DEPT_BY_ID[c.deptId] : null;
      return <><span style={{color:'var(--text-subtle)'}}>{d?.name} › {c?.name} ›</span> <strong>{name}</strong></>;
    }
  }

  return (
    <div className="pd-modal-back" onClick={onClose}>
      <div className="pd-modal" onClick={e => e.stopPropagation()} style={{width: 500}}>
        <header className="pd-modal-head">
          <div>
            <div style={{fontSize:'var(--fs-11)', textTransform:'uppercase', letterSpacing:'0.08em', color:'var(--text-subtle)', fontWeight:600}}>Categories</div>
            <div style={{fontSize:'var(--fs-16)', fontWeight:600, marginTop:2, letterSpacing:'-0.012em'}}>New {LEVEL_LABEL[level].toLowerCase()}</div>
          </div>
          <button className="off-topbar-btn" onClick={onClose}><OIcon name="x"/></button>
        </header>
        <div className="pd-modal-body">
          {/* Level picker */}
          <div className="field">
            <label className="field-label">Level</label>
            <div className="newcat-level">
              {[
                { id:"dept", title:"Department", hint:"Top-level (e.g. NDSS)" },
                { id:"cat",  title:"Category",   hint:"Inside a department" },
                { id:"sub",  title:"Subcategory", hint:"Inside a category" },
              ].map(opt => (
                <button
                  key={opt.id}
                  className={"newcat-level-opt" + (level === opt.id ? " is-selected" : "")}
                  onClick={() => pickLevel(opt.id)}
                  type="button"
                >
                  <div className="newcat-level-title">{opt.title}</div>
                  <div className="newcat-level-hint">{opt.hint}</div>
                </button>
              ))}
            </div>
          </div>

          {/* Parent picker — only for cat/sub */}
          {level === "cat" && (
            <div className="field">
              <label className="field-label">Parent department</label>
              <select className="input select" value={parentId} onChange={e => setParentId(e.target.value)}>
                <option value="">Select…</option>
                {OD.CATEGORIES.map(d => <option key={d.id} value={d.id}>{d.name}</option>)}
              </select>
            </div>
          )}
          {level === "sub" && (
            <div className="field">
              <label className="field-label">Parent category</label>
              <select className="input select" value={parentId} onChange={e => setParentId(e.target.value)}>
                <option value="">Select…</option>
                {catOptions.map(c => <option key={c.id} value={c.id}>{c.label}</option>)}
              </select>
            </div>
          )}

          {/* Name */}
          <div className="field">
            <label className="field-label">Name <span className="field-required">*</span></label>
            <input className="input" autoFocus value={name} onChange={e => setName(e.target.value)}
              placeholder={
                level === "dept" ? "e.g. Veterinary"
                : level === "cat" ? "e.g. Eye care"
                : "e.g. Wound dressings"
              }/>
          </div>

          {/* Description — only for departments */}
          {level === "dept" && (
            <div className="field">
              <label className="field-label">Description <span style={{color:'var(--text-subtle)', fontWeight:400}}>(optional)</span></label>
              <input className="input" value={desc} onChange={e => setDesc(e.target.value)}
                placeholder="One-line label shown in the tree"/>
            </div>
          )}

          {/* Preview */}
          <div style={{padding:'10px 12px', background:'var(--bg-subtle)', borderRadius:8, border:'1px solid var(--border-subtle)', fontSize:'var(--fs-13)'}}>
            <div style={{fontSize:10, textTransform:'uppercase', letterSpacing:'0.08em', color:'var(--text-subtle)', fontWeight:600, marginBottom:4}}>Will appear as</div>
            <div style={{fontFamily:'var(--font-mono)', fontSize:12.5}}>{structurePreview()}</div>
          </div>
        </div>
        <footer className="pd-modal-foot">
          <button className="btn btn--ghost btn--sm" onClick={onClose}>Cancel</button>
          <button className="btn btn--primary btn--sm" disabled={!canCreate}
            onClick={() => onCreate({ level, parentId, name, desc })}>
            <OIcon name="check"/> Create {LEVEL_LABEL[level].toLowerCase()}
          </button>
        </footer>
      </div>
    </div>
  );
}

// -----------------------------------------------------------------
// CATEGORY RAIL — left-side hierarchical filter tree
//   Department › Category › Subcategory · click any node to scope the
//   product list. Counts are SKUs visible in current scope, value is
//   rolled up from on-hand × cost.
// -----------------------------------------------------------------
function CategoryRail({ rows, treeSel, setTreeSel, onAdd, catTick }) {
  // Default-open the department that contains the current selection
  const initOpen = stkUseMemo(() => {
    const open = {};
    OD.CATEGORIES.forEach(d => { open[d.id] = false; });
    if (treeSel.kind === "dept") open[treeSel.id] = true;
    if (treeSel.kind === "cat")  open[OD.CAT_BY_ID[treeSel.id]?.deptId] = true;
    if (treeSel.kind === "sub")  open[OD.SUB_BY_ID[treeSel.id]?.deptId] = true;
    return open;
  }, []);
  const [openDept, setOpenDept] = stkUseState(initOpen);
  const [openCat, setOpenCat] = stkUseState(() => {
    const o = {};
    if (treeSel.kind === "cat") o[treeSel.id] = true;
    if (treeSel.kind === "sub") o[OD.SUB_BY_ID[treeSel.id]?.catId] = true;
    return o;
  });

  // Pre-compute stats
  const stats = stkUseMemo(() => {
    const byDept = {}, byCat = {}, bySub = {};
    rows.forEach(r => {
      const d = byDept[r.dept] = byDept[r.dept] || { skus:0, units:0, value:0, low:0 };
      const c = byCat[r.cat]   = byCat[r.cat]   || { skus:0, units:0, value:0, low:0 };
      const s = bySub[r.sub]   = bySub[r.sub]   || { skus:0, units:0, value:0, low:0 };
      [d,c,s].forEach(o => {
        o.skus++; o.units += r.onHand; o.value += r.stockValue;
        if (r.onHand <= r.min && r.min > 0) o.low++;
      });
    });
    return { byDept, byCat, bySub, total: rows.length };
  }, [rows]);

  const isSel = (kind, id) => treeSel.kind === kind && (kind === "all" || treeSel.id === id);

  return (
    <aside className="cat-rail">
      <div className="cat-rail-header">
        <OIcon name="tag" width={13} height={13}/>
        <span>Categories</span>
        <button
          className="cat-rail-add"
          onClick={() => onAdd && onAdd({ level: "dept" })}
          title="New department"
        >
          <OIcon name="plus" width={11} height={11}/>
        </button>
        <span className="cat-rail-count">{stats.total} SKUs</span>
      </div>

      <button
        className={"cat-rail-row cat-rail-row--all" + (isSel("all") ? " is-selected" : "")}
        onClick={() => setTreeSel({ kind:"all" })}
      >
        <span className="cat-rail-label">All departments</span>
        <span className="cat-rail-num">{stats.total}</span>
      </button>

      <div className="cat-rail-tree">
        {OD.CATEGORIES.map(d => {
          const s = stats.byDept[d.id] || { skus:0, low:0 };
          if (s.skus === 0) return null;
          const open = openDept[d.id];
          return (
            <div key={d.id} className="cat-rail-dept">
              <div
                className={"cat-rail-row cat-rail-row--dept" + (isSel("dept", d.id) ? " is-selected" : "")}
              >
                <button
                  className="cat-rail-twist"
                  onClick={e => { e.stopPropagation(); setOpenDept(o => ({...o, [d.id]: !o[d.id]})); }}
                  aria-label={open ? "Collapse" : "Expand"}
                >
                  <OIcon name={open ? "chevron-down" : "chevron-right"} width={11} height={11}/>
                </button>
                <button
                  className="cat-rail-label cat-rail-label--btn"
                  onClick={() => setTreeSel({ kind:"dept", id:d.id })}
                  title={d.desc}
                >
                  {d.name}
                </button>
                <button
                  className="cat-rail-inline-add"
                  onClick={e => { e.stopPropagation(); onAdd && onAdd({ level:"cat", parentId:d.id }); }}
                  title={`Add category in ${d.name}`}
                >
                  <OIcon name="plus" width={10} height={10}/>
                </button>
                {s.low > 0 && <span className="cat-rail-pip" title={`${s.low} low`}/>}
                <span className="cat-rail-num">{s.skus}</span>
              </div>

              {open && (
                <div className="cat-rail-children">
                  {d.cats.map(c => {
                    const cs = stats.byCat[c.id];
                    if (!cs || cs.skus === 0) return null;
                    const copen = openCat[c.id];
                    return (
                      <div key={c.id}>
                        <div className={"cat-rail-row cat-rail-row--cat" + (isSel("cat", c.id) ? " is-selected" : "")}>
                          <button
                            className="cat-rail-twist"
                            onClick={e => { e.stopPropagation(); setOpenCat(o => ({...o, [c.id]: !o[c.id]})); }}
                            aria-label={copen ? "Collapse" : "Expand"}
                          >
                            <OIcon name={copen ? "chevron-down" : "chevron-right"} width={11} height={11}/>
                          </button>
                          <button
                            className="cat-rail-label cat-rail-label--btn"
                            onClick={() => setTreeSel({ kind:"cat", id:c.id })}
                          >
                            {c.name}
                          </button>
                          <button
                            className="cat-rail-inline-add"
                            onClick={e => { e.stopPropagation(); onAdd && onAdd({ level:"sub", parentId:c.id }); }}
                            title={`Add subcategory in ${c.name}`}
                          >
                            <OIcon name="plus" width={10} height={10}/>
                          </button>
                          {cs.low > 0 && <span className="cat-rail-pip" title={`${cs.low} low`}/>}
                          <span className="cat-rail-num">{cs.skus}</span>
                        </div>

                        {copen && (
                          <div className="cat-rail-children cat-rail-children--sub">
                            {c.subs.map(s2 => {
                              const ss = stats.bySub[s2.id];
                              if (!ss || ss.skus === 0) return null;
                              return (
                                <button
                                  key={s2.id}
                                  className={"cat-rail-row cat-rail-row--sub" + (isSel("sub", s2.id) ? " is-selected" : "")}
                                  onClick={() => setTreeSel({ kind:"sub", id:s2.id })}
                                >
                                  <span className="cat-rail-label">{s2.name}</span>
                                  {ss.low > 0 && <span className="cat-rail-pip" title={`${ss.low} low`}/>}
                                  <span className="cat-rail-num">{ss.skus}</span>
                                </button>
                              );
                            })}
                          </div>
                        )}
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
          );
        })}
      </div>

      <button
        className="cat-rail-footer-add"
        onClick={() => onAdd && onAdd({ level: "dept" })}
        title="New top-level department"
      >
        <OIcon name="plus" width={11} height={11}/>
        <span>New department</span>
      </button>
    </aside>
  );
}

// -----------------------------------------------------------------
// TREE BREADCRUMB — shows current selection above the table, with clear
// -----------------------------------------------------------------
function TreeBreadcrumb({ treeSel, setTreeSel }) {
  const parts = [];
  if (treeSel.kind === "dept") {
    parts.push({ label: OD.DEPT_BY_ID[treeSel.id]?.name });
  } else if (treeSel.kind === "cat") {
    const c = OD.CAT_BY_ID[treeSel.id];
    parts.push({ label: OD.DEPT_BY_ID[c?.deptId]?.name, onClick: () => setTreeSel({kind:"dept", id:c.deptId}) });
    parts.push({ label: c?.name });
  } else if (treeSel.kind === "sub") {
    const s = OD.SUB_BY_ID[treeSel.id];
    parts.push({ label: OD.DEPT_BY_ID[s?.deptId]?.name, onClick: () => setTreeSel({kind:"dept", id:s.deptId}) });
    parts.push({ label: OD.CAT_BY_ID[s?.catId]?.name,  onClick: () => setTreeSel({kind:"cat",  id:s.catId}) });
    parts.push({ label: s?.name });
  }
  return (
    <div className="tree-crumb">
      <button className="tree-crumb-root" onClick={() => setTreeSel({kind:"all"})}>All</button>
      {parts.map((p, i) => (
        <React.Fragment key={i}>
          <span className="tree-crumb-sep">›</span>
          {p.onClick
            ? <button className="tree-crumb-link" onClick={p.onClick}>{p.label}</button>
            : <span className="tree-crumb-current">{p.label}</span>}
        </React.Fragment>
      ))}
      <button className="tree-crumb-clear" onClick={() => setTreeSel({kind:"all"})}>
        <OIcon name="x" width={11} height={11}/> Clear
      </button>
    </div>
  );
}

// -----------------------------------------------------------------
// STOCK TREE VIEW — hierarchical 3-level rollup: Department → Category
// → Subcategory → products. Collapsible at every level. Totals at each
// header row. This is the "By category" view, properly hierarchical.
// -----------------------------------------------------------------
function StockTreeView({ rows, openProduct, treeSel, setTreeSel }) {
  // Build nested structure from flat rows
  const tree = stkUseMemo(() => {
    const t = {};
    rows.forEach(r => {
      const d = t[r.dept] = t[r.dept] || { rows:[], cats:{} };
      d.rows.push(r);
      const c = d.cats[r.cat] = d.cats[r.cat] || { rows:[], subs:{} };
      c.rows.push(r);
      const s = c.subs[r.sub] = c.subs[r.sub] || { rows:[] };
      s.rows.push(r);
    });
    return t;
  }, [rows]);

  // Default-open behaviour:
  //  - if a tree filter is active, expand the matching path; collapse others
  //  - if no filter, open departments at top level only
  const [openDept, setOpenDept] = stkUseState(() => {
    const o = {};
    OD.CATEGORIES.forEach(d => { o[d.id] = true; });
    if (treeSel.kind === "dept") { OD.CATEGORIES.forEach(d => o[d.id] = d.id === treeSel.id); }
    return o;
  });
  const [openCat, setOpenCat] = stkUseState(() => {
    const o = {};
    if (treeSel.kind === "cat") o[treeSel.id] = true;
    if (treeSel.kind === "sub") o[OD.SUB_BY_ID[treeSel.id]?.catId] = true;
    return o;
  });
  const [openSub, setOpenSub] = stkUseState(() => {
    const o = {};
    if (treeSel.kind === "sub") o[treeSel.id] = true;
    return o;
  });

  function totals(arr) {
    return arr.reduce((a, r) => ({
      units: a.units + r.onHand,
      value: a.value + r.stockValue,
      low:   a.low   + (r.onHand <= r.min && r.min > 0 ? 1 : 0),
      out:   a.out   + (r.onHand === 0 ? 1 : 0),
    }), { units:0, value:0, low:0, out:0 });
  }

  if (rows.length === 0) {
    return (
      <div className="off-empty" style={{padding:'48px 24px', textAlign:'center'}}>
        <div className="empty-icon"><OIcon name="tag" width={22} height={22}/></div>
        <div className="empty-title">No SKUs in this branch</div>
        <div style={{fontSize:'var(--fs-13)', color:'var(--text-muted)', marginTop:6}}>Try clearing filters or selecting a different category.</div>
      </div>
    );
  }

  return (
    <div className="stk-tree">
      {OD.CATEGORIES.map(d => {
        const dnode = tree[d.id];
        if (!dnode) return null;
        const t = totals(dnode.rows);
        const expanded = openDept[d.id];
        return (
          <section key={d.id} className="stk-tree-dept">
            <header
              className={"stk-tree-head stk-tree-head--dept" + (expanded ? " is-open" : "")}
              onClick={() => setOpenDept(o => ({...o, [d.id]: !o[d.id]}))}
            >
              <OIcon name={expanded ? "chevron-down" : "chevron-right"} width={14} height={14} className="stk-tree-twist"/>
              <div className="stk-tree-head-main">
                <div className="stk-tree-head-title">{d.name}</div>
                <div className="stk-tree-head-desc">{d.desc}</div>
              </div>
              <TreeStat label="SKUs"  value={dnode.rows.length}/>
              <TreeStat label="Units" value={t.units.toLocaleString()}/>
              <TreeStat label="Low"   value={t.low || "—"} tone={t.low > 0 ? "warn" : null}/>
              <TreeStat label="Value" value={oMoney(t.value)} strong/>
            </header>

            {expanded && Object.keys(dnode.cats).map(catId => {
              const cobj = OD.CAT_BY_ID[catId];
              const cnode = dnode.cats[catId];
              const ct = totals(cnode.rows);
              const copen = openCat[catId] !== false; // default-open inside expanded dept
              return (
                <div key={catId} className="stk-tree-cat">
                  <header
                    className={"stk-tree-head stk-tree-head--cat" + (copen ? " is-open" : "")}
                    onClick={() => setOpenCat(o => ({...o, [catId]: copen ? false : true}))}
                  >
                    <OIcon name={copen ? "chevron-down" : "chevron-right"} width={12} height={12} className="stk-tree-twist"/>
                    <div className="stk-tree-head-title stk-tree-head-title--cat">{cobj?.name}</div>
                    <TreeStat label="SKUs"  value={cnode.rows.length}/>
                    <TreeStat label="Units" value={ct.units.toLocaleString()}/>
                    <TreeStat label="Low"   value={ct.low || "—"} tone={ct.low > 0 ? "warn" : null}/>
                    <TreeStat label="Value" value={oMoney(ct.value)} strong/>
                  </header>

                  {copen && Object.keys(cnode.subs).map(subId => {
                    const sobj = OD.SUB_BY_ID[subId];
                    const snode = cnode.subs[subId];
                    const st = totals(snode.rows);
                    const sopen = openSub[subId] !== false;
                    return (
                      <div key={subId} className="stk-tree-sub">
                        <header
                          className={"stk-tree-head stk-tree-head--sub" + (sopen ? " is-open" : "")}
                          onClick={() => setOpenSub(o => ({...o, [subId]: sopen ? false : true}))}
                        >
                          <OIcon name={sopen ? "chevron-down" : "chevron-right"} width={11} height={11} className="stk-tree-twist"/>
                          <div className="stk-tree-head-title stk-tree-head-title--sub">{sobj?.name}</div>
                          <span className="stk-tree-sku-count">{snode.rows.length} {snode.rows.length === 1 ? "SKU" : "SKUs"}</span>
                          <TreeStat label="Units" value={st.units.toLocaleString()}/>
                          <TreeStat label="Low"   value={st.low || "—"} tone={st.low > 0 ? "warn" : null}/>
                          <TreeStat label="Value" value={oMoney(st.value)} strong/>
                        </header>

                        {sopen && (
                          <div className="off-grid stk-tree-rows" style={{gridTemplateColumns:'minmax(220px, 1.6fr) 150px 70px 90px 70px 100px 90px 100px'}}>
                            {snode.rows.map(r => {
                              const lowStock = r.onHand <= r.min && r.min > 0;
                              const outOfStock = r.onHand === 0;
                              return (
                                <div key={r.productId} className="off-grid-row" onClick={() => openProduct(r.productId)}>
                                  <div className="off-grid-cell off-cell-strong">{r.name} <span style={{color:'var(--text-subtle)', fontWeight:400}}>· {r.product.strength}</span></div>
                                  <div className="off-grid-cell off-cell-mono">{r.sku}</div>
                                  <div className="off-grid-cell">{r.schedule !== "—" && <window.SchedBadge schedule={r.schedule} compact/>}</div>
                                  <div className={"off-grid-cell off-cell-num " + (outOfStock ? "off-cell-strong" : "")} style={{color: outOfStock ? 'var(--void)' : lowStock ? 'var(--hold-text)' : 'inherit'}}>{r.onHand}</div>
                                  <div className="off-grid-cell off-cell-num off-muted">{r.min}</div>
                                  <div className="off-grid-cell off-cell-mono">{r.expiry}</div>
                                  <div className="off-grid-cell off-cell-num">{oMoney(r.price)}</div>
                                  <div className="off-grid-cell off-cell-num off-cell-strong">{oMoney(r.stockValue)}</div>
                                </div>
                              );
                            })}
                          </div>
                        )}
                      </div>
                    );
                  })}
                </div>
              );
            })}
          </section>
        );
      })}
    </div>
  );
}

function TreeStat({ label, value, strong, tone }) {
  return (
    <div className="tree-stat">
      <div className="tree-stat-label">{label}</div>
      <div className={"tree-stat-value" + (strong ? " is-strong" : "") + (tone === "warn" ? " is-warn" : "")}>{value}</div>
    </div>
  );
}

// -----------------------------------------------------------------
// MANAGE STOCK — bulk edit pricing, reorder rules
// -----------------------------------------------------------------
// -----------------------------------------------------------------
// MANAGE STOCK — LIVE · this pharmacy (read + WRITE)
// Real products from /products (search-driven over the org's 98k catalogue).
// Inline-edit cost / price / reorder min / max; Save PATCHes each changed
// product to Aurora — which the POS terminals then sync down. No mock.
// -----------------------------------------------------------------
function ManageStock() {
  const { setScreen, pushToast, setScreenState } = window.useOffice();
  const openProduct = (pid) => { setScreenState((st) => ({ ...st, activeProductId: pid, productFrom: "manageStock" })); setScreen("productDetail"); };
  const [products, setProducts] = stkUseState([]);
  const [loading, setLoading] = stkUseState(true);
  const [err, setErr] = stkUseState(null);
  const [q, setQ] = stkUseState("");
  const [selected, setSelected] = stkUseState(new Set());
  const [dirty, setDirty] = stkUseState({}); // { productId: { cost?, price?, min?, max? } }
  const [saving, setSaving] = stkUseState(false);
  const [catMap, setCatMap] = stkUseState({});
  const [supMap, setSupMap] = stkUseState({});

  // Reference maps (categories + suppliers) for name display — loaded once.
  React.useEffect(() => {
    let alive = true;
    Promise.all([
      window.OfficeAPI.listCategories().catch(() => []),
      window.OfficeAPI.listSuppliers().catch(() => []),
    ]).then(([cats, sups]) => {
      if (!alive) return;
      const cm = {}; (cats || []).forEach((c) => { cm[c.id] = c.name; });
      const sm = {}; (sups || []).forEach((s) => { sm[s.id] = s.name; });
      setCatMap(cm); setSupMap(sm);
    });
    return () => { alive = false; };
  }, []);

  function loadProducts() {
    const query = { limit: 200 };
    if (q.trim()) query.search = q.trim();
    return window.OfficeAPI.listProducts(query);
  }

  // Product list — server-side search, debounced.
  React.useEffect(() => {
    let alive = true;
    setLoading(true); setErr(null);
    const t = setTimeout(() => {
      loadProducts()
        .then((ps) => { if (alive) { setProducts(ps || []); setLoading(false); } })
        .catch((e) => { if (alive) { setErr(String((e && e.message) || e)); setLoading(false); } });
    }, q ? 300 : 0);
    return () => { alive = false; clearTimeout(t); };
  }, [q]);

  function toggle(id) { setSelected((s) => { const ns = new Set(s); ns.has(id) ? ns.delete(id) : ns.add(id); return ns; }); }
  function toggleAll() { setSelected((s) => (s.size === products.length ? new Set() : new Set(products.map((p) => p.id)))); }

  function baseField(p, field) {
    if (field === "cost") return p.costPriceCents == null ? 0 : p.costPriceCents / 100;
    if (field === "price") return (p.sellPriceCents || 0) / 100;
    if (field === "min") return p.reorderMin ?? 0;
    if (field === "max") return p.reorderMax ?? 0;
    return 0;
  }
  function setField(id, field, value) { setDirty((d) => ({ ...d, [id]: { ...(d[id] || {}), [field]: value } })); }
  function getField(p, field) { const v = dirty[p.id]?.[field]; return v !== undefined ? v : baseField(p, field); }
  function isDirty(id, field) { return dirty[id] && Object.prototype.hasOwnProperty.call(dirty[id], field); }
  const dirtyCount = Object.values(dirty).reduce((s, o) => s + Object.keys(o).length, 0);

  async function save() {
    setSaving(true);
    const ids = Object.keys(dirty);
    let ok = 0, failed = 0;
    for (const id of ids) {
      const d = dirty[id], patch = {};
      if ("cost" in d) patch.costPriceCents = Math.max(0, Math.round(Number(d.cost) * 100));
      if ("price" in d) patch.sellPriceCents = Math.max(0, Math.round(Number(d.price) * 100));
      if ("min" in d) patch.reorderMin = Math.max(0, parseInt(d.min, 10) || 0);
      if ("max" in d) patch.reorderMax = Math.max(0, parseInt(d.max, 10) || 0);
      try { await window.OfficeAPI.updateProduct(id, patch); ok++; } catch (e) { failed++; }
    }
    setSaving(false);
    setDirty({});
    setSelected(new Set());
    pushToast({
      kind: failed ? "hold" : "paid",
      icon: failed ? "alert" : "check-circle",
      title: failed ? `${ok} saved, ${failed} failed` : `${ok} product${ok > 1 ? "s" : ""} updated`,
      meta: "Written to Aurora → syncs to the POS",
    });
    loadProducts().then((ps) => setProducts(ps || [])).catch(() => {});
  }

  function bulkRepriceUp() {
    setDirty((d) => {
      const next = { ...d };
      [...selected].forEach((id) => {
        const p = products.find((x) => x.id === id); if (!p) return;
        const base = d[id]?.price !== undefined ? Number(d[id].price) : (p.sellPriceCents || 0) / 100;
        next[id] = { ...(next[id] || {}), price: Number((base * 1.05).toFixed(2)) };
      });
      return next;
    });
    if (selected.size) pushToast({ kind: "hold", icon: "edit", title: "Bulk reprice prepared", meta: `${selected.size} items +5% — review & save` });
  }

  const money2 = (n) => "$" + Number(n || 0).toFixed(2);
  const dispName = (p) => (p.name && p.name !== "-" ? p.name : (p.sku || "(unnamed)"));

  return (
    <div className="off-screen">
      <header className="off-screen-header">
        <div className="off-screen-header-meta">
          <span className="off-screen-eyebrow">Inventory · this pharmacy · live from Aurora</span>
          <h2 className="off-screen-title">Manage stock</h2>
        </div>
        <div className="off-screen-actions">
          <button className="btn btn--ghost btn--sm" onClick={() => setScreen("stockOverview")}>
            <OIcon name="chevron-right" style={{transform:'rotate(180deg)'}}/> Overview
          </button>
          {dirtyCount > 0 && <button className="btn btn--ghost btn--sm" onClick={() => setDirty({})}>Discard {dirtyCount}</button>}
          <button className="btn btn--primary btn--sm" disabled={dirtyCount === 0 || saving} onClick={save}>
            {saving ? <span className="spinner"/> : <OIcon name="check"/>} {saving ? "Saving…" : (dirtyCount > 0 ? `Save ${dirtyCount} edit${dirtyCount > 1 ? "s" : ""}` : "Save")}
          </button>
        </div>
      </header>

      <div className="off-screen-body">
        {err && (
          <div className="off-card" style={{borderColor:'var(--void-border)',background:'var(--void-bg)',color:'var(--void-text)',marginBottom:14,padding:'12px 16px',flexDirection:'row',alignItems:'center',gap:10}}>
            <OIcon name="alert"/> <span>Couldn't load products: {err}</span>
          </div>
        )}

        {selected.size > 0 && (
          <div className="off-bulk-bar">
            <span className="off-bulk-bar-count">{selected.size}</span>
            <span style={{fontSize:'var(--fs-13)'}}>{selected.size === 1 ? "product" : "products"} selected</span>
            <div className="off-bulk-bar-actions">
              <button className="btn btn--ghost btn--sm" style={{color:'#fff'}} onClick={bulkRepriceUp}>Reprice +5%</button>
              <button className="btn btn--ghost btn--sm" style={{color:'#fff'}} onClick={() => setSelected(new Set())}>Clear</button>
            </div>
          </div>
        )}

        <div className="off-table">
          <div className="off-table-toolbar">
            <div className="input-wrap" style={{flex:1, maxWidth:340}}>
              <OIcon name="search" className="input-wrap-icon"/>
              <input className="input" placeholder="Search the catalogue (name or SKU)…" value={q} onChange={(e) => setQ(e.target.value)} style={{height:34, fontSize:'var(--fs-13)'}}/>
            </div>
            <div style={{marginLeft:'auto', fontSize:'var(--fs-12)', color:'var(--text-muted)'}}>
              {loading ? "loading…" : `${products.length} shown${q ? " for “" + q + "”" : " · search to find any of 98,666"} · click a cell to edit`}
            </div>
          </div>

          <div className="off-grid" style={{gridTemplateColumns: "32px minmax(220px,1.6fr) 150px 90px 90px 90px 80px 80px 130px"}}>
            <div className="off-grid-headcell" style={{textAlign:'center'}}>
              <input type="checkbox" checked={selected.size === products.length && products.length > 0} onChange={toggleAll}/>
            </div>
            <div className="off-grid-headcell">Product</div>
            <div className="off-grid-headcell">Category</div>
            <div className="off-grid-headcell" style={{justifyContent:'flex-end'}}>Cost</div>
            <div className="off-grid-headcell" style={{justifyContent:'flex-end'}}>Price</div>
            <div className="off-grid-headcell" style={{justifyContent:'flex-end'}}>Margin %</div>
            <div className="off-grid-headcell" style={{justifyContent:'flex-end'}}>Min</div>
            <div className="off-grid-headcell" style={{justifyContent:'flex-end'}}>Max</div>
            <div className="off-grid-headcell">Supplier</div>

            {products.map((p) => {
              const cost = parseFloat(getField(p, "cost")) || 0;
              const price = parseFloat(getField(p, "price")) || 0;
              const margin = price > 0 ? Math.round(((price - cost) / price) * 100) : 0;
              return (
                <div key={p.id} className="off-grid-row">
                  <div className="off-grid-cell" style={{textAlign:'center'}}>
                    <input type="checkbox" checked={selected.has(p.id)} onChange={() => toggle(p.id)} onClick={(e) => e.stopPropagation()}/>
                  </div>
                  <div className="off-grid-cell off-cell-strong" style={{cursor:'pointer'}} title="Open product" onClick={() => openProduct(p.id)}>
                    {dispName(p)} {p.schedule ? <window.SchedBadge schedule={p.schedule} compact/> : null}
                    <div className="off-cell-mono" style={{marginTop:2, color:'var(--text-subtle)', fontWeight:400}}>{p.sku}</div>
                  </div>
                  <div className="off-grid-cell off-muted">{catMap[p.categoryId] || <span style={{color:'var(--text-subtle)'}}>—</span>}</div>
                  <div className={"off-grid-cell off-cell-edit off-cell-num" + (isDirty(p.id, "cost") ? " off-cell-edit--dirty" : "")}>
                    <input type="text" value={money2(getField(p, "cost"))} onChange={(e) => setField(p.id, "cost", parseFloat(e.target.value.replace(/[^\d.]/g, "")) || 0)}/>
                  </div>
                  <div className={"off-grid-cell off-cell-edit off-cell-num" + (isDirty(p.id, "price") ? " off-cell-edit--dirty" : "")}>
                    <input type="text" value={money2(getField(p, "price"))} onChange={(e) => setField(p.id, "price", parseFloat(e.target.value.replace(/[^\d.]/g, "")) || 0)}/>
                  </div>
                  <div className="off-grid-cell off-cell-num" style={{color: margin < 30 ? 'var(--hold-text)' : margin > 60 ? 'var(--paid-text)' : 'inherit', fontWeight:500}}>{margin}%</div>
                  <div className={"off-grid-cell off-cell-edit off-cell-num" + (isDirty(p.id, "min") ? " off-cell-edit--dirty" : "")}>
                    <input type="number" value={getField(p, "min")} onChange={(e) => setField(p.id, "min", parseInt(e.target.value, 10) || 0)}/>
                  </div>
                  <div className={"off-grid-cell off-cell-edit off-cell-num" + (isDirty(p.id, "max") ? " off-cell-edit--dirty" : "")}>
                    <input type="number" value={getField(p, "max")} onChange={(e) => setField(p.id, "max", parseInt(e.target.value, 10) || 0)}/>
                  </div>
                  <div className="off-grid-cell off-cell-mono" style={{overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{supMap[p.primarySupplierId] || ""}</div>
                </div>
              );
            })}
          </div>
          {!loading && products.length === 0 && <div className="off-empty" style={{padding:30}}><div className="empty-title">No products match “{q}”.</div></div>}
        </div>
      </div>
    </div>
  );
}

window.OFFICE_SCREENS = Object.assign(window.OFFICE_SCREENS || {}, {
  stockOverview: StockOverview,
  manageStock:   ManageStock,
});
