/* global React, Icon, Button, Field, Select, NumInput, Tag, useToast, Textarea, Dialog,
   PRODUCT_MASTER, CellEditor, TableGroupHeader, SubmitConfirmPage,
   DeleteRowBtn, PageHead, SysCheckBanner, EmptyState, SkuToolbar, FormField, FormFieldVal, FormDivider, FormSectionDivider, SectionCard,
   useSubmissionModel, ApprovalStatusBanner, ImportBanner, runImportProgress,
   ShowErrorsProvider, isPrototypeSuperUser, canActAsPurchase, canActAsJoanna */
// Collection Offer L2 — 活動明細頁（草稿／未生效／生效中／已失效／審核中）

const { useState, useMemo, useEffect, useRef } = React;

// ── Column definitions ─────────────────────────────────────────────────────
const cc = (key, label, w, group, opts = {}) => ({ key, label, w, group, ...opts });
const CO_NUM_W = 32;

const CO_SKU_COLS = [
  cc("#",               "#",            CO_NUM_W, null, { stickyNum: true }),
  cc("barcode",         "品號",         160,  "A", { mono: true, sticky2nd: true }),
  cc("name",            "品名",         200,  "A"),
  cc("cost",            "進價（未稅）", 100,  "B", { mono: true }),
  cc("suggestPrice",    "建議售價",     100,  "C", { mono: true }),
  cc("listPrice",       "牌價",          90,  "C", { mono: true }),
  cc("promoPrice",      "促銷售價",     100,  "C", { mono: true }),
  cc("discountedPrice", "折扣後價格",   110,  "C", { mono: true }),
  cc("isNegativeMargin","是否負毛利",     80,  "D"),
  cc("pacing",          "Pacing",        90,  "E", { mono: true }),
  cc("externalId",      "Eats 品號",    120,  "F", { mono: true }),
  cc("redeemQty",       "歷史兌換件數", 110,  "G", { mono: true }),
  cc("itemSpend",       "歷史消費金額", 110,  "G", { mono: true }),
  cc("_action",         "操作",          44, null, { actionCol: true }),
];

const CO_GROUP_LABELS = {
  A: "商品識別", B: "進價", C: "售價",
  D: "負毛利確認", E: "Pacing", F: "Collection Page", G: "歷史兌換",
};

// ── Helpers ────────────────────────────────────────────────────────────────
const coUid = () => "co-r" + Math.random().toString(36).slice(2, 9);

const getWeeklyFriday = (dateStr) => {
  if (!dateStr) return "";
  const d = new Date(dateStr + "T00:00:00");
  if (isNaN(d.getTime())) return "";
  const diff = (5 - d.getDay() + 7) % 7;
  d.setDate(d.getDate() + diff);
  return d.toISOString().slice(0, 10);
};

const calcScheme = (o) => {
  if (o.type === "BxFy") {
    if (!o.buy || !o.forPrice) return "";
    return `${o.buy} 件 $${o.forPrice}`;
  }
  if (!o.spend || !o.save) return "";
  return `$${o.spend} 折 $${o.save}`;
};

const calcUnitPrice = (o) => {
  const buy = +o.buy, fp = +o.forPrice;
  if (!buy || !fp) return null;
  return +(fp / buy).toFixed(2);
};

const calcDiscount = (o) => {
  const sp = +o.spend, sv = +o.save;
  if (!sp || !sv) return null;
  return +((sp - sv) / sp).toFixed(4);
};

const calcDiscountedPrice = (offer, row) => {
  if (offer.type === "BxFy") {
    const buy = +offer.buy, fp = +offer.forPrice;
    if (!buy || !fp) return null;
    return +(fp / buy).toFixed(2);
  } else {
    const sp = +offer.spend, sv = +offer.save, sugg = +(row.suggestPrice || 0);
    if (!sp || !sv || !sugg) return null;
    return +(sugg * (sp - sv) / sp).toFixed(2);
  }
};

const autoFillRow = (barcode, offer) => {
  const master = PRODUCT_MASTER.find(p => p.barcode === barcode);
  if (!master) return null;
  const partial = {
    barcode,
    name:         master.name,
    cost:         master.cost        ?? null,
    suggestPrice: master.suggestPrice ?? null,
    listPrice:    master.listPrice    ?? null,
    promoPrice:   master.promoPrice   ?? null,
    pacing:       master.pacing       ?? null,
    externalId:   master.externalId   ?? "",
    redeemQty:    null,
    itemSpend:    null,
  };
  const dp  = calcDiscountedPrice(offer, partial);
  const neg = dp !== null && partial.cost != null ? +dp < +partial.cost : false;
  return { ...partial, discountedPrice: dp, isNegativeMargin: neg };
};

const newSkuRow = () => ({
  uid: coUid(),
  barcode:         "",
  name:            "",
  cost:            null,
  suggestPrice:    null,
  listPrice:       null,
  promoPrice:      null,
  discountedPrice: null,
  isNegativeMargin: false,
  pacing:          null,
  externalId:      "",
  redeemQty:       null,
  itemSpend:       null,
});

const EMPTY_OFFER = {
  id: "__new__",
  name: "", type: "BxFy",
  buy: "", forPrice: "",
  spend: "", save: "",
  campaignStart: "", campaignEnd: "",
  budget: "", collectionUuid: "",
  applicant: "", createdAt: "", updatedAt: "",
  status: "coDraft",
  items: [],
};

// ── SysCheck logic ─────────────────────────────────────────────────────────
const coSysCheckRow = (row) => {
  if (!row.barcode) return { pass: false, msg: "品號不可為空" };
  const inMaster = PRODUCT_MASTER.find(p => p.barcode === row.barcode);
  if (!inMaster) return { pass: false, msg: `品號 ${row.barcode} 查無商品主檔資料` };
  if (!row.name)  return { pass: false, msg: "品名未帶入，請重新輸入品號" };
  return { pass: true, msg: "✓ 檢查通過" };
};


// ── Double Check View ──────────────────────────────────────────────────────
const CoDoubleCheckView = ({ offer, hasApproval, onBack, onConfirm }) => {
  const typeLabel = offer.type === "BxFy" ? "滿件折" : "滿額折";
  const scheme    = calcScheme(offer);
  return (
    <SubmitConfirmPage
      moduleName="優惠與活動"
      batchName={offer.name || "(未命名)"}
      subtitle="請確認以下活動資訊無誤，確認後將進行系統檢查。"
      pageTitle={hasApproval ? "送審前確認" : "送出前確認"}
      confirmLabel={hasApproval ? "確認送審" : "確認送出"}
      onBack={onBack}
      onConfirm={onConfirm}
    >
      <div className="card" style={{ marginBottom: 12 }}>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px 24px", padding: "16px 20px" }}>
          {[
            ["活動名稱",       offer.name || "—"],
            ["類型",           typeLabel],
            ["活動文案",       scheme || "—"],
            ["預算",           offer.budget ? `NT$${(+offer.budget).toLocaleString("zh-TW")}` : "—"],
            ["活動開始日",     offer.campaignStart || "—"],
            ["活動結束日",     offer.campaignEnd   || "—"],
            ["Collection UUID",offer.collectionUuid || "—"],
          ].map(([lbl, val]) => (
            <div key={lbl}>
              <div style={{ fontSize: 11, color: "var(--fg-3)", marginBottom: 2 }}>{lbl}</div>
              <div style={{ fontSize: 13, color: "var(--fg-1)", fontWeight: 500 }}>{val}</div>
            </div>
          ))}
        </div>
      </div>
      <div className="card">
        <div style={{ padding: "10px 16px", borderBottom: "1px solid var(--border)", fontSize: 13, color: "var(--fg-2)" }}>
          共 <b style={{ color: "var(--fg-1)" }}>{offer.items.length}</b> 筆商品
        </div>
        <div className="tbl-wrap">
          <table className="tbl">
            <thead>
              <tr>
                <th style={{ width: 32,  textAlign: "center" }}>#</th>
                <th style={{ width: 160 }}>品號</th>
                <th>品名</th>
                <th style={{ width: 120 }}>折扣後價格</th>
                <th style={{ width: 80,  textAlign: "center" }}>是否負毛利</th>
              </tr>
            </thead>
            <tbody>
              {offer.items.length === 0 && (
                <tr><td colSpan={5} style={{ textAlign: "center", padding: 24, color: "var(--fg-3)" }}>尚無商品</td></tr>
              )}
              {offer.items.map((row, idx) => (
                <tr key={row.uid || idx}>
                  <td style={{ textAlign: "center", color: "var(--fg-3)" }}>{idx + 1}</td>
                  <td className="mono" style={{ fontSize: 12 }}>{row.barcode || "—"}</td>
                  <td>{row.name || "—"}</td>
                  <td className="mono">
                    {row.discountedPrice != null
                      ? `NT$${(+row.discountedPrice).toLocaleString("zh-TW", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
                      : "—"}
                  </td>
                  <td style={{ textAlign: "center" }}>
                    {row.isNegativeMargin
                      ? <span style={{ fontWeight: 600, color: "var(--fg-1)" }}>是</span>
                      : <span style={{ color: "var(--fg-3)" }}>—</span>}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    </SubmitConfirmPage>
  );
};

// ── SysCheck View ──────────────────────────────────────────────────────────
const CoSysCheckView = ({ offer, hasApproval, onBack, onConfirm }) => {
  const checks     = useMemo(() => offer.items.map(r => coSysCheckRow(r)), [offer.items]);
  const errorCount = checks.filter(c => !c.pass).length;
  const allPass    = offer.items.length > 0 && errorCount === 0;

  return (
    <>
      <PageHead
        crumbs={[
          { label: "優惠與活動" },
          { label: "滿額折／滿件折", onClick: onBack },
          { label: offer.name || "(未命名)", onClick: onBack },
          { label: "系統檢查", current: true },
        ]}
        title="系統檢查"
        actions={<>
          <Button variant="secondary" onClick={onBack}>{hasApproval ? "修正後重新送出" : "修正後重新送出"}</Button>
          <Button variant="primary" icon="send" disabled={!allPass} onClick={onConfirm}>{hasApproval ? "確認送審" : "確認送出"}</Button>
        </>}
      />
      <SysCheckBanner allPass={allPass} errorCount={errorCount} actionLabel={hasApproval ? "送審" : "送出"} />
      <div className="card">
        <div className="tbl-wrap">
          <table className="tbl">
            <thead>
              <tr>
                <th style={{ minWidth: 220 }}>系統檢查提示</th>
                <th style={{ width: 32,  textAlign: "center" }}>#</th>
                <th style={{ width: 160 }}>品號</th>
                <th style={{ minWidth: 200 }}>品名</th>
                <th style={{ width: 120 }}>折扣後價格</th>
              </tr>
            </thead>
            <tbody>
              {offer.items.map((row, idx) => {
                const chk = checks[idx];
                return (
                  <tr key={row.uid || idx}>
                    <td className={chk.pass ? "syschk-pass" : "syschk-fail"}>{chk.msg}</td>
                    <td style={{ textAlign: "center", color: "var(--fg-3)" }}>{idx + 1}</td>
                    <td className="mono" style={{ fontSize: 12 }}>{row.barcode || "—"}</td>
                    <td>{row.name || "—"}</td>
                    <td className="mono">
                      {row.discountedPrice != null
                        ? `NT$${(+row.discountedPrice).toLocaleString("zh-TW", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
                        : "—"}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>
    </>
  );
};

// ── Column field descriptors (validate set dynamically inside component) ───
const CO_FLD = {
  barcode: { key: "barcode", type: "str", label: "品號", mono: true, placeholder: "輸入品號…" },
};

// ── CollectionOfferDetail ──────────────────────────────────────────────────
const CollectionOfferDetail = ({ offer: offerProp, hasApproval, role, currentUser, onBack, onSaveOffer, onToggleStatus, onDelete, onDirtyChange }) => {
  const isNew = !offerProp || offerProp.id === "__new__";

  // Enrich items with uid if missing (seed data doesn't have uid)
  const enrichItems = (items) =>
    (items || []).map(it => it.uid ? it : { ...it, uid: coUid() });

  const [offer, setOffer] = useState(() =>
    isNew
      ? { ...EMPTY_OFFER, applicant: currentUser || "", items: [] }
      : { ...offerProp, items: enrichItems(offerProp.items) }
  );
  const [dirty,        setDirty]        = useState(false);
  const [pageMode,     setPageMode]     = useState("view");
  const [importPhase,  setImportPhase]  = useState(null); // null | "parsing" | "ok"
  const [importStats,  setImportStats]  = useState({ done: 0, total: 0 });
  const fileInputRef = useRef(null);
  const [showFormErrors, setShowFormErrors] = useState(false);
  const { push: toast } = useToast();

  useEffect(() => { onDirtyChange?.(dirty); }, [dirty]);
  useEffect(() => () => onDirtyChange?.(false), []);

  const isApplicant     = !offer?.id || offer.applicant === currentUser || isPrototypeSuperUser(role);
  const hasEditPermission = canActAsPurchase(role);
  const isReviewer        = hasApproval && canActAsJoanna(role);
  const {
    isDraft, isUnderReview, isApproved, isNoReview, isRejected, isReadOnly,
    canEdit, canAddRow, canDeleteRow,
    canSaveDraft, canSubmit, canSave,
    showApprovalUI,
  } = useSubmissionModel(offer, hasApproval, hasEditPermission, isApplicant);

  // 送審條件（prototype 假條件）：任一品項負毛利 → 需人工審核；否則自動/即時生效。
  // ⚠️ 正式系統應改為依「審核流程設定」模組（SEED_FLOWS by formId）的條件判斷，非寫死負毛利。
  const requiresReview = (offer?.items || []).some(i => i.isNegativeMargin);
  const [rejectDialog, setRejectDialog] = useState(false);
  const [rejectReasonInput, setRejectReasonInput] = useState("");

  // ── Dynamic barcode field (duplicate-aware validate) ───────────────────
  const barcodeField = useMemo(() => {
    const cnt = {};
    offer.items.forEach(it => { if (it.barcode) cnt[it.barcode] = (cnt[it.barcode] || 0) + 1; });
    return {
      ...CO_FLD.barcode,
      validate: r => {
        if (!r.barcode) return "必填";
        if (!PRODUCT_MASTER.find(p => p.barcode === r.barcode)) return "查無商品主檔";
        if (cnt[r.barcode] > 1) return "重複品號";
        return null;
      },
    };
  }, [offer.items]);

  // ── Form-level validation ─────────────────────────────────────────────
  const offerFormErrors = useMemo(() => ({
    name:           !offer.name?.trim()                 ? "必填" : null,
    collectionUuid: !offer.collectionUuid?.trim()       ? "必填" : null,
    campaignStart:  !offer.campaignStart                ? "必填" : null,
    campaignEnd:    !offer.campaignEnd                  ? "必填"
                    : (offer.campaignStart && offer.campaignEnd < offer.campaignStart) ? "須晚於活動開始日" : null,
    type:           !offer.type                         ? "必填" : null,
    budget:         !offer.budget || +offer.budget <= 0 ? "必填" : null,
    buy:    offer.type === "BxFy" && (!offer.buy    || +offer.buy    <= 0) ? "必填" : null,
    forPrice: offer.type === "BxFy" && (!offer.forPrice || +offer.forPrice <= 0) ? "必填" : null,
    spend:  offer.type === "SxSy" && (!offer.spend  || +offer.spend  <= 0) ? "必填" : null,
    save:   offer.type === "SxSy"
            ? (!offer.save || +offer.save <= 0 ? "必填"
               : +offer.save >= +offer.spend   ? "須小於滿額門檻" : null)
            : null,
  }), [offer]);
  const hasFormErr = Object.values(offerFormErrors).some(Boolean);

  // ── Offer-level field patcher ──────────────────────────────────────────
  const patchOffer = (key, value) => {
    setDirty(true);
    setOffer(o => {
      const next = { ...o, [key]: value };
      if (key === "type") {
        // Clear type-specific fields on type switch
        next.buy = ""; next.forPrice = "";
        next.spend = ""; next.save = "";
      }
      // Recompute formula fields when type-specific inputs change
      if (["type", "buy", "forPrice", "spend", "save"].includes(key)) {
        const items = next.items.map(row => {
          const dp  = calcDiscountedPrice(next, row);
          const neg = dp !== null && row.cost != null ? +dp < +row.cost : false;
          return { ...row, discountedPrice: dp, isNegativeMargin: neg };
        });
        return { ...next, items };
      }
      return next;
    });
  };

  // ── Item patching ──────────────────────────────────────────────────────
  const patchItem = (uid, patch) => {
    setDirty(true);
    setOffer(o => ({
      ...o,
      items: o.items.map(it => it.uid === uid ? { ...it, ...patch } : it),
    }));
  };

  const handleBarcodeCommit = (uid, barcode) => {
    const filled = autoFillRow(barcode, offer);
    if (filled) {
      patchItem(uid, filled);
    } else {
      patchItem(uid, {
        barcode, name: "", cost: null, suggestPrice: null, listPrice: null,
        promoPrice: null, discountedPrice: null, isNegativeMargin: false,
        pacing: null, externalId: "",
      });
    }
  };

  const addRow = () => {
    setDirty(true);
    setOffer(o => ({ ...o, items: [...o.items, newSkuRow()] }));
  };

  const deleteRow = (uid) => {
    setDirty(true);
    setOffer(o => ({ ...o, items: o.items.filter(it => it.uid !== uid) }));
  };

  // ── Excel import simulation ────────────────────────────────────────────
  const _runFakeImport = () => {
    const total = 6;
    const existingBarcodes = new Set(offer.items.map(it => it.barcode));
    const currentOffer = offer;
    setImportPhase("parsing");
    setImportStats({ done: 0, total });
    let done = 0;
    const tick = () => {
      done += 1;
      setImportStats(s => ({ ...s, done }));
      if (done < total) {
        setTimeout(tick, 110);
      } else {
        const candidates = PRODUCT_MASTER.filter(p => !existingBarcodes.has(p.barcode)).slice(0, total);
        const newRows = candidates.map(master => {
          const filled = autoFillRow(master.barcode, currentOffer);
          return filled ? { ...filled, uid: coUid() } : { ...newSkuRow(), uid: coUid() };
        });
        setOffer(o => ({ ...o, items: [...o.items, ...newRows] }));
        setDirty(true);
        setImportStats({ done: total, total });
        setImportPhase("ok");
        toast({ kind: "success", msg: `已匯入 ${newRows.length} 筆品項` });
      }
    };
    setTimeout(tick, 200);
  };

  // ── Save / submit handlers ────────────────────────────────────────────
  const handleSaveDraft = () => {
    if (!offer.name?.trim()) {
      setShowFormErrors(true);
      toast({ kind: "warning", msg: "請至少填寫活動名稱才能儲存草稿" });
      return;
    }
    // 已退回的 offer 儲存草稿時保留 coRejected（連同退回原因 banner），直到重新送出
    onSaveOffer({ ...offer, status: isRejected ? "coRejected" : "coDraft" }, isNew);
    setDirty(false);
    toast({ kind: "success", msg: "草稿已儲存" });
  };

  const handleSave = () => {
    setShowFormErrors(true);
    if (hasFormErr) {
      toast({ kind: "warning", msg: "請填寫完整活動資訊後再儲存" });
      return;
    }
    // 編輯已通過 / 已退回的 offer：命中送審條件 → 重新進入審核；否則自動放行（無需審核）生效
    const goReview = hasApproval && requiresReview;
    const updated = { ...offer, status: goReview ? "coUnderReview" : "coNoReview", rejectReason: "" };
    onSaveOffer(updated, isNew);
    setOffer(updated);
    setDirty(false);
    toast(goReview
      ? { kind: "warning", msg: `「${offer.name}」變更觸發審核條件，已送審，待審核` }
      : { kind: "success", msg: "變更已儲存並生效（無需審核）" });
  };

  const { showErrors, submitGuard } = useFormValidation(offer.items, [barcodeField], "送出");
  const handleSend = () => {
    setShowFormErrors(true);
    if (hasFormErr) {
      toast({ kind: "warning", msg: "請填寫完整活動資訊後再送出" });
      return;
    }
    submitGuard(() => setPageMode("doubleCheck"));
  };

  const handleDoubleCheckConfirm = () => setPageMode("sysCheck");

  const handleSysCheckConfirm = () => {
    // 送審條件判斷：有審核流程且命中條件 → 進入人工審核；否則自動放行（無需審核）生效
    const goReview = hasApproval && requiresReview;
    const updated = { ...offer, status: goReview ? "coUnderReview" : "coNoReview", rejectReason: "" };
    const msg = goReview ? `「${offer.name}」已送審，待審核` : `「${offer.name}」已提交生效（無需審核）`;
    onSaveOffer(updated, isNew);
    setDirty(false);
    setOffer(updated);
    setPageMode("view");
    toast({ kind: "success", msg });
  };

  const handleApprove = () => {
    const updated = { ...offer, status: "coApproved", rejectReason: "" };
    onSaveOffer(updated, false);
    setOffer(updated);
    toast({ kind: "success", msg: `「${offer.name}」已審核通過` });
    onBack();
  };

  const handleReject = () => {
    const updated = { ...offer, status: "coRejected", rejectReason: rejectReasonInput.trim() };
    onSaveOffer(updated, false);
    setOffer(updated);
    setRejectDialog(false);
    setRejectReasonInput("");
    toast({ kind: "warning", msg: `「${offer.name}」已退回，申請人需修正後重新送出` });
    onBack();
  };

  // ── Page actions ───────────────────────────────────────────────────────
  const importBtns = canEdit && !isUnderReview ? (<>
    <Button variant="default" icon="download" onClick={() => toast({ kind: "success", msg: "範本下載中…" })}>下載匯入格式</Button>
    <Button variant="default" icon="upload" onClick={() => fileInputRef.current?.click()}>匯入 Excel</Button>
  </>) : null;

  const renderActions = () => {
    if (isReviewer && isUnderReview) return (<>
      <Button variant="secondary" onClick={onBack}>返回</Button>
      <Button variant="danger" onClick={() => setRejectDialog(true)}>退回</Button>
      <Button variant="primary" onClick={handleApprove}>審核通過</Button>
    </>);
    if (isReadOnly || isUnderReview) return (
      <Button variant="secondary" onClick={onBack}>返回</Button>
    );
    if (isDraft || isRejected) return (<>
      {importBtns}
      <Button variant="default" disabled={!canSaveDraft} onClick={handleSaveDraft}>儲存草稿</Button>
      <Button variant="primary" icon="send" disabled={offer.items.length === 0} onClick={handleSend}>{isRejected ? "重新送出" : "送出"}</Button>
    </>);
    if (isApproved || isNoReview) return (<>
      {importBtns}
      <Button variant="primary" onClick={handleSave}>儲存</Button>
    </>);
    return null;
  };

  // ── Cell rendering ─────────────────────────────────────────────────────
  const renderCell = (col, row, idx) => {
    const k = col.key;

    if (k === "#") return (
      <td key={k} style={{
        textAlign: "center", color: "var(--fg-3)",
        position: "sticky", left: 0, background: "var(--bg-surface)", zIndex: 1, padding: "0 4px",
      }}>
        {idx + 1}
      </td>
    );

    if (k === "barcode") return (
      <td key={k} style={{
        position: "sticky", left: CO_NUM_W, background: "var(--bg-surface)", zIndex: 1,
        boxShadow: "2px 0 4px rgba(0,0,0,.06)",
      }}>
        {canEdit
          ? <CellEditor alwaysOn
                field={barcodeField}
                value={row.barcode} row={row}
                onCommit={val => handleBarcodeCommit(row.uid, val)} />
          : <span className="mono" style={{ fontSize: 12, padding: "0 8px" }}>
              {row.barcode || <span style={{ color: "var(--fg-4)" }}>—</span>}
            </span>
        }
      </td>
    );

    if (k === "_action") return (
      <td key={k} style={{ textAlign: "center", padding: "0 4px", verticalAlign: "middle" }}>
        {canDeleteRow && <DeleteRowBtn onClick={() => deleteRow(row.uid)} />}
      </td>
    );

    if (k === "isNegativeMargin") return (
      <td key={k} style={{ textAlign: "center" }}>
        {row.isNegativeMargin
          ? <span style={{ fontWeight: 600, color: "var(--fg-1)" }}>是</span>
          : <span style={{ color: "var(--fg-3)" }}>—</span>}
      </td>
    );

    if (k === "name") return (
      <td key={k}>{row.name || <span style={{ color: "var(--fg-4)" }}>—</span>}</td>
    );

    // Money columns — NT$ with 2 decimal places
    if (["cost", "suggestPrice", "listPrice", "promoPrice", "discountedPrice"].includes(k)) {
      const v = row[k];
      if (v == null) return <td key={k} style={{ color: "var(--fg-4)", fontFamily: "var(--font-mono)" }}>—</td>;
      return (
        <td key={k} className="mono" style={{ color: "var(--fg-2)" }}>
          NT${(+v).toLocaleString("zh-TW", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
        </td>
      );
    }

    // Decimal number columns
    if (k === "pacing") {
      const v = row.pacing;
      if (v == null) return <td key={k} style={{ color: "var(--fg-4)" }}>—</td>;
      return <td key={k} className="mono" style={{ color: "var(--fg-2)" }}>{(+v).toFixed(2)}</td>;
    }

    // Generic readonly
    const v = row[k];
    if (v == null || v === "") return <td key={k} style={{ color: "var(--fg-4)" }}>—</td>;
    return (
      <td key={k} className={col.mono ? "mono" : ""} style={{ color: "var(--fg-2)" }}>{v}</td>
    );
  };

  // ── Transient page modes ───────────────────────────────────────────────
  if (pageMode === "doubleCheck") {
    return <CoDoubleCheckView offer={offer} hasApproval={hasApproval} onBack={() => setPageMode("view")} onConfirm={handleDoubleCheckConfirm} />;
  }
  if (pageMode === "sysCheck") {
    return <CoSysCheckView offer={offer} hasApproval={hasApproval} onBack={() => setPageMode("view")} onConfirm={handleSysCheckConfirm} />;
  }

  // ── Main view ──────────────────────────────────────────────────────────
  const scheme      = calcScheme(offer);
  const unitPrice   = calcUnitPrice(offer);
  const discount    = calcDiscount(offer);
  const weeklyFriday = getWeeklyFriday(offer.campaignStart);

  const cardTypeLabel = offer.type === "BxFy" ? "滿件折" : offer.type === "SxSy" ? "滿額折" : "—";
  const cardPeriod = offer.campaignStart && offer.campaignEnd ? `${offer.campaignStart} ~ ${offer.campaignEnd}` : "—";
  const cardSummary = `${offer.name || "（未命名）"} · ${cardTypeLabel} · ${cardPeriod}`;

  const activeCols = canDeleteRow ? CO_SKU_COLS : CO_SKU_COLS.filter(c => !c.actionCol);
  const dataCols   = activeCols.filter(c => !c.stickyNum && !c.actionCol);
  const actionCol  = canDeleteRow ? CO_SKU_COLS.find(c => c.actionCol) : null;
  const totalWidth = activeCols.reduce((s, c) => s + c.w, 0);

  const pageTitle = isNew ? "新增活動" : (offer.name || "(未命名)");

  return (
    <>
      <PageHead
        crumbs={[
          { label: "優惠與活動" },
          { label: "滿額折／滿件折", onClick: onBack },
          { label: pageTitle, current: true },
        ]}
        title={
          <span style={{ display: "flex", alignItems: "center", gap: 8 }}>
            {pageTitle}
            {!isNew && showApprovalUI && <Tag status={offer.status} />}
          </span>
        }
        dirty={dirty}
        actions={renderActions()}
      />

      <input
        ref={fileInputRef}
        type="file"
        accept=".xlsx,.xls,.csv"
        style={{ display: "none" }}
        onChange={(e) => {
          if (!e.target.files?.[0]) return;
          e.target.value = "";
          _runFakeImport();
        }}
      />

      {showApprovalUI && isUnderReview && !isReviewer && <ApprovalStatusBanner />}
      {showApprovalUI && isRejected && (
        <ApprovalStatusBanner variant="rejected"
          msg={`此活動已被退回：${offer.rejectReason || "（未填寫退回原因）"}`} />
      )}

      {importPhase && (
        <ImportBanner
          phase={importPhase}
          total={importStats.total}
          done={importStats.done}
          onClose={() => setImportPhase(null)}
        />
      )}

      {/* ── 活動主檔 ── */}
      <SectionCard title="活動主檔" summary={cardSummary} defaultOpen={canEdit}>
        {/* Info strip */}
        <div className="form-grid">

          {/* A. 活動資訊 */}
          <FormSectionDivider badge="A" label="活動資訊" first />

          {/* Row 1: 活動名稱 | Collection UUID */}
          <FormField label="活動名稱" required error={showFormErrors ? offerFormErrors.name : null}>
            {canEdit
              ? <Field value={offer.name} error={showFormErrors && !!offerFormErrors.name} onChange={e => patchOffer("name", e.target.value)} placeholder="10–15 字內" />
              : <FormFieldVal>{offer.name || "—"}</FormFieldVal>}
          </FormField>
          <FormField label="Collection UUID" required error={showFormErrors ? offerFormErrors.collectionUuid : null}>
            {canEdit
              ? <Field value={offer.collectionUuid} error={showFormErrors && !!offerFormErrors.collectionUuid} onChange={e => patchOffer("collectionUuid", e.target.value)} placeholder="從他系統取得後貼入" />
              : <FormFieldVal mono>{offer.collectionUuid || "—"}</FormFieldVal>}
          </FormField>

          {/* Row 2: 活動開始日 | 活動結束日 */}
          <FormField label="活動開始日" required error={showFormErrors ? offerFormErrors.campaignStart : null}>
            {canEdit
              ? <Field type="date" value={offer.campaignStart} error={showFormErrors && !!offerFormErrors.campaignStart} onChange={e => patchOffer("campaignStart", e.target.value)} />
              : <FormFieldVal mono>{offer.campaignStart || "—"}</FormFieldVal>}
          </FormField>
          <FormField label="活動結束日" required error={showFormErrors ? offerFormErrors.campaignEnd : null}>
            {canEdit
              ? <Field type="date" value={offer.campaignEnd} error={showFormErrors && !!offerFormErrors.campaignEnd} onChange={e => patchOffer("campaignEnd", e.target.value)} />
              : <FormFieldVal mono>{offer.campaignEnd || "—"}</FormFieldVal>}
          </FormField>

          {/* Row 3: 菜單更新日 🔒 | 更新時間 🔒 */}
          <FormField label="菜單更新日" locked hint="菜單將固定於每週五更新">
            <FormFieldVal mono>{weeklyFriday || "—"}</FormFieldVal>
          </FormField>
          <FormField label="更新時間" locked>
            <FormFieldVal mono>{offer.updatedAt || "—"}</FormFieldVal>
          </FormField>

          {/* Row 4: 建立日期 🔒 | 申請人 🔒 */}
          <FormField label="建立日期" locked>
            <FormFieldVal mono>{offer.createdAt || "—"}</FormFieldVal>
          </FormField>
          <FormField label="申請人" locked>
            <FormFieldVal>{offer.applicant || "—"}</FormFieldVal>
          </FormField>

          {/* Row 5: 類型 | 預算 */}
          <FormField label="類型" required error={showFormErrors ? offerFormErrors.type : null}>
            {canEdit
              ? <Select value={offer.type} error={showFormErrors && !!offerFormErrors.type} onChange={e => patchOffer("type", e.target.value)}>
                  <option value="" disabled>請選擇類型</option>
                  <option value="BxFy">滿件折（BxFy）</option>
                  <option value="SxSy">滿額折（SxSy）</option>
                </Select>
              : <FormFieldVal>{offer.type === "BxFy" ? "滿件折" : "滿額折"}</FormFieldVal>}
          </FormField>
          <FormField label="預算" required error={showFormErrors ? offerFormErrors.budget : null}>
            {canEdit
              ? <NumInput value={offer.budget} error={showFormErrors && !!offerFormErrors.budget} onChange={e => patchOffer("budget", e.target.value)} unit="NT$" placeholder="0" />
              : <FormFieldVal mono>
                  {offer.budget ? `NT$${(+offer.budget).toLocaleString("zh-TW")}` : "—"}
                </FormFieldVal>}
          </FormField>

          {/* B. 折扣方案 */}
          {offer.type && <FormSectionDivider badge="B" label="折扣方案" />}

          {/* BxFy 專用欄位 */}
          {offer.type === "BxFy" && (<>
            <FormField label="購買件數" required error={showFormErrors ? offerFormErrors.buy : null}>
              {canEdit
                ? <NumInput value={offer.buy} error={showFormErrors && !!offerFormErrors.buy} onChange={e => patchOffer("buy", e.target.value)} placeholder="例：2" />
                : <FormFieldVal mono>{offer.buy || "—"}</FormFieldVal>}
            </FormField>
            <FormField label="優惠總價" required error={showFormErrors ? offerFormErrors.forPrice : null}>
              {canEdit
                ? <NumInput value={offer.forPrice} error={showFormErrors && !!offerFormErrors.forPrice} onChange={e => patchOffer("forPrice", e.target.value)} unit="NT$" placeholder="例：58" />
                : <FormFieldVal mono>{offer.forPrice ? `NT$${offer.forPrice}` : "—"}</FormFieldVal>}
            </FormField>
            <FormField label="活動文案" locked>
              <FormFieldVal>{scheme || "—"}</FormFieldVal>
            </FormField>
            <FormField label="折算單件價" locked>
              <FormFieldVal mono>{unitPrice != null ? `NT$${unitPrice}` : "—"}</FormFieldVal>
            </FormField>
          </>)}

          {/* SxSy 專用欄位 */}
          {offer.type === "SxSy" && (<>
            <FormField label="滿額門檻" required error={showFormErrors ? offerFormErrors.spend : null}>
              {canEdit
                ? <NumInput value={offer.spend} error={showFormErrors && !!offerFormErrors.spend} onChange={e => patchOffer("spend", e.target.value)} unit="NT$" placeholder="例：399" />
                : <FormFieldVal mono>{offer.spend ? `NT$${offer.spend}` : "—"}</FormFieldVal>}
            </FormField>
            <FormField label="折抵金額" required error={showFormErrors ? offerFormErrors.save : null}>
              {canEdit
                ? <NumInput value={offer.save} error={showFormErrors && !!offerFormErrors.save} onChange={e => patchOffer("save", e.target.value)} unit="NT$" placeholder="例：30" />
                : <FormFieldVal mono>{offer.save ? `NT$${offer.save}` : "—"}</FormFieldVal>}
            </FormField>
            <FormField label="活動文案" locked>
              <FormFieldVal>{scheme || "—"}</FormFieldVal>
            </FormField>
            <FormField label="折扣率" locked>
              <FormFieldVal mono>{discount != null ? discount : "—"}</FormFieldVal>
            </FormField>
          </>)}
        </div>{/* form-grid */}
      </SectionCard>

      {/* ── SKU 商品子表 ── */}
      <div className="card">
        <SkuToolbar>
          <span style={{ fontSize: 13, color: "var(--fg-2)", flexShrink: 0 }}>
            共 <b style={{ color: "var(--fg-1)" }}>{offer.items.length}</b> 筆
          </span>
          {canAddRow && (
            <Button variant="default" size="sm" icon="plus" onClick={addRow}
              style={{ marginLeft: "auto" }}>新增品項</Button>
          )}
        </SkuToolbar>

        {offer.items.length === 0 ? (
          <EmptyState
            icon="tag"
            title="尚無商品"
            desc={canAddRow ? "點下方「新增品項」開始輸入品號。" : "此活動目前沒有商品。"}
          >
            {canAddRow && (
              <Button variant="primary" icon="plus" onClick={addRow}>新增品項</Button>
            )}
          </EmptyState>
        ) : (
          <ShowErrorsProvider value={showErrors}>
          <>
            <div className="tbl-wrap">
              <table className="tbl" style={{ borderCollapse: "separate", borderSpacing: 0, minWidth: totalWidth }}>
                <thead>
                  <TableGroupHeader
                    cols={dataCols}
                    groupLabels={CO_GROUP_LABELS}
                    leadingSlot={
                      <th rowSpan={2} style={{
                        width: CO_NUM_W, minWidth: CO_NUM_W, padding: "0 4px",
                        position: "sticky", left: 0, zIndex: 3, background: "var(--gray-50)",
                      }}>#</th>
                    }
                    actionCol={actionCol}
                  />
                  <tr>
                    {dataCols.map(c => {
                      const s = { width: c.w, minWidth: c.w };
                      if (c.sticky2nd) return (
                        <th key={c.key} style={{ ...s, position: "sticky", left: CO_NUM_W, zIndex: 2, boxShadow: "2px 0 4px rgba(0,0,0,.06)" }}>
                          {c.label}
                        </th>
                      );
                      return <th key={c.key} style={s}>{c.label}</th>;
                    })}
                  </tr>
                </thead>
                <tbody>
                  {offer.items.map((row, idx) => (
                    <tr key={row.uid}>
                      {activeCols.map(c => renderCell(c, row, idx))}
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            {canAddRow && (
              <button className="tbl-add-row" onClick={addRow}>
                <Icon name="plus" size={16} /> 新增品項
              </button>
            )}
          </>
          </ShowErrorsProvider>
        )}
      </div>

      <Dialog
        open={rejectDialog}
        title="退回此活動"
        onClose={() => { setRejectDialog(false); setRejectReasonInput(""); }}
        footer={<>
          <Button variant="default" onClick={() => { setRejectDialog(false); setRejectReasonInput(""); }}>取消</Button>
          <Button variant="danger" disabled={!rejectReasonInput.trim()} onClick={handleReject}>確認退回</Button>
        </>}
      >
        <FormField label="退回原因" required>
          <Textarea
            variant="reject"
            rows={3}
            value={rejectReasonInput}
            onChange={e => setRejectReasonInput(e.target.value)}
            placeholder="說明需修正的項目，申請人將依此修正後重新送出"
            autoFocus
          />
        </FormField>
      </Dialog>
    </>
  );
};

Object.assign(window, { CollectionOfferDetail });
