/* global React, Icon, Button, Field, Tag, useToast,
   PRODUCT_MASTER, CellEditor, CellDisplay, TableGroupHeader, SubmitConfirmPage,
   DeleteRowBtn, PageHead, ImportBanner, EmptyState, SkuToolbar, ModeToggle,
   FormField, FormFieldVal, MetaCard, PortalSelect,
   useSubmissionModel, ApprovalStatusBanner,
   EO_ALL_COLS, EO_COMPACT_KEYS, EO_GROUP_LABELS, EO_NUM_W,
   DISCOUNT_TYPE_OPTIONS, isFriday, isThursday,
   ShowErrorsProvider, ErrWrap, ErrIconTip, useFormValidation, RowErrTip, isPrototypeSuperUser, canActAsPurchase, canActAsJoanna */
// Eats Offer L2 — 單品優惠批次明細

const { useState, useMemo, useCallback } = React;

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

const calcFinalPrice = (row) => row.u1PromoPrice || row.promoPrice || null;

const calcUnitPrice = (row) => {
  const fp = +calcFinalPrice(row), bq = +(row.buyQty || 0);
  if (!fp || !bq) return null;
  return +(fp / bq).toFixed(2);
};

const calcSetPrice = (row) => {
  const fp = +calcFinalPrice(row), bq = +(row.buyQty || 0);
  if (!fp || !bq) return null;
  return +(fp * bq).toFixed(2);
};

const calcMarginPct = (row) => {
  const fp = +calcFinalPrice(row), cost = +(row.cost || 0);
  if (!fp) return null;
  return +((fp - cost) / fp).toFixed(4);
};

const calcDiscountPct = (row) => {
  const fp = +calcFinalPrice(row), sugg = +(row.suggestPrice || 0);
  if (!fp || !sugg) return null;
  return +(1 - fp / sugg).toFixed(4);
};

const calcPromoCheck = (row) => {
  const fp = +calcFinalPrice(row), list = +(row.listPrice || 0);
  if (!fp || !list) return null;
  return fp < list ? "PASS" : "FAIL";
};

const calcPromoVsList = (row) => {
  const fp = +calcFinalPrice(row), sugg = +(row.suggestPrice || 0);
  if (!fp || !sugg) return null;
  return fp < sugg ? "PASS" : "FAIL";
};

const calcPromoDepth = (row) => {
  const fp = +calcFinalPrice(row), list = +(row.listPrice || 0);
  if (!fp || !list) return null;
  return +((list - fp) / list).toFixed(4);
};

const recalcRow = (row) => {
  const finalPrice  = calcFinalPrice(row);
  const marginPct   = calcMarginPct(row);
  const discountPct = calcDiscountPct(row);
  const promoCheck  = calcPromoCheck(row);
  const promoVsList = calcPromoVsList(row);
  const promoDepth  = calcPromoDepth(row);
  const unitPrice   = calcUnitPrice(row);
  const setPrice    = calcSetPrice(row);
  const bogoType    = row.discountType === "BOGO" || row.discountType === "Volume Discount";
  return {
    ...row,
    finalPrice,
    marginPct,
    discountPct,
    promoCheck,
    promoVsList,
    promoDepth,
    unitPrice:    bogoType ? unitPrice : null,
    setPrice:     bogoType ? setPrice  : null,
    doEatsOffer:  "YES",
    buyQty:       bogoType ? row.buyQty    : null,
    discountQty:  bogoType ? row.discountQty : null,
  };
};

const autoFillRow = (barcode, existingRow = {}) => {
  const master = PRODUCT_MASTER.find(p => p.barcode === barcode);
  if (!master) return null;
  const partial = {
    ...existingRow,
    barcode,
    name:         master.name,
    mainCat:      master.mainCat      ?? "",
    subCat:       master.subCat       ?? "",
    cost:         master.cost         ?? null,
    listPrice:    master.listPrice     ?? null,
    suggestPrice: master.suggestPrice  ?? null,
    vat:          master.taxRate       ?? "",
  };
  return recalcRow(partial);
};

const newEoRow = (applicant) => recalcRow({
  uid: eoUid(),
  fillBy: applicant || "",
  barcode: "", name: "", mainCat: "", subCat: "",
  cost: null, newCost: null, promoStart: "", promoEnd: "",
  listPrice: null, suggestPrice: null,
  promoPrice: null, u1PromoPrice: null,
  discountType: "", buyQty: null, discountQty: null,
  menuPromoStart: "", menuPromoEnd: "", purchaseLimit: null,
  autoListWhitelist: false,
  marketPrice: null, poc: "", vat: "",
  costInErp: null, timeCheck: false,
});

const EMPTY_OFFER = {
  id: "__new__",
  name: "",
  applicant: "", createdAt: "", updatedAt: "",
  status: "eoDraft",
  items: [],
};

// ── SysCheck logic ─────────────────────────────────────────────────────────
const eoSysCheckRow = (row) => {
  if (!row.barcode) return { pass: false, msg: "品號不可為空" };
  if (!PRODUCT_MASTER.find(p => p.barcode === row.barcode))
    return { pass: false, msg: `品號 ${row.barcode} 查無商品主檔資料` };
  if (!row.promoPrice) return { pass: false, msg: "促銷售價不可為空" };
  if (!row.discountType) return { pass: false, msg: "折扣種類不可為空" };
  if (row.promoCheck === "FAIL") return { pass: false, msg: "促銷售價需低於牌價（真促銷價檢查 FAIL）" };
  if (row.promoVsList === "FAIL") return { pass: false, msg: "促銷售價需低於建議售價（促銷<>牌價 FAIL）" };
  if (row.menuPromoStart && !isFriday(row.menuPromoStart))
    return { pass: false, msg: `菜單促銷開始（${row.menuPromoStart}）必須為週五` };
  if (row.menuPromoEnd && !isThursday(row.menuPromoEnd))
    return { pass: false, msg: `菜單促銷結束（${row.menuPromoEnd}）必須為週四` };
  return { pass: true, msg: "✓ 檢查通過" };
};

// ── Double Check (with inline sysCheck) ───────────────────────────────────
const EoDoubleCheckView = ({ offer, onBack, onConfirm }) => {
  const [sysChecked, setSysChecked] = useState(false);
  const items = offer.items;

  const checks        = useMemo(() => items.map(r => eoSysCheckRow(r)), [items]);
  const errCount      = checks.filter(c => !c.pass).length;
  const allPass       = items.length > 0 && errCount === 0;
  const requiresReview = items.some(i => (i.marginPct ?? 1) < 0.20);

  const handleConfirm = () => {
    setSysChecked(true);
    if (allPass) onConfirm();
  };

  const handleBack = () => {
    if (sysChecked && !allPass) {
      const errMap = {};
      items.forEach((r, i) => { if (!checks[i].pass) errMap[r.uid || i] = checks[i].msg; });
      onBack(errMap);
    } else {
      onBack(null);
    }
  };

  return (
    <SubmitConfirmPage
      moduleName="促銷活動"
      batchName={offer.name || "(未命名)"}
      subtitle={sysChecked && !allPass
        ? "系統檢查未通過，請修正後重新送出。"
        : "請確認以下批次內容無誤，確認後將進行系統檢查並送審。"
      }
      pageTitle="送審前確認"
      confirmLabel="確認送審"
      onBack={handleBack}
      onConfirm={handleConfirm}
      confirmDisabled={sysChecked && !allPass}
    >
      {sysChecked && !allPass && (
        <ImportBanner
          phase="error"
          titlePrefix="系統檢查："
          titleSuffix="品項需要修正"
          desc="請修正後點擊「返回修改」，重新送出。"
          errorCount={errCount}
        />
      )}
      {sysChecked && allPass && (
        <div style={{
          background: "var(--blue-50, #EFF6FF)", border: "1px solid var(--blue-200, #BFDBFE)",
          borderRadius: 4, padding: "10px 16px", marginBottom: 12,
          fontSize: 13, color: "var(--blue-700, #1D4ED8)", display: "flex", gap: 8, alignItems: "center",
        }}>
          <Icon name="info" size={16} />
          {requiresReview
            ? `${items.filter(i => (i.marginPct ?? 1) < 0.20).length} 筆商品毛利率 < 20%，送出後將進入採購主管人工審核。`
            : "所有商品毛利率 ≥ 20%，送出後系統自動放行，無需人工審核。"
          }
        </div>
      )}
      <div className="card" style={{ marginBottom: 12 }}>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px 24px", padding: "16px 20px" }}>
          {[
            ["批次名稱", offer.name || "—"],
            ["申請人",   offer.applicant || "—"],
            ["品項筆數", `${items.length} 筆`],
          ].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)" }}>{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: 100 }}>促銷售價</th>
                <th style={{ width: 110 }}>最終新售價</th>
                <th style={{ width: 85 }}>毛利率 M%</th>
                <th style={{ width: 110 }}>折扣種類</th>
                <th style={{ width: 115 }}>菜單促銷開始</th>
                <th style={{ width: 115 }}>菜單促銷結束</th>
              </tr>
            </thead>
            <tbody>
              {items.length === 0 && (
                <tr><td colSpan={9} style={{ textAlign: "center", padding: 24, color: "var(--fg-3)" }}>尚無商品</td></tr>
              )}
              {items.map((row, idx) => {
                const m = row.marginPct;
                const mStyle = m != null && m < 0.20
                  ? { fontWeight: 600, color: "var(--red-600, #DC2626)" }
                  : {};
                return (
                  <tr key={row.uid || idx}>
                    <td style={{ textAlign: "center", color: "var(--fg-3)", position: "relative" }}>
                      {sysChecked && !checks[idx].pass && <RowErrTip msg={checks[idx].msg} />}
                      {idx + 1}
                    </td>
                    <td className="mono" style={{ fontSize: 12 }}>{row.barcode || "—"}</td>
                    <td>{row.name || "—"}</td>
                    <td className="mono">{row.promoPrice != null ? `NT$${row.promoPrice}` : "—"}</td>
                    <td className="mono">{row.finalPrice != null ? `NT$${row.finalPrice}` : "—"}</td>
                    <td className="mono" style={mStyle}>
                      {m != null ? `${(m * 100).toFixed(1)}%` : "—"}
                    </td>
                    <td>{row.discountType || "—"}</td>
                    <td className="mono">{row.menuPromoStart || "—"}</td>
                    <td className="mono">{row.menuPromoEnd   || "—"}</td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>
    </SubmitConfirmPage>
  );
};

// ── Validation fields ──────────────────────────────────────────────────────
const EO_FLD = {
  barcode:      { key: "barcode",        type: "str",  label: "品號",       mono: true, placeholder: "輸入品號…",
                  validate: r => !r.barcode ? "必填" : (!PRODUCT_MASTER.find(p => p.barcode === r.barcode) ? "查無商品主檔" : null) },
  discountType: { validate: r => !r.discountType ? "必填" : null },
  promoStart:   { key: "menuPromoStart", type: "date", label: "菜單促銷開始",
                  validate: r => r.menuPromoStart && !isFriday(r.menuPromoStart) ? "需為週五" : null },
  promoEnd:     { key: "menuPromoEnd",   type: "date", label: "菜單促銷結束",
                  validate: r => r.menuPromoEnd && !isThursday(r.menuPromoEnd) ? "需為週四" : null },
  promoPrice:   { key: "promoPrice",     type: "num",  label: "促銷售價",   mono: true, unit: "NT$",
                  validate: r => !r.promoPrice ? "必填" : null },
};
const EO_FORM_FIELDS = Object.values(EO_FLD);

// ── EatsOfferDetail main component ────────────────────────────────────────
const EatsOfferDetail = ({
  offer: offerProp, hasApproval, role, currentUser,
  onBack, onSaveOffer, onDirtyChange,
}) => {
  const enrichItems = (items) =>
    (items || []).map(it => it.uid ? it : { ...it, uid: eoUid() });

  const isNew = !offerProp || offerProp.id === "__new__";
  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 [colMode,    setColMode]    = useState("compact");
  const { push: toast } = useToast();

  const isApplicant      = !offer?.id || offer.applicant === currentUser || isPrototypeSuperUser(role);
  const hasEditPermission = canActAsPurchase(role);
  const isReviewer        = canActAsJoanna(role);

  const {
    isDraft, isUnderReview, isApproved, isNoReview,
    isReadOnly, canEdit, canAddRow, canDeleteRow,
    canSaveDraft, canSubmit, canSave,
    showApprovalUI,
  } = useSubmissionModel(offer, hasApproval, hasEditPermission, isApplicant);

  const markDirty = useCallback(() => {
    setDirty(true);
    onDirtyChange?.(true);
  }, [onDirtyChange]);

  // ── Offer patcher ──────────────────────────────────────────────────────
  const patchOffer = (key, value) => {
    markDirty();
    setOffer(o => ({ ...o, [key]: value }));
  };

  // ── Item patching ──────────────────────────────────────────────────────
  const patchItem = (uid, patch) => {
    markDirty();
    setOffer(o => ({
      ...o,
      items: o.items.map(it => {
        if (it.uid !== uid) return it;
        return recalcRow({ ...it, ...patch });
      }),
    }));
  };

  const handleBarcodeCommit = (uid, barcode) => {
    const existing = offer.items.find(it => it.uid === uid) || {};
    const filled = autoFillRow(barcode, existing);
    if (filled) {
      markDirty();
      setOffer(o => ({
        ...o,
        items: o.items.map(it => it.uid === uid ? { ...it, ...filled, uid } : it),
      }));
    } else {
      patchItem(uid, { barcode, name: "", mainCat: "", subCat: "", cost: null, listPrice: null, suggestPrice: null, vat: "" });
    }
  };

  const addRow = () => {
    markDirty();
    setOffer(o => ({ ...o, items: [...o.items, newEoRow(currentUser)] }));
  };

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

  // ── Save / submit ──────────────────────────────────────────────────────
  const clearDirty = () => { setDirty(false); onDirtyChange?.(false); };

  const handleSaveDraft = () => {
    onSaveOffer({ ...offer, status: "eoDraft" }, isNew);
    clearDirty();
    toast({ kind: "success", msg: "草稿已儲存" });
  };

  const handleSave = () => {
    const st = isNoReview ? "eoNoReview" : "eoApproved";
    onSaveOffer({ ...offer, status: st }, isNew);
    clearDirty();
    toast({ kind: "success", msg: "變更已儲存" });
  };

  const [sysCheckErrMap, setSysCheckErrMap] = useState(null);
  const { showErrors, submitGuard } = useFormValidation(offer.items, EO_FORM_FIELDS, "送出");
  const handleSend = () => submitGuard(() => { setSysCheckErrMap(null); setPageMode("doubleCheck"); });
  const handleDoubleCheckBack = (errMap) => {
    if (errMap) setSysCheckErrMap(errMap);
    setPageMode("view");
  };

  const handleSysCheckConfirm = () => {
    const requiresReview = offer.items.some(i => (i.marginPct ?? 1) < 0.20);
    const nextStatus = requiresReview ? "eoUnderReview" : "eoNoReview";
    const msg = requiresReview
      ? `「${offer.name}」已送審，待 Joanna 審核`
      : `「${offer.name}」已提交，系統自動放行（毛利率 ≥ 20%）`;
    const updated = { ...offer, status: nextStatus };
    onSaveOffer(updated, isNew);
    clearDirty();
    setOffer(updated);
    setSysCheckErrMap(null);
    setPageMode("view");
    toast({ kind: "success", msg });
  };

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

  const handleReject = () => {
    const updated = { ...offer, status: "eoDraft" };
    onSaveOffer(updated, false);
    setOffer(updated);
    toast({ kind: "warning", msg: `「${offer.name}」已退回，申請人需重新送出` });
  };

  // ── Page actions ───────────────────────────────────────────────────────
  const showImportBtns = canEdit && !isUnderReview;

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

  // ── Column selection ───────────────────────────────────────────────────
  const showCols = useMemo(() => {
    const base = colMode === "compact"
      ? EO_ALL_COLS.filter(c => c.stickyNum || c.sticky2nd || c.actionCol || EO_COMPACT_KEYS.includes(c.key))
      : EO_ALL_COLS;
    return canDeleteRow ? base : base.filter(c => !c.actionCol);
  }, [colMode, canDeleteRow]);

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

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

    // ── sticky # ──
    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",
      }}>
        {sysCheckErrMap?.[row.uid] && <RowErrTip msg={sysCheckErrMap[row.uid]} />}
        {idx + 1}
      </td>
    );

    // ── sticky barcode ──
    if (k === "barcode") return (
      <td key={k} style={{
        position: "sticky", left: EO_NUM_W, background: "var(--bg-surface)", zIndex: 1,
        boxShadow: "2px 0 4px rgba(0,0,0,.06)",
      }}>
        {canEdit
          ? <CellEditor alwaysOn
                field={EO_FLD.barcode}
                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>
    );

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

    // ── discountType (portal select) ──
    if (k === "discountType") return (
      <td key={k}>
        {canEdit
          ? <ErrWrap errMsg={showErrors ? EO_FLD.discountType.validate(row) : null}>
              <PortalSelect
                value={row.discountType}
                options={DISCOUNT_TYPE_OPTIONS}
                onChange={v => patchItem(row.uid, { discountType: v })}
                triggerClassName="cell-editor-portal-trigger"
                placeholder="選擇…"
              />
            </ErrWrap>
          : <span style={{ padding: "0 8px", fontSize: 13, color: "var(--fg-2)" }}>{row.discountType || <span style={{ color: "var(--fg-4)" }}>—</span>}</span>
        }
      </td>
    );

    // ── buyQty / discountQty — locked unless BOGO/Volume ──
    if (k === "buyQty" || k === "discountQty") {
      const bogoType = row.discountType === "BOGO" || row.discountType === "Volume Discount";
      if (!bogoType || !canEdit) {
        const v = bogoType ? row[k] : null;
        return <td key={k} className="mono" style={{ color: bogoType ? "var(--fg-2)" : "var(--fg-4)", textAlign: "right" }}>{v ?? "—"}</td>;
      }
      return (
        <td key={k}>
          <CellEditor alwaysOn
            field={{ key: k, type: "num", label: col.label }}
            value={row[k]} row={row}
            onCommit={val => patchItem(row.uid, { [k]: val ? +val : null })} />
        </td>
      );
    }

    // ── menuPromoStart — must be Friday ──
    if (k === "menuPromoStart") return (
      <td key={k}>
        {canEdit
          ? <CellEditor alwaysOn
              field={EO_FLD.promoStart}
              value={row.menuPromoStart} row={row}
              onCommit={val => patchItem(row.uid, { menuPromoStart: val })} />
          : <span className="mono" style={{ padding: "0 8px", fontSize: 12, color: "var(--fg-2)" }}>
              {row.menuPromoStart || <span style={{ color: "var(--fg-4)" }}>—</span>}
            </span>
        }
      </td>
    );

    // ── menuPromoEnd — must be Thursday ──
    if (k === "menuPromoEnd") return (
      <td key={k}>
        {canEdit
          ? <CellEditor alwaysOn
              field={EO_FLD.promoEnd}
              value={row.menuPromoEnd} row={row}
              onCommit={val => patchItem(row.uid, { menuPromoEnd: val })} />
          : <span className="mono" style={{ padding: "0 8px", fontSize: 12, color: "var(--fg-2)" }}>
              {row.menuPromoEnd || <span style={{ color: "var(--fg-4)" }}>—</span>}
            </span>
        }
      </td>
    );

    // ── marginPct — warn if < 20% ──
    if (k === "marginPct") {
      const v = row.marginPct;
      const warn = v != null && v < 0.20;
      return (
        <td key={k} className="mono" style={{ textAlign: "right", color: warn ? "var(--red-600, #DC2626)" : "var(--fg-2)", fontWeight: warn ? 600 : undefined }}>
          {v != null ? `${(v * 100).toFixed(1)}%` : <span style={{ color: "var(--fg-4)" }}>—</span>}
        </td>
      );
    }

    // ── pct formula cols ──
    if (k === "discountPct" || k === "promoDepth" || k === "stackDepth") {
      const v = row[k];
      return <td key={k} className="mono" style={{ textAlign: "right", color: "var(--fg-2)" }}>
        {v != null ? `${(v * 100).toFixed(1)}%` : <span style={{ color: "var(--fg-4)" }}>—</span>}
      </td>;
    }

    // ── autoListWhitelist (boolean editable) ──
    if (k === "autoListWhitelist") {
      if (canEdit) return (
        <td key={k} style={{ textAlign: "center" }}>
          <CellEditor alwaysOn
            field={{ key: k, type: "bool", label: col.label }}
            value={row.autoListWhitelist} row={row}
            onCommit={val => patchItem(row.uid, { autoListWhitelist: !!val })} />
        </td>
      );
      return <td key={k} style={{ textAlign: "center", color: row.autoListWhitelist ? "var(--fg-1)" : "var(--fg-4)" }}>
        {row.autoListWhitelist ? "✓" : "—"}
      </td>;
    }

    // ── costInErp / timeCheck (boolean editable by H group) ──
    if (k === "costInErp" || k === "timeCheck") {
      const v = row[k];
      return <td key={k} style={{ textAlign: "center", color: v ? "var(--fg-1)" : "var(--fg-4)" }}>
        {v ? "✓" : "—"}
      </td>;
    }

    // ── promoPrice — editable with 必填 hint ──
    if (k === "promoPrice" && canEdit) return (
      <td key={k}>
        <CellEditor alwaysOn
          field={EO_FLD.promoPrice}
          value={row.promoPrice} row={row}
          onCommit={val => patchItem(row.uid, { promoPrice: val ? +val : null })} />
      </td>
    );

    // ── simple editable text/num cols ──
    if (col.editable && canEdit && !["discountType","buyQty","discountQty","menuPromoStart","menuPromoEnd","autoListWhitelist","costInErp","timeCheck","promoPrice"].includes(k)) {
      const isNum = ["newCost","u1PromoPrice","purchaseLimit"].includes(k);
      return (
        <td key={k}>
          <CellEditor alwaysOn
            field={{ key: k, type: isNum ? "num" : (k.includes("date") || k.includes("Start") || k.includes("End") || k.includes("start") || k.includes("end") ? "date" : "str"), label: col.label, mono: col.mono }}
            value={row[k]} row={row}
            onCommit={val => patchItem(row.uid, { [k]: isNum && val ? +val : val })} />
        </td>
      );
    }

    // ── money cols (NT$) ──
    const moneyKeys = ["cost","newCost","listPrice","suggestPrice","promoPrice","u1PromoPrice","finalPrice","unitPrice","setPrice","marketPrice"];
    if (moneyKeys.includes(k)) {
      const v = row[k];
      if (v == null) return <td key={k} className="mono" style={{ color: "var(--fg-4)", textAlign: "right" }}>—</td>;
      return <td key={k} className="mono" style={{ color: "var(--fg-2)", textAlign: "right" }}>
        NT${(+v).toLocaleString("zh-TW", { minimumFractionDigits: 0, maximumFractionDigits: 2 })}
      </td>;
    }

    // ── promoCheck / promoVsList / autoListAdj ──
    if (k === "promoCheck" || k === "promoVsList" || k === "autoListAdj") {
      const v = row[k];
      const isPass = v === "PASS" || v === "NO";
      const isFail = v === "FAIL" || v === "YES";
      const failMsg = k === "promoCheck" ? "促銷售價 ≥ 牌價" : k === "promoVsList" ? "促銷售價 ≥ 建議售價" : null;
      return (
        <td key={k} style={{ textAlign: "center", fontWeight: isFail ? 600 : undefined, color: isFail ? "var(--red-600, #DC2626)" : isPass ? "var(--fg-2)" : "var(--fg-4)" }}>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 4 }}>
            {v || "—"}
            {isFail && failMsg && <ErrIconTip msg={failMsg} />}
          </span>
        </td>
      );
    }

    // ── generic ──
    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)" }}>{String(v)}</td>;
  };

  // ── Page modes ─────────────────────────────────────────────────────────
  if (pageMode === "doubleCheck") {
    return <EoDoubleCheckView offer={offer} onBack={handleDoubleCheckBack} onConfirm={handleSysCheckConfirm} />;
  }

  // ── Main view ──────────────────────────────────────────────────────────
  const pageTitle = isNew ? "新增批次" : (offer.name || "(未命名)");
  const metaSummary = `${offer.name || "（未命名）"} · ${offer.applicant || "—"} · ${offer.items.length} 筆`;

  return (
    <>
      <PageHead
        crumbs={[
          { label: "促銷活動" },
          { label: "單品優惠（Eats Offer）", 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={
          <>
            {showImportBtns && <>
              <Button variant="default" icon="download" onClick={() => toast({ kind: "success", msg: "範本下載中…" })}>下載匯入格式</Button>
              <Button variant="default" icon="upload" onClick={() => toast({ kind: "success", msg: "請選擇 Excel 檔案" })}>匯入 Excel</Button>
            </>}
            {renderActions()}
          </>
        }
      />

      {showApprovalUI && isUnderReview && !isReviewer && <ApprovalStatusBanner />}

      {/* ── 批次資訊 MetaCard ── */}
      <MetaCard summary={metaSummary} sectionTitle="批次資訊">
        <div className="eo-batch__grid">
          <FormField label="批次名稱" required>
            {canEdit
              ? <Field value={offer.name} onChange={e => patchOffer("name", e.target.value)} placeholder="填寫此批次識別名稱" />
              : <FormFieldVal>{offer.name || "—"}</FormFieldVal>}
          </FormField>
          <FormField label="申請人" locked>
            <FormFieldVal>{offer.applicant || "—"}</FormFieldVal>
          </FormField>
          <FormField label="建立日期" locked>
            <FormFieldVal mono>{offer.createdAt || "—"}</FormFieldVal>
          </FormField>
          <FormField label="更新時間" locked>
            <FormFieldVal mono>{offer.updatedAt || "—"}</FormFieldVal>
          </FormField>
        </div>
      </MetaCard>

      {canEdit && sysCheckErrMap && Object.keys(sysCheckErrMap).length > 0 && (
        <ImportBanner
          phase="error"
          titlePrefix="系統檢查："
          titleSuffix="品項需要修正"
          desc="下方標示的列有問題，修正後請重新送出。"
          errorCount={Object.keys(sysCheckErrMap).length}
          onView={(idx) => { const inds = document.querySelectorAll('.row-err-ind'); inds[idx % (inds.length || 1)]?.closest('tr')?.scrollIntoView({ block: 'center', behavior: 'smooth' }); }}
          onClose={() => setSysCheckErrMap(null)}
        />
      )}

      {/* ── SKU 商品子表 ── */}
      <div className="card">
        <SkuToolbar>
          <ModeToggle mode={colMode} onChange={setColMode} compactLabel="精選欄位" />
          <span style={{ fontSize: 13, color: "var(--fg-2)", flexShrink: 0, marginLeft: 12 }}>
            共 <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 ? "點「新增品項」或匯入 Excel 開始作業。" : "此批次目前沒有商品。"}>
            {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={EO_GROUP_LABELS}
                    leadingSlot={
                      <th rowSpan={2} style={{
                        width: EO_NUM_W, minWidth: EO_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: EO_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}>
                      {showCols.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>
    </>
  );
};

Object.assign(window, { EatsOfferDetail });
