window.FB = window.FB || {};
// v19 驾驶舱 — 异常驱动, 按「今天要做什么」分组
// 严重度四级: 已断 / 来不及 / 要注意 / 正常
// 圆角三级: 区块 2xl · 卡片 xl · 药丸 md
FB.Dashboard = function Dashboard({ lang }) {
  const { useState, useEffect } = React;
  const t = FB.makeT(lang);

  const S  = { s1: "#A81E12", s2: "#C2540A", s3: "#A07A06", s4: "#4A7A1C", g: "#A8A29E", n: "#78716C" };
  const BG = { s1: "#FCEDEA", s2: "#FDF2E8", s3: "#FBF6E4", s4: "#F0F6E8", g: "#F5F5F4" };

  const [runway, setRunway]   = useState(null);
  const [reorder, setReorder] = useState(null);
  const [margin, setMargin]   = useState(null);
  const [exc, setExc]         = useState(null);
  const [snz, setSnz]         = useState(undefined);  // undefined=加载中 · null=表没建 · {}=数据
  const [th, setTh]           = useState(() => Number(FB.draft.get("margin_th", 2)) || 2);
  const [open, setOpen]       = useState({ out: true });
  const [gop, setGop]         = useState({ make: true, buy: true, wait: true, soon: true, order: true, low: true, exc: true });
  const [closing, setClosing] = useState(null);
  const [noteTxt, setNoteTxt] = useState("");
  const [busy, setBusy]       = useState(false);
  const [err, setErr]         = useState("");
  const [copied, setCopied]   = useState("");

  function loadSnooze() { FB.api.marginSnooze().then(setSnz); }
  useEffect(() => {
    FB.api.stockRunway().then(setRunway);
    FB.api.reorder().then(setReorder);
    FB.api.sellMargin().then(setMargin);
    FB.api.openExceptions().then(setExc);
    loadSnooze();
  }, []);

  // ── 小工具 ──
  const num = (v) => Number(v) || 0;
  const rm  = (v) => "RM" + num(v).toFixed(2);
  const qty = (v) => (num(v) < 0 ? "−" : "") + Math.abs(num(v)).toFixed(Math.abs(num(v)) % 1 ? 1 : 0);
  const dua = (v) => (num(v) < 1 ? num(v).toFixed(3) : num(v).toFixed(1));
  const tog = (set, k) => set((o) => ({ ...o, [k]: !o[k] }));
  const chev = (isOpen, size) => (
    <svg width={size || 13} height={size || 13} viewBox="0 0 24 24" fill="none" stroke="currentColor"
      strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"
      className="shrink-0 transition-transform" style={{ transform: isOpen ? "rotate(90deg)" : "none" }}>
      <path d="M9 5l7 7-7 7" />
    </svg>
  );
  function ago(ts) {
    if (!ts) return "";
    const h = Math.floor((Date.now() - new Date(ts).getTime()) / 3600000);
    if (h < 1) return lang === "zh" ? "刚刚" : "just now";
    if (h < 24) return h + (lang === "zh" ? " 小时前" : "h ago");
    return Math.floor(h / 24) + (lang === "zh" ? " 天前" : "d ago");
  }

  // ── 断货: 按「今天要做什么」分三组, 每组独立标尺 ──
  const rw    = runway || [];
  const out   = rw.filter((r) => num(r.qty) <= 0);
  const soon  = rw.filter((r) => num(r.qty) > 0).sort((a, b) => num(a.runway_days) - num(b.runway_days));
  const sevOf = (r) => Math.abs(num(r.runway_days)) + num(r.lt_buf);
  const bySev = (a, b) => sevOf(b) - sevOf(a);
  const gMake = out.filter((r) => !r.supplier_name).sort(bySev);
  const gBuy  = out.filter((r) => r.supplier_name && num(r.lt_buf) < 8).sort(bySev);
  const gWait = out.filter((r) => r.supplier_name && num(r.lt_buf) >= 8).sort(bySev);
  const mxOf  = (l) => (l.length ? Math.max.apply(null, l.map(sevOf)) : 1);
  // 「快断了」也要分线 (2026-07-30 Shanqian: 成品和原料放一起很难分辨)
  // 成品=自己做(没供应商) → 排生产; 原料=要买 → 进订货单。两种动作完全不同, 不能混在一个列表
  const sMake = soon.filter((r) => !r.supplier_name);
  const sBuy  = soon.filter((r) => r.supplier_name);
  const mxSoon = (l) => (l.length ? Math.max.apply(null, l.map((r) => num(r.runway_days))) : 1);

  // ── 订货: 按供应商分组, LT = lt + buffer ──
  const supMap = {};
  (reorder || []).forEach((r) => {
    const s = r.supplier_name || t("selfMade");
    if (!supMap[s]) supMap[s] = { s, lt: num(r.lt) + num(r.buffer), items: [] };
    supMap[s].items.push(r);
  });
  const sups   = Object.keys(supMap).map((k) => supMap[k]).sort((a, b) => b.lt - a.lt);
  const maxLt  = sups.length ? Math.max.apply(null, sups.map((g) => g.lt)) : 1;
  const outSup = {};
  out.forEach((r) => { if (r.supplier_name) outSup[r.supplier_name] = (outSup[r.supplier_name] || 0) + 1; });

  // ── 毛利: 四类各有目标线 (成品 30% · 其余 5–9%) ──
  const MCATS = {
    finished: { label: t("catFinished"), lo: 30, hi: null },
    buy:      { label: t("catBuy"),      lo: 5,  hi: 9 },
    pkg:      { label: t("catPkg"),      lo: 5,  hi: 9 },
    dcs:      { label: t("catDcs"),      lo: 5,  hi: 9 },
  };
  function catKey(m) {
    if (m.cost_source === "finished") return "finished";
    const p = String(m.sell_code || "").replace(/[0-9].*$/, "");
    return p === "PKG" ? "pkg" : p === "DCS" ? "dcs" : "buy";
  }
  function mState(m) {
    const c = MCATS[catKey(m)];
    if (m.margin_pct == null) return "none";
    const v = num(m.margin_pct);
    if (v < c.lo) return "low";
    if (c.hi != null && v > c.hi) return "high";
    return "ok";
  }
  function mGap(m) {
    const c = MCATS[catKey(m)], v = num(m.margin_pct), st = mState(m);
    return st === "low" ? c.lo - v : st === "high" ? v - c.hi : 0;
  }
  function mSuggest(m) {
    const c = MCATS[catKey(m)], st = mState(m);
    const tgt = st === "low" ? c.lo : c.hi;
    return { tgt: tgt, price: num(m.cost_sell_unit) * (1 + tgt / 100) };
  }
  // 忽略后, margin 动超过当时记下的 threshold 才重新出现
  function isMuted(m) {
    if (!snz) return false;
    const s = snz[m.sell_code];
    if (!s) return false;
    if (m.margin_pct == null) return true;
    return Math.abs(num(m.margin_pct) - num(s.snoozed_pct)) <= num(s.threshold);
  }
  const act   = (margin || []).filter((m) => m.active !== false);
  const byGap = (a, b) => mGap(b) - mGap(a);
  const mLow  = act.filter((m) => mState(m) === "low"  && !isMuted(m)).sort(byGap);
  const mHigh = act.filter((m) => mState(m) === "high" && !isMuted(m)).sort(byGap);
  const mNone = act.filter((m) => mState(m) === "none" && !isMuted(m));
  const mMut  = act.filter((m) => isMuted(m));
  const okCats = Object.keys(MCATS).map((k) => ({
    k: k, label: MCATS[k].label, lo: MCATS[k].lo, hi: MCATS[k].hi,
    items: act.filter((m) => catKey(m) === k && mState(m) === "ok").sort((a, b) => num(a.margin_pct) - num(b.margin_pct)),
  })).filter((c) => c.items.length);
  const nOk   = okCats.reduce((a, c) => a + c.items.length, 0);
  const nAdj  = mLow.length + mHigh.length;
  const nExc  = (exc || []).length;

  const segs = [
    [out.length,  S.s1, t("secOut")],
    [soon.length, S.s2, t("secSoon")],
    [sups.length, S.s3, t("secOrder")],
    [nAdj,        S.s4, t("secMargin")],
    [nExc,        S.n,  t("secExc")],
  ].filter((x) => x[0] > 0);
  const TOTAL  = segs.reduce((a, x) => a + x[0], 0);
  const segTot = TOTAL || 1;
  const today  = new Date().toLocaleDateString(lang === "zh" ? "zh-CN" : "en-GB",
    { month: "short", day: "numeric", weekday: "short" });
  const ready  = runway && reorder && margin && exc;

  // header 精华: 15 项今天要处理
  useEffect(() => {
    if (ready && FB.setPageInfo) FB.setPageInfo(TOTAL === 0 ? t("nothingToday") : TOTAL + " " + t("toProcess"));
  }, [ready, TOTAL, lang]);

  // ── 动作 ──
  async function doResolve() {
    if (!closing) return;
    setBusy(true);
    const r = await FB.api.resolveException({ id: closing.id, note: noteTxt || null, resolved_by: "Shanqian" });
    setBusy(false);
    if (!r || r.error) { setErr(t("failed")); return; }
    setClosing(null); setNoteTxt("");
    FB.api.openExceptions().then(setExc);
  }
  async function doMute(m) {
    const r = await FB.api.muteMargin(m.sell_code, num(m.margin_pct), th, "Shanqian");
    if (r && r.error) { setErr(t("saveFail")); return; }
    loadSnooze();
  }
  async function doUnmute(m) {
    const r = await FB.api.unmuteMargin(m.sell_code);
    if (r && r.error) { setErr(t("saveFail")); return; }
    loadSnooze();
  }
  // 「数量 单位 供应商叫法」
  // 只有 FB.PCS_SUPPLIERS(中国)在箱数后附换算量, 其他供应商按 ctn 报就行
  // 换算只用真实字段 (carton_size × unit); 品名里的「50个/包」没有字段, 不去猜
  function orderLine(r, supplier) {
    const n = num(r.reorder_ctn), cs = num(r.carton_size), u = r.unit || "";
    const name = r.alias || r.name_cn || r.code;
    if (!cs) return n + " " + u + " " + name;            // 无箱规: 按需求量直接报
    const needConv = (FB.PCS_SUPPLIERS || []).some((s) => FB.sameSupplier(supplier, s));
    return needConv ? n + " ctn (" + +(n * cs).toFixed(2) + " " + u + ") " + name
                    : n + " ctn " + name;
  }
  function copyOrder(g) {
    const txt = g.s + "\n" + g.items.map((r) => orderLine(r, g.s)).join("\n");
    const done = () => { setCopied(g.s); setTimeout(() => setCopied(""), 1500); };
    if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(txt).then(done, done);
    else done();
  }

  // ── 渲染块 ──
  // 卡片底边 = 严重度条, 不占垂直空间
  function edge(a, b, mx, ca, cb) {
    const A = mx > 0 ? (a / mx) * 100 : 0, B = mx > 0 ? (b / mx) * 100 : 0;
    return (
      <span className="absolute left-0 right-0 bottom-0 flex" style={{ height: 3, background: "#F2F1EF" }}>
        <i className="block h-full" style={{ width: A + "%", background: ca }} />
        <i className="block h-full" style={{ width: B + "%", background: cb, opacity: 0.5 }} />
      </span>
    );
  }
  function shell(key, col, kids, flat) {
    return (
      <div key={key} className={"relative bg-white border border-stone-200 rounded-xl overflow-hidden px-3 pt-2.5 flex flex-col gap-1.5 " + (flat ? "pb-2.5" : "pb-3.5")}
        style={{ borderLeft: "3px solid " + col }}>{kids}</div>
    );
  }
  function pill(txt) {
    return <span className="text-[10.5px] text-stone-500 bg-stone-100 px-1.5 rounded-md shrink-0 whitespace-nowrap">{txt}</span>;
  }

  function cardOut(r, mx) {
    const a = Math.abs(num(r.runway_days)), b = num(r.lt_buf);
    return shell(r.code, S.s1, [
      <div key="a" className="flex items-center gap-2">
        <span className="text-[13.5px] font-semibold text-stone-800 truncate min-w-0">{FB.nm(r, lang)}</span>
        {pill(t("dailyUse") + " " + dua(r.daily_usage) + " " + r.unit)}
        <span className="ml-auto text-[15px] font-bold shrink-0" style={{ color: S.s1 }}>{qty(r.qty)} {r.unit}</span>
      </div>,
      <div key="b" className="flex items-center gap-1.5">
        <span className="text-xs font-bold whitespace-nowrap" style={{ color: S.s1 }}>{t("brokeD")} {a.toFixed(0)}{t("dayU")}</span>
        {b > 0 ? <span className="text-[11px] text-stone-300">＋</span> : null}
        {b > 0 ? <span className="text-xs font-bold whitespace-nowrap" style={{ color: S.s2 }}>{t("waitD")} {b}{t("dayU")}</span> : null}
        <span className="ml-auto text-[10.5px] text-stone-400 shrink-0">{r.supplier_name || t("selfMade")}</span>
      </div>,
      edge(a, b, mx, S.s1, S.s2),
    ]);
  }
  function cardSoon(r, mx) {
    const d = num(r.runway_days);
    return shell(r.code, S.s2, [
      <div key="a" className="flex items-center gap-2">
        <span className="text-[13.5px] font-semibold text-stone-800 truncate min-w-0">{FB.nm(r, lang)}</span>
        {pill(t("dailyUse") + " " + dua(r.daily_usage) + " " + r.unit)}
        <span className="ml-auto text-[15px] font-bold shrink-0" style={{ color: S.s2 }}>{d}{t("dayU")}</span>
      </div>,
      <div key="b" className="flex items-center gap-1.5">
        <span className="text-xs font-bold whitespace-nowrap" style={{ color: S.s2 }}>{t("canLast")} {d}{t("dayU")}</span>
        <span className="text-[11px] text-stone-300">＋</span>
        <span className="text-xs font-bold whitespace-nowrap" style={{ color: S.s3 }}>{t("countIn")} {r.days_to_count}{t("dayU")}</span>
        <span className="ml-auto text-[10.5px] text-stone-400 shrink-0">{r.supplier_name || t("selfMade")}</span>
      </div>,
      edge(d, 0, mx, S.s2, S.s3),
    ]);
  }
  function cardSup(g) {
    // 箱数合计只对有箱规的品有意义 — kg/pkt/tray 的量不能混进去加 (2026-07-29)
    const csItems = g.items.filter((r) => num(r.carton_size) > 0);
    const ctn = csItems.reduce((a, r) => a + num(r.reorder_ctn), 0);
    const oc  = outSup[g.s] || 0;
    const col = g.lt >= 30 ? S.s1 : g.lt >= 8 ? S.s2 : S.s3;
    const isOpen = !!gop["sup_" + g.s];
    return shell(g.s, col, [
      <div key="a" className="flex items-center gap-2">
        <span className="text-[13.5px] font-semibold text-stone-800 truncate min-w-0">{g.s}</span>
        <span className="ml-auto text-[15px] font-bold shrink-0" style={{ color: col }}>LT {g.lt}{t("dayU")}</span>
      </div>,
      <div key="b" className="flex items-center gap-1.5">
        <span className="text-xs font-bold whitespace-nowrap" style={{ color: col }}>{g.items.length} {t("itemU")}{csItems.length === g.items.length && ctn > 0 ? " · " + ctn + " ctn" : ""}</span>
        {oc ? <span className="text-[11px] text-stone-300">＋</span> : null}
        {oc ? <span className="text-xs font-bold whitespace-nowrap" style={{ color: S.s1 }}>{oc} {t("alreadyOut")}</span> : null}
        <span className="ml-auto text-[10.5px] text-stone-400 shrink-0">{t("fastestIn")} {g.lt}{t("dayU")}</span>
      </div>,
      <div key="c" className="border-t border-stone-100 mt-0.5">
        <button onClick={() => tog(setGop, "sup_" + g.s)}
          className="w-full flex items-center gap-1.5 pt-2 pb-0.5 text-[11.5px] text-stone-500">
          {chev(isOpen)}{t("expandDetail")}
        </button>
        {isOpen ? (
          <div>
            {g.items.map((r) => (
              <div key={r.code} className="flex justify-between gap-2.5 text-[12.5px] py-1 border-t border-stone-50 text-stone-800">
                <span className="truncate">{FB.nm(r, lang)}</span>
                {/* 单位跟品项走: 有箱规才是 ctn, 否则 kg 就 kg、pkt 就 pkt (2026-07-29) */}
                <span className="text-stone-500 font-semibold shrink-0">{num(r.reorder_ctn)} {num(r.carton_size) > 0 ? "ctn" : (r.unit || "")}</span>
              </div>
            ))}
            <button onClick={() => copyOrder(g)}
              className="mt-2 mb-1 h-9 w-full rounded-lg border border-stone-200 bg-stone-50 active:bg-stone-100 text-xs font-semibold text-stone-800 flex items-center justify-center gap-1.5">
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
                <path d="M9 9h10v12H9zM5 15V3h10v2" /></svg>
              {copied === g.s ? "✓ " + t("copiedTxt") : t("copyOrderBtn")}
            </button>
          </div>
        ) : null}
      </div>,
      edge(g.lt, 0, maxLt, col, col),
    ]);
  }
  function cardAdj(m, up) {
    const col = up ? S.s1 : S.s3, sg = mSuggest(m);
    const d = Math.abs(sg.price - num(m.price)), gap = mGap(m);
    return shell(m.sell_code, col, [
      <div key="a" className="flex items-center gap-2">
        <span className="text-[13.5px] font-semibold text-stone-800 truncate min-w-0">{m.name_en}</span>
        {pill(t("targetIs") + " " + sg.tgt + "%")}
        <span className="ml-auto text-[15px] font-bold shrink-0" style={{ color: col }}>{num(m.margin_pct).toFixed(1)}%</span>
      </div>,
      <div key="b" className="flex items-center gap-2 bg-stone-50 rounded-lg px-2 py-1.5">
        <span className="text-xs text-stone-500 line-through decoration-stone-300">{rm(m.price)}</span>
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#C4C0BB" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" className="shrink-0">
          <path d="M4 12h15M14 7l5 5-5 5" /></svg>
        <span className="text-[13.5px] font-bold" style={{ color: col }}>{rm(sg.price)}</span>
        <span className="ml-auto text-[11px] font-bold px-1.5 py-0.5 rounded-md shrink-0" style={{ background: up ? BG.s1 : BG.s3, color: col }}>
          {up ? "↑" : "↓"} {d.toFixed(2)}
        </span>
      </div>,
      <div key="c" className="flex items-center gap-1.5">
        <span className="text-xs font-bold whitespace-nowrap" style={{ color: col }}>
          {up ? t("pctLow") : t("pctHigh")} {gap.toFixed(1)} {t("pointsU")}
        </span>
        {gap < 2 ? <span className="text-[10px] font-semibold px-1.5 rounded-md shrink-0" style={{ background: BG.s4, color: S.s4 }}>{t("closeEnough")}</span> : null}
        {snz ? (
          <button onClick={() => doMute(m)}
            className="ml-auto h-6 px-2 rounded-md border border-stone-200 bg-white active:bg-stone-100 text-[11px] font-semibold text-stone-500 flex items-center gap-1 shrink-0">
            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"><path d="M18 6L6 18M6 6l12 12" /></svg>
            {t("notNow")}
          </button>
        ) : <span className="ml-auto text-[10.5px] text-stone-400 shrink-0">{t("costIs")} {rm(m.cost_sell_unit)}</span>}
      </div>,
    ], true);
  }
  function cardNone(m) {
    return shell(m.sell_code, S.g, [
      <div key="a" className="flex items-center gap-2">
        <span className="text-[13.5px] font-semibold text-stone-800 truncate min-w-0">{m.name_en}</span>
        {pill(MCATS[catKey(m)].label)}
        <span className="ml-auto text-[15px] font-bold shrink-0" style={{ color: S.g }}>—</span>
      </div>,
      <div key="b" className="text-[11.5px] font-semibold text-stone-600">{t("mNoCostSub")}</div>,
    ], true);
  }
  function cardExc(e) {
    const col = e.severity === "red" ? S.s1 : e.severity === "orange" ? S.s2 : e.severity === "yellow" ? S.s3 : S.s4;
    return shell(e.id, col, [
      <div key="a" className="flex items-start gap-2">
        <span className="text-[13.5px] font-semibold text-stone-800 leading-snug min-w-0">{e.title}</span>
        <span className="ml-auto text-[10.5px] text-stone-400 shrink-0">{ago(e.last_seen_at)}</span>
      </div>,
      e.suggested_action
        ? <div key="b" className="text-[11.5px] text-stone-600 leading-snug">→ {e.suggested_action}</div> : null,
      <div key="c" className="flex items-center gap-1.5">
        <span className="text-[10px] font-bold px-1.5 py-0.5 rounded-md" style={{ background: BG[e.severity === "red" ? "s1" : e.severity === "orange" ? "s2" : e.severity === "yellow" ? "s3" : "s4"], color: col }}>
          {String(e.severity || "").toUpperCase()}{e.item_code ? " · " + e.item_code : ""}
        </span>
        <button onClick={() => { setClosing(e); setNoteTxt(""); }}
          className="ml-auto h-6 px-2.5 rounded-md border border-stone-200 bg-white active:bg-stone-100 text-[11px] font-semibold text-stone-500 shrink-0">
          ✓ {t("resolvedBtn")}
        </button>
      </div>,
    ], true);
  }

  function cards(list, fn, mx) {
    return <div className="flex flex-col gap-2 px-3 pb-3">{list.map((x) => fn(x, mx))}</div>;
  }
  function grp(key, verb, sub, count, col, body) {
    const isOpen = !!gop[key];
    return (
      <div key={key} className="border-b border-stone-200 last:border-b-0">
        <button onClick={() => tog(setGop, key)} className="w-full flex items-baseline gap-2 px-3 py-3.5 text-left" style={{ background: "#FAFAF9" }}>
          <span className="self-center text-stone-300">{chev(isOpen, 14)}</span>
          <span className="text-[15px] font-bold" style={{ color: col }}>{verb}</span>
          <span className="text-[10.5px] text-stone-400">{sub}</span>
          <span className="ml-auto text-[13px] font-bold shrink-0" style={{ color: col }}>
            {count}<small className="text-[10px] font-semibold ml-1">{t("itemU")}</small>
          </span>
        </button>
        {isOpen ? body : null}
      </div>
    );
  }
  function sec(key, col, bg, title, sub, count, body) {
    const isOpen = !!open[key];
    return (
      <div key={key} className="bg-white border border-stone-200 rounded-2xl overflow-hidden">
        <button onClick={() => tog(setOpen, key)} className="w-full flex items-center gap-2.5 px-3.5 py-3.5 text-left">
          <span className="w-2.5 h-2.5 rounded shrink-0" style={{ background: col }} />
          <span className="min-w-0">
            <span className="block text-sm font-semibold text-stone-800">{title}</span>
            <span className="block text-[11px] text-stone-500">{sub}</span>
          </span>
          <span className="ml-auto text-xs font-semibold px-2.5 py-0.5 rounded-md shrink-0" style={{ background: bg, color: col }}>{count}</span>
          <span className="text-stone-300">{chev(isOpen)}</span>
        </button>
        {isOpen ? <div className="border-t border-stone-200" style={{ background: "#FAFAF9" }}>{body}</div> : null}
      </div>
    );
  }
  const legend = (
    <div className="flex gap-3 flex-wrap px-3 pb-3.5">
      <span className="text-[10.5px] text-stone-500 flex items-center gap-1.5">
        <i className="block rounded-sm" style={{ width: 9, height: 6, background: S.s1 }} />{t("lgBroke")}</span>
      <span className="text-[10.5px] text-stone-500 flex items-center gap-1.5">
        <i className="block rounded-sm" style={{ width: 9, height: 6, background: S.s2, opacity: 0.5 }} />{t("lgWait")}</span>
    </div>
  );

  // ── 毛利区 ──
  const marginBody = (
    <div>
      {grp("low", t("mRaise"), t("mRaiseSub"), mLow.length, S.s1,
        mLow.length ? cards(mLow, (m) => cardAdj(m, true))
          : <p className="py-3.5 text-xs text-stone-400 text-center">{t("allMutedTxt")}</p>)}
      {grp("high", t("mLower"), t("mLowerSub"), mHigh.length, S.s3,
        mHigh.length ? cards(mHigh, (m) => cardAdj(m, false))
          : <p className="py-3.5 text-xs text-stone-400 text-center">{t("allMutedTxt")}</p>)}
      {mNone.length ? grp("none", t("mNoCost"), t("mNoCostSub"), mNone.length, S.n, cards(mNone, cardNone)) : null}
      {grp("ok", t("mOk"), t("mOkSub"), nOk, S.s4,
        <div className="flex flex-col gap-px bg-stone-200 border-y border-stone-200">
          {okCats.map((c) => {
            const isOpen = !!gop["ok_" + c.k];
            return (
              <div key={c.k} className="bg-white px-3 py-2.5">
                <div className="flex items-baseline gap-2">
                  <span className="text-[12.5px] font-semibold text-stone-800">{c.label}</span>
                  <span className="text-[10.5px] text-stone-400">{t("targetIs")} {c.hi == null ? c.lo + "%" : c.lo + "–" + c.hi + "%"}</span>
                  <span className="ml-auto text-[11px] font-bold shrink-0" style={{ color: S.s4 }}>{c.items.length} {t("itemU")}</span>
                </div>
                <button onClick={() => tog(setGop, "ok_" + c.k)} className="flex items-center gap-1.5 pt-1.5 text-[11px] text-stone-500">
                  {chev(isOpen)}{t("expandDetail")}
                </button>
                {isOpen ? c.items.map((m) => (
                  <div key={m.sell_code} className="flex items-center gap-2 text-[12.5px] py-1.5 border-t border-stone-50">
                    <span className="flex-1 min-w-0 truncate text-stone-800">{m.name_en}</span>
                    <span className="w-12 text-right font-bold" style={{ color: S.s4 }}>{num(m.margin_pct).toFixed(1)}%</span>
                  </div>
                )) : null}
              </div>
            );
          })}
        </div>)}
      {snz ? grp("muted", t("mMuted"), t("backWhen") + " " + th + " " + t("pointsBack"), mMut.length, S.g,
        mMut.length ? (
          <div className="flex flex-col gap-2 px-3 pb-3">
            {mMut.map((m) => {
              const s = snz[m.sell_code] || {};
              return (
                <div key={m.sell_code} className="bg-white border border-dashed border-stone-200 rounded-xl px-3 py-2.5 flex items-center gap-2">
                  <span className="flex-1 min-w-0 truncate text-[12.5px] text-stone-500">{m.name_en}</span>
                  <span className="text-[10.5px] text-stone-400 shrink-0">
                    {m.margin_pct == null ? "—" : num(m.margin_pct).toFixed(1) + "%"} · ±{num(s.threshold)}
                  </span>
                  <button onClick={() => doUnmute(m)} className="text-[11px] font-bold px-1.5 py-1 rounded-md shrink-0" style={{ color: S.s2 }}>
                    {t("restoreBtn")}
                  </button>
                </div>
              );
            })}
          </div>
        ) : <p className="py-3.5 text-xs text-stone-400 text-center">{t("noneMuted")}</p>) : null}

      {snz ? (
        <div className="px-3 py-3 border-t border-stone-200 flex items-center gap-2 flex-wrap">
          <span className="text-[11px] text-stone-500">{t("thresholdLb")}</span>
          <span className="flex gap-1 bg-white border border-stone-200 rounded-lg p-0.5">
            {[1, 2, 3, 5].map((v) => (
              <button key={v} onClick={() => { setTh(v); FB.draft.set("margin_th", v); }}
                className={"text-[11px] px-2 py-1 rounded-md " + (th === v ? "text-white font-semibold" : "text-stone-400")}
                style={th === v ? { background: S.s4 } : {}}>±{v}</button>
            ))}
          </span>
        </div>
      ) : (
        <p className="px-3 py-3 text-[11px] text-stone-400 border-t border-stone-200">{t("needTable")}</p>
      )}
      <p className="px-3 pb-3.5 text-[11px] text-stone-500 leading-snug">{t("suggestNote")}</p>
    </div>
  );

  return (
    <div className="max-w-md mx-auto pb-20">
      {/* 顶栏: 一个总数 + 按严重度分段, 比例本身就是信息 */}
      <div className="bg-white px-4 pt-4 pb-3.5 border-b border-stone-200">
        <div className="flex items-baseline justify-between">
          <p className="text-sm text-stone-500 m-0">
            {ready && TOTAL === 0 ? t("nothingToday")
              : <><em className="not-italic text-3xl font-semibold text-stone-800 mr-1 tracking-tight">{ready ? TOTAL : "…"}</em>{t("toProcess")}</>}
          </p>
          <p className="text-[11px] text-stone-400 m-0">{today}</p>
        </div>
        {segs.length ? (
          <>
            <div className="flex gap-0.5 my-3 h-2.5 rounded-md overflow-hidden">
              {segs.map((x, i) => <i key={i} className="block rounded-sm" style={{ width: (x[0] / segTot) * 100 + "%", background: x[1] }} />)}
            </div>
            <div className="flex gap-3 flex-wrap">
              {segs.map((x, i) => (
                <span key={i} className="text-[11px] text-stone-500 flex items-center gap-1.5">
                  <i className="block rounded-sm" style={{ width: 7, height: 7, background: x[1] }} />{x[2]} {x[0]}
                </span>
              ))}
            </div>
          </>
        ) : null}
      </div>

      {err ? (
        <p className="mx-3.5 mt-3 px-3 py-2 rounded-xl text-xs" style={{ background: BG.s1, color: S.s1 }} onClick={() => setErr("")}>{err}</p>
      ) : null}

      <div className="flex flex-col gap-2 px-3.5 pt-3">
        {!ready ? <p className="text-sm text-stone-400 px-1 py-4">{t("loading")}</p> : null}

        {ready && out.length ? sec("out", S.s1, BG.s1, t("secOut"), t("secOutSub"), out.length,
          <div>
            {gMake.length ? grp("make", t("gMake"), t("gMakeSub"), gMake.length, S.s1, cards(gMake, cardOut, mxOf(gMake))) : null}
            {gBuy.length  ? grp("buy",  t("gBuy"),  t("gBuySub"),  gBuy.length,  S.s1, cards(gBuy,  cardOut, mxOf(gBuy)))  : null}
            {gWait.length ? grp("wait", t("gWait"), t("gWaitSub"), gWait.length, S.s1, cards(gWait, cardOut, mxOf(gWait))) : null}
            {legend}
          </div>) : null}

        {ready && soon.length ? sec("soon", S.s2, BG.s2, t("secSoon"), t("secSoonSub"), soon.length,
          <div>
            {sMake.length ? grp("soonmake", t("gMake"), t("gMakeSub"), sMake.length, S.s2, cards(sMake, cardSoon, mxSoon(sMake))) : null}
            {sBuy.length  ? grp("soonbuy",  t("gBuy2"), t("gBuy2Sub"), sBuy.length,  S.s2, cards(sBuy,  cardSoon, mxSoon(sBuy)))  : null}
          </div>) : null}

        {ready && sups.length ? sec("order", S.s3, BG.s3, t("secOrder"),
          (reorder || []).length + " " + t("itemU") + " · " + sups.length + " " + t("supU"), sups.length,
          grp("order", t("gOrder"), t("gOrderSub"), (reorder || []).length, S.s3,
            <div className="flex flex-col gap-2 px-3 pb-3">
              {/* 去订货页: 勾选建单 + WhatsApp 一键复制 (v20.1) */}
              <button onClick={() => { try { window.dispatchEvent(new CustomEvent("fbnav", { detail: "ordering" })); } catch (e) {} }}
                className="h-10 w-full rounded-xl bg-red-700 text-white text-[13px] font-semibold flex items-center justify-center gap-1.5 active:scale-[0.98]">
                <FB.UI.Icon name="ordering" size={15} />{lang === "zh" ? "去建单" : "Create POs"}
              </button>
              {sups.map(cardSup)}
            </div>)) : null}

        {ready && margin ? sec("margin", S.s4, BG.s4, t("secMargin"),
          act.length + " " + t("itemU"), nAdj, marginBody) : null}

        {ready && nExc ? sec("exc", S.n, BG.g, t("secExc"), "", nExc,
          grp("exc", t("secExc"), "", nExc, S.n, cards(exc, cardExc))) : null}
      </div>

      {/* 异常关闭 */}
      {closing ? (
        <div className="fixed inset-0 z-40 flex items-end" style={{ background: "rgba(28,25,23,.45)" }}>
          <div className="bg-white w-full rounded-t-3xl p-4 pb-safe">
            <p className="text-[15px] font-medium text-stone-900 leading-snug">{closing.title}</p>
            <p className="text-xs text-stone-400 mt-1 mb-3">{t("reasonTitle")}</p>
            <input value={noteTxt} onChange={(ev) => setNoteTxt(ev.target.value)} placeholder={t("reasonPh")}
              className="w-full h-11 px-3 rounded-xl border border-stone-300 text-sm mb-3" />
            <div className="flex gap-2">
              <button onClick={() => setClosing(null)} className="px-5 h-12 rounded-xl border border-stone-200 text-stone-500 text-sm">
                {t("cancelBtn")}
              </button>
              <button onClick={doResolve} disabled={busy}
                className="flex-1 h-12 rounded-xl text-white font-medium disabled:opacity-40" style={{ background: S.s1 }}>
                {busy ? "…" : t("confirmClose")}
              </button>
            </div>
          </div>
        </div>
      ) : null}
    </div>
  );
};
