import type { TFunction } from "i18next";
import type { ItemLotTraceResponse } from "@/app/api/itemTracing";
import {
  exportMultiSheetToXlsx,
  type MultiSheetSpec,
} from "@/app/(main)/chart/_components/exportChartToXlsx";
import type { CompiledTraceGraph } from "./compileTraceGraph";
import {
  mergeScopedAdjustments,
  mergeScopedDoDeliveries,
  mergeScopedFailEvents,
  mergeScopedOpenMovements,
  mergeScopedOrigins,
  mergeScopedOutboundUsage,
  mergeScopedPurchaseEvents,
  mergeScopedPutawayEvents,
  mergeScopedQcResults,
  mergeScopedReplenishmentEvents,
  mergeScopedReturnEvents,
  mergeScopedStockTakeEvents,
  mergeScopedTransfers,
} from "./mergeLocationScopedData";
import {
  resolveStockTakeAcceptedQty,
  resolveStockTakeBookQty,
} from "./traceStockTakeUtils";
import {
  createTraceLabelTranslator,
  type TraceLabelTranslator,
} from "./traceLabelUtils";
import { kindLabelKey, phaseLabelKey } from "./traceFlowNodeUtils";
import { buildTracePresentationRows } from "./tracePresentationAdapter";
import { isDoGroupChild } from "./traceDoGroupLayout";

const NO_DATA = "（無資料 / No records）";

const SHEET = {
  summary: "摘要 Summary",
  timeline: "生命週期時間軸 Timeline",
  origins: "來源與採購 Origins",
  qc: "品質檢驗 QC",
  warehouse: "倉儲作業 Warehouse",
  outbound: "出庫 Outbound",
  stockTake: "盤點 Stock Take",
  production: "生產與BOM Production",
} as const;

export type ItemLotTraceExportLabels = {
  bomDirectionFg: string;
  bomDirectionMaterial: string;
  bomDirectionUnknown: string;
};

const orDash = (v: string | number | null | undefined): string | number => {
  if (v == null) return "—";
  if (typeof v === "string" && !v.trim()) return "—";
  return v;
};

const bomDirectionLabel = (
  direction: string,
  labels: ItemLotTraceExportLabels,
): string => {
  const d = direction.trim().toUpperCase();
  if (d === "FINISHED_GOOD") return labels.bomDirectionFg;
  if (d === "MATERIAL") return labels.bomDirectionMaterial;
  return labels.bomDirectionUnknown;
};

const ensureRows = (
  rows: Record<string, unknown>[],
  emptyTemplate: Record<string, unknown>,
): Record<string, unknown>[] => {
  if (rows.length > 0) return rows;
  const firstKey = Object.keys(emptyTemplate)[0];
  return [{ ...emptyTemplate, ...(firstKey ? { [firstKey]: NO_DATA } : {}) }];
};

const todayYmd = (): string => {
  const d = new Date();
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}${m}${day}`;
};

const safeFilenamePart = (s: string): string =>
  s.replace(/[\\/:*?"<>|]/g, "_").trim() || "unknown";

export const buildItemLotTraceFilename = (
  data: ItemLotTraceResponse,
): string => {
  const item = safeFilenamePart(data.lot.itemCode || "item");
  const lot = safeFilenamePart(data.lot.lotNo || "lot");
  return `批號追溯_${item}_${lot}_${todayYmd()}`;
};

const summaryEmpty = (): Record<string, unknown> => ({
  "Field / 欄位": "",
  "Value / 值": "",
});

const buildSummarySheet = (
  data: ItemLotTraceResponse,
  labels: ItemLotTraceExportLabels,
  exportAt: string,
): Record<string, unknown>[] => {
  const { lot, warehouseLines, joPrelude, alternateLocations, bomTrace } = data;
  const totalAvailable = warehouseLines.reduce(
    (s, w) => s + (w.availableQty ?? 0),
    0,
  );
  const rows: Record<string, unknown>[] = [
    { "Field / 欄位": "Lot No. / 批號", "Value / 值": orDash(lot.lotNo) },
    { "Field / 欄位": "Item Code / 貨品編號", "Value / 值": orDash(lot.itemCode) },
    { "Field / 欄位": "Item Name / 品名", "Value / 值": orDash(lot.itemName) },
    { "Field / 欄位": "UOM / 單位", "Value / 值": orDash(lot.uom) },
    { "Field / 欄位": "Expiry / 效期", "Value / 值": orDash(lot.expiryDate) },
    {
      "Field / 欄位": "Production Date / 生產日期",
      "Value / 值": orDash(lot.productionDate),
    },
    {
      "Field / 欄位": "Stock In Date / 入庫日期",
      "Value / 值": orDash(lot.stockInDate),
    },
    {
      "Field / 欄位": "Total Available / 總可用量",
      "Value / 值": totalAvailable,
    },
    {
      "Field / 欄位": "BOM Direction / BOM 方向",
      "Value / 值": bomDirectionLabel(bomTrace.direction, labels),
    },
  ];

  if (joPrelude) {
    const jo = joPrelude.jobOrder;
    rows.push(
      {
        "Field / 欄位": "Job Order / 工單",
        "Value / 值": orDash(jo.jobOrderCode),
      },
      {
        "Field / 欄位": "Plan Start / 計劃開工",
        "Value / 值": orDash(jo.planStart),
      },
      { "Field / 欄位": "Planned Qty / 計劃產量", "Value / 值": jo.reqQty },
      {
        "Field / 欄位": "JO Status / 工單狀態",
        "Value / 值": orDash(jo.status),
      },
    );
  }

  rows.push(
    { "Field / 欄位": "Export Time / 匯出時間", "Value / 值": exportAt },

    { "Field / 欄位": "", "Value / 值": "" },
    {
      "Field / 欄位": "— Warehouse Breakdown / 各倉庫存量 —",
      "Value / 值": "",
    },
  );

  warehouseLines.forEach((w) => {
    rows.push({
      "Field / 欄位": `Warehouse / 倉位: ${w.warehouseCode}`,
      "Value / 值": `In ${w.inQty} · Out ${w.outQty} · Available ${w.availableQty} · ${w.status}`,
    });
  });

  if (alternateLocations.length > 0) {
    rows.push(
      { "Field / 欄位": "", "Value / 值": "" },
      {
        "Field / 欄位": "— Alternate Locations / 其他倉位 —",
        "Value / 值": "",
      },
    );
    alternateLocations.forEach((loc) => {
      rows.push({
        "Field / 欄位": `Inventory Lot ${loc.inventoryLotId}`,
        "Value / 值": `${loc.warehouseCode} · Available ${loc.availableQty}`,
      });
    });
  }

  return rows.length > 0 ? rows : [summaryEmpty()];
};

const timelineEmpty = (): Record<string, unknown> => ({
  "Date/Time / 時間": "",
  "Phase / 階段": "",
  "Event Type / 事件類型": "",
  "Doc No. / 單號": "",
  "Ref Type / 來源類型": "",
  "Ref Type Code": "",
  "Qty / 數量": "",
  "UOM / 單位": "",
  "Warehouse / 倉位": "",
  "Handler / 經手人": "",
  "Lot / 批號": "",
  "Remarks / 備註": "",
});

const buildTimelineSheet = (
  compiledGraph: CompiledTraceGraph,
  t: TFunction,
  tr: TraceLabelTranslator,
): Record<string, unknown>[] => {
  const nodes = compiledGraph.layout.nodes
    .filter(
      (n) =>
        n.kind !== "DO_GROUP" && n.kind !== "PICK_GROUP" && !isDoGroupChild(n),
    )
    .slice()
    .sort((a, b) => {
      const ta = a.timestamp ?? "";
      const tb = b.timestamp ?? "";
      if (ta !== tb) return ta.localeCompare(tb);
      return a.sortKey - b.sortKey || a.sequenceIndex - b.sequenceIndex;
    });

  const rows = nodes.map((n) => {
    const handlerDetail = n.details.find(
      (d) =>
        d.label.toLowerCase().includes("handler") ||
        d.label.includes("經手") ||
        d.label.includes("核准") ||
        d.label.includes("盤點"),
    );
    return {
      "Date/Time / 時間": orDash(n.timestamp),
      "Phase / 階段": t(phaseLabelKey(n.phase)),
      "Event Type / 事件類型": t(kindLabelKey(n.kind)),
      "Doc No. / 單號": orDash(n.refCode),
      "Ref Type / 來源類型": tr.refType(n.refType),
      "Ref Type Code": orDash(n.refType),
      "Qty / 數量": n.qty ?? "",
      "UOM / 單位": orDash(n.uom),
      "Warehouse / 倉位": orDash(n.warehouseCode),
      "Handler / 經手人": orDash(
        handlerDetail?.value ?? n.meta?.split(" · ")[0],
      ),
      "Lot / 批號": orDash(n.traceLotNo),
      "Remarks / 備註": orDash(n.subtitle),
    };
  });
  return ensureRows(rows, timelineEmpty());
};

const originsEmpty = (): Record<string, unknown> => ({
  "Section / 區塊": "",
  "Warehouse / 倉位": "",
  "Inventory Lot Id": "",
  "Type / 類型": "",
  "Type Code": "",
  "Doc No. / 單號": "",
  "Supplier Code / 供應商編號": "",
  "Supplier / 供應商": "",
  "DN No. / 送貨單號": "",
  "Qty / 數量": "",
  "Status / 狀態": "",
  "Date / 日期": "",
});

const buildOriginsSheet = (
  data: ItemLotTraceResponse,
  tr: TraceLabelTranslator,
): Record<string, unknown>[] => {
  const originRows = mergeScopedOrigins(data).map((o) => ({
    "Section / 區塊": "Origin / 來源",
    "Warehouse / 倉位": o.scopeWarehouseCode,
    "Inventory Lot Id": o.inventoryLotId,
    "Type / 類型": tr.refType(o.type),
    "Type Code": orDash(o.type),
    "Doc No. / 單號": orDash(o.refCode),
    "Supplier Code / 供應商編號": orDash(o.supplierCode),
    "Supplier / 供應商": orDash(o.supplierName),
    "DN No. / 送貨單號": orDash(o.dnNo),
    "Qty / 數量": o.acceptedQty,
    "Status / 狀態": tr.stockInStatus(o.status),
    "Date / 日期": orDash(o.receiptDate),
  }));

  const purchaseRows = mergeScopedPurchaseEvents(data).map((p) => ({
    "Section / 區塊": "Purchase / 採購",
    "Warehouse / 倉位": p.scopeWarehouseCode,
    "Inventory Lot Id": p.inventoryLotId,
    "Type / 類型": tr.refType("PO"),
    "Type Code": "PO",
    "Doc No. / 單號": orDash(p.purchaseOrderCode),
    "Supplier Code / 供應商編號": orDash(p.supplierCode),
    "Supplier / 供應商": orDash(p.supplierName),
    "DN No. / 送貨單號": "",
    "Qty / 數量": p.orderQty,
    "Status / 狀態": orDash(p.status),
    "Date / 日期": orDash(p.orderDate),
  }));

  return ensureRows([...originRows, ...purchaseRows], originsEmpty());
};

const qcEmpty = (): Record<string, unknown> => ({
  "Warehouse / 倉位": "",
  "Inventory Lot Id": "",
  "QC Result / 結果": "",
  "QC Type / 品檢類型": "",
  "QC Type Code": "",
  "QC Item Code / 品檢項編號": "",
  "QC Item / 品檢項": "",
  "Accepted Qty / 抽樣數量": "",
  "Fail Qty / 不良數量": "",
  "Remarks / 備註": "",
  "Handler / 經手人": "",
  "Date/Time / 時間": "",
  "Stock In Line Id": "",
});

const buildQcSheet = (
  data: ItemLotTraceResponse,
  tr: TraceLabelTranslator,
): Record<string, unknown>[] => {
  const rows = mergeScopedQcResults(data).map((q) => ({
    "Warehouse / 倉位": q.scopeWarehouseCode,
    "Inventory Lot Id": q.inventoryLotId,
    "QC Result / 結果": tr.qcPassed(q.qcPassed),
    "QC Type / 品檢類型": tr.qcType(q.qcType),
    "QC Type Code": orDash(q.qcType),
    "QC Item Code / 品檢項編號": orDash(q.qcItemCode),
    "QC Item / 品檢項": orDash(q.qcItemName || q.qcItemDescription),
    "Accepted Qty / 抽樣數量": q.acceptedQty,
    "Fail Qty / 不良數量": q.failQty,
    "Remarks / 備註": orDash(q.remarks),
    "Handler / 經手人": orDash(q.handledBy),
    "Date/Time / 時間": orDash(q.created),
    "Stock In Line Id": q.stockInLineId,
  }));
  return ensureRows(rows, qcEmpty());
};

const warehouseEmpty = (): Record<string, unknown> => ({
  "Section / 區塊": "",
  "Warehouse / 倉位": "",
  "Inventory Lot Id": "",
  "Direction / 方向": "",
  "Type / 類型": "",
  "Type Code": "",
  "Doc No. / 單號": "",
  "From / 來源倉": "",
  "To / 目標倉": "",
  "Qty / 數量": "",
  "Handler / 經手人": "",
  "Status / 狀態": "",
  "Remarks / 備註": "",
  "Date/Time / 時間": "",
});

const buildWarehouseSheet = (
  data: ItemLotTraceResponse,
  tr: TraceLabelTranslator,
): Record<string, unknown>[] => {
  const putaway = mergeScopedPutawayEvents(data).map((p) => ({
    "Section / 區塊": "Putaway / 上架",
    "Warehouse / 倉位": p.scopeWarehouseCode,
    "Inventory Lot Id": p.inventoryLotId,
    "Direction / 方向": "",
    "Type / 類型": tr.refType(p.refType),
    "Type Code": orDash(p.refType),
    "Doc No. / 單號": orDash(p.refCode),
    "From / 來源倉": "",
    "To / 目標倉": orDash(p.warehouseCode),
    "Qty / 數量": p.qty,
    "Handler / 經手人": orDash(p.handledBy),
    "Status / 狀態": orDash(p.status),
    "Remarks / 備註": "",
    "Date/Time / 時間": orDash(p.timestamp),
  }));

  const transfers = mergeScopedTransfers(data).map((trRow) => ({
    "Section / 區塊": "Transfer / 轉倉",
    "Warehouse / 倉位": trRow.scopeWarehouseCode,
    "Inventory Lot Id": trRow.inventoryLotId,
    "Direction / 方向": "",
    "Type / 類型": tr.refType("TRANSFER"),
    "Type Code": "TRANSFER",
    "Doc No. / 單號": orDash(trRow.transferCode),
    "From / 來源倉": orDash(trRow.fromWarehouse),
    "To / 目標倉": orDash(trRow.toWarehouse),
    "Qty / 數量": trRow.qty,
    "Handler / 經手人": "",
    "Status / 狀態": "",
    "Remarks / 備註": "",
    "Date/Time / 時間": orDash(trRow.timestamp),
  }));

  const adjustments = mergeScopedAdjustments(data).map((a) => ({
    "Section / 區塊": "Adjustment / 庫存調整",
    "Warehouse / 倉位": a.scopeWarehouseCode,
    "Inventory Lot Id": a.inventoryLotId,
    "Direction / 方向": tr.direction(a.direction),
    "Type / 類型": tr.adjustmentType(a.adjustmentType),
    "Type Code": orDash(a.adjustmentType),
    "Doc No. / 單號": orDash(a.refCode),
    "From / 來源倉": "",
    "To / 目標倉": orDash(a.warehouseCode),
    "Qty / 數量": a.qty,
    "Handler / 經手人": orDash(a.handledBy),
    "Status / 狀態": "",
    "Remarks / 備註": orDash(a.reason),
    "Date/Time / 時間": orDash(a.timestamp),
  }));

  const openMoves = mergeScopedOpenMovements(data).map((o) => ({
    "Section / 區塊": "Opening Stock / 開倉入庫",
    "Warehouse / 倉位": o.scopeWarehouseCode,
    "Inventory Lot Id": o.inventoryLotId,
    "Direction / 方向": tr.direction("IN"),
    "Type / 類型": tr.refType("OPEN"),
    "Type Code": "OPEN",
    "Doc No. / 單號": orDash(o.refCode),
    "From / 來源倉": "",
    "To / 目標倉": orDash(o.warehouseCode),
    "Qty / 數量": o.qty,
    "Handler / 經手人": orDash(o.handledBy),
    "Status / 狀態": "",
    "Remarks / 備註": orDash(o.remarks),
    "Date/Time / 時間": orDash(o.timestamp),
  }));

  return ensureRows(
    [...putaway, ...transfers, ...adjustments, ...openMoves],
    warehouseEmpty(),
  );
};

const outboundEmpty = (): Record<string, unknown> => ({
  "Section / 區塊": "",
  "Warehouse / 倉位": "",
  "Inventory Lot Id": "",
  "Usage Type / 類型": "",
  "Usage Type Code": "",
  "Doc No. / 單號": "",
  "Pick Order / 提料單": "",
  "Conso / 併單": "",
  "Job Order / 工單": "",
  "Delivery Order / 送貨單": "",
  "Delivery Note / DN": "",
  "Ticket / 票號": "",
  "Shop / 門市": "",
  "Qty / 數量": "",
  "Handler / 經手人": "",
  "Extra / 加單": "",
  "Replenish / 補貨": "",
  "Remarks / 備註": "",
  "Date/Time / 時間": "",
});

const buildOutboundSheet = (
  data: ItemLotTraceResponse,
  tr: TraceLabelTranslator,
): Record<string, unknown>[] => {
  const usage = mergeScopedOutboundUsage(data).map((u) => ({
    "Section / 區塊": "Outbound Usage / 出庫使用",
    "Warehouse / 倉位": u.scopeWarehouseCode,
    "Inventory Lot Id": u.inventoryLotId,
    "Usage Type / 類型": tr.usageType(u.usageType),
    "Usage Type Code": orDash(u.usageType),
    "Doc No. / 單號": orDash(u.deliveryOrderCode || u.pickOrderCode),
    "Pick Order / 提料單": orDash(u.pickOrderCode),
    "Conso / 併單": orDash(u.consoCode),
    "Job Order / 工單": orDash(u.jobOrderCode),
    "Delivery Order / 送貨單": orDash(u.deliveryOrderCode),
    "Delivery Note / DN": "",
    "Ticket / 票號": "",
    "Shop / 門市": "",
    "Qty / 數量": u.qty,
    "Handler / 經手人": orDash(u.handler),
    "Extra / 加單": "",
    "Replenish / 補貨": "",
    "Remarks / 備註": "",
    "Date/Time / 時間": orDash(u.timestamp),
  }));

  const doRows = mergeScopedDoDeliveries(data).map((d) => ({
    "Section / 區塊": "DO Delivery / 成品出倉",
    "Warehouse / 倉位": d.scopeWarehouseCode,
    "Inventory Lot Id": d.inventoryLotId,
    "Usage Type / 類型": tr.usageType("FG_DELIVERY"),
    "Usage Type Code": "FG_DELIVERY",
    "Doc No. / 單號": orDash(d.deliveryOrderCode),
    "Pick Order / 提料單": orDash(d.pickOrderCode),
    "Conso / 併單": orDash(d.consoCode),
    "Job Order / 工單": "",
    "Delivery Order / 送貨單": orDash(d.deliveryOrderCode),
    "Delivery Note / DN": orDash(d.deliveryNoteCode),
    "Ticket / 票號": orDash(d.ticketNo),
    "Shop / 門市": [d.shopCode, d.shopName].filter(Boolean).join(" · ") || "—",
    "Qty / 數量": d.qty,
    "Handler / 經手人": orDash(d.handler),
    "Extra / 加單": d.isExtra ? "Y" : "",
    "Replenish / 補貨": d.isReplenish ? "Y" : "",
    "Remarks / 備註": "",
    "Date/Time / 時間": orDash(d.timestamp),
  }));

  const replenish = mergeScopedReplenishmentEvents(data).map((r) => ({
    "Section / 區塊": "Replenishment / 補貨",
    "Warehouse / 倉位": r.scopeWarehouseCode,
    "Inventory Lot Id": r.inventoryLotId,
    "Usage Type / 類型": "Replenishment",
    "Usage Type Code": "REPLENISH",
    "Doc No. / 單號": orDash(r.replenishmentCode),
    "Pick Order / 提料單": "",
    "Conso / 併單": "",
    "Job Order / 工單": "",
    "Delivery Order / 送貨單": orDash(r.sourceDoCode),
    "Delivery Note / DN": "",
    "Ticket / 票號": "",
    "Shop / 門市": [r.shopCode, r.shopName].filter(Boolean).join(" · ") || "—",
    "Qty / 數量": r.replenishQty,
    "Handler / 經手人": orDash(r.handler),
    "Extra / 加單": "",
    "Replenish / 補貨": "Y",
    "Remarks / 備註": orDash(r.reason),
    "Date/Time / 時間": orDash(r.timestamp),
  }));

  const returns = mergeScopedReturnEvents(data).map((r) => ({
    "Section / 區塊": "Return / 退貨",
    "Warehouse / 倉位": r.scopeWarehouseCode,
    "Inventory Lot Id": r.inventoryLotId,
    "Usage Type / 類型": tr.movementType(r.movementType),
    "Usage Type Code": orDash(r.movementType),
    "Doc No. / 單號": orDash(r.refCode),
    "Pick Order / 提料單": "",
    "Conso / 併單": "",
    "Job Order / 工單": "",
    "Delivery Order / 送貨單": "",
    "Delivery Note / DN": "",
    "Ticket / 票號": "",
    "Shop / 門市": "",
    "Qty / 數量": r.qty,
    "Handler / 經手人": orDash(r.handler),
    "Extra / 加單": "",
    "Replenish / 補貨": "",
    "Remarks / 備註": orDash(r.remarks),
    "Date/Time / 時間": orDash(r.timestamp),
  }));

  const fails = mergeScopedFailEvents(data).map((f) => ({
    "Section / 區塊": "Pick Failure / 揀貨異常",
    "Warehouse / 倉位": f.scopeWarehouseCode,
    "Inventory Lot Id": f.inventoryLotId,
    "Usage Type / 類型": tr.failType(f.failType),
    "Usage Type Code": orDash(f.failType),
    "Doc No. / 單號": orDash(f.pickOrderCode),
    "Pick Order / 提料單": orDash(f.pickOrderCode),
    "Conso / 併單": "",
    "Job Order / 工單": "",
    "Delivery Order / 送貨單": "",
    "Delivery Note / DN": "",
    "Ticket / 票號": "",
    "Shop / 門市": "",
    "Qty / 數量": f.qty,
    "Handler / 經手人": orDash(f.handlerName),
    "Extra / 加單": "",
    "Replenish / 補貨": "",
    "Remarks / 備註": orDash(f.category),
    "Date/Time / 時間": orDash(f.recordDate),
  }));

  return ensureRows(
    [...usage, ...doRows, ...replenish, ...returns, ...fails],
    outboundEmpty(),
  );
};

const stockTakeEmpty = (): Record<string, unknown> => ({
  "Warehouse / 倉位": "",
  "Inventory Lot Id": "",
  "Stock Take Code / 盤點單號": "",
  "Round / 輪次": "",
  "Section / 區域": "",
  "Lot / 批號": "",
  "Book Qty / 帳面數量": "",
  "Accepted Qty / 核准數量": "",
  "Variance Qty / 差異數量": "",
  "Approver / 核准人": "",
  "Stock Taker / 初盤人": "",
  "First Count Qty / 初盤數量": "",
  "Second Count Qty / 複盤數量": "",
  "Approver Count Qty / 覆盤數量": "",
  "Record Status / 紀錄狀態": "",
  "Date/Time / 時間": "",
});

const buildStockTakeSheet = (
  data: ItemLotTraceResponse,
): Record<string, unknown>[] => {
  const rows = mergeScopedStockTakeEvents(data).map((e) => {
    const d = e.recordDetail;
    return {
      "Warehouse / 倉位": e.scopeWarehouseCode,
      "Inventory Lot Id": e.inventoryLotId,
      "Stock Take Code / 盤點單號": orDash(e.stockTakeCode),
      "Round / 輪次": orDash(
        e.stockTakeRoundName ??
          (e.stockTakeRoundId != null ? `#${e.stockTakeRoundId}` : ""),
      ),
      "Section / 區域": orDash(e.stockTakeSection),
      "Lot / 批號": orDash(e.lotNo),
      "Book Qty / 帳面數量": resolveStockTakeBookQty(d, e.beforeQty) ?? "",
      "Accepted Qty / 核准數量":
        resolveStockTakeAcceptedQty(d, e.afterQty) ?? "",
      "Variance Qty / 差異數量":
        d?.varianceQty != null ? d.varianceQty : e.varianceQty,
      "Approver / 核准人": orDash(d?.approverName || e.approver),
      "Stock Taker / 初盤人": orDash(d?.stockTakerName),
      "First Count Qty / 初盤數量": d?.pickerFirstQty ?? "",
      "Second Count Qty / 複盤數量": d?.pickerSecondQty ?? "",
      "Approver Count Qty / 覆盤數量": d?.approverQty ?? "",
      "Record Status / 紀錄狀態": orDash(d?.recordStatus),
      "Date/Time / 時間": orDash(e.timestamp),
    };
  });
  return ensureRows(rows, stockTakeEmpty());
};

const productionEmpty = (): Record<string, unknown> => ({
  "Section / 區塊": "",
  "Job Order / 工單": "",
  "Doc No. / 單號": "",
  "Item Code / 貨品編號": "",
  "Lot / 批號": "",
  "Item Name / 品名": "",
  "Step / 步驟": "",
  "Assigned Step / 對應製程": "",
  "Qty / 數量": "",
  "Scrap Qty / 損耗數量": "",
  "Defect Qty / 不良數量": "",
  "UOM / 單位": "",
  "Operator / 操作員": "",
  "Equipment / 設備": "",
  "Status / 狀態": "",
  "Date/Time / 時間": "",
});

const buildProductionSheet = (
  data: ItemLotTraceResponse,
  compiledGraph: CompiledTraceGraph,
  tr: TraceLabelTranslator,
): Record<string, unknown>[] => {
  const rows: Record<string, unknown>[] = [];
  const presentation = buildTracePresentationRows(compiledGraph.nodes, data);

  const upstreamSource = data.joPrelude?.materialInputs?.length
    ? data.joPrelude.materialInputs.map((m) => ({
        jobOrderCode: m.jobOrderCode,
        materialItemCode: m.materialItemCode,
        materialLotNo: m.materialLotNo,
        materialQty: m.materialQty,
        materialUom: m.materialUom,
        assignedStepName: m.assignedStepName,
        pickOrderCode: m.pickOrderCode,
        pickedAt: m.pickedAt,
        materialItemName: m.materialItemName,
      }))
    : data.bomTrace.upstream.map((u) => ({
        jobOrderCode: u.jobOrderCode,
        materialItemCode: u.materialItemCode,
        materialLotNo: u.materialLotNo,
        materialQty: u.materialQty,
        materialUom: "",
        assignedStepName: "",
        pickOrderCode: "",
        pickedAt: null as string | null,
        materialItemName: "",
      }));

  upstreamSource.forEach((m) => {
    rows.push({
      "Section / 區塊": "BOM Upstream / BOM 上游",
      "Job Order / 工單": orDash(m.jobOrderCode),
      "Doc No. / 單號": orDash(m.pickOrderCode),
      "Item Code / 貨品編號": orDash(m.materialItemCode),
      "Lot / 批號": orDash(m.materialLotNo),
      "Item Name / 品名": orDash(m.materialItemName),
      "Step / 步驟": "",
      "Assigned Step / 對應製程": orDash(m.assignedStepName),
      "Qty / 數量": m.materialQty,
      "Scrap Qty / 損耗數量": "",
      "Defect Qty / 不良數量": "",
      "UOM / 單位": orDash(m.materialUom),
      "Operator / 操作員": "",
      "Equipment / 設備": "",
      "Status / 狀態": "",
      "Date/Time / 時間": orDash(m.pickedAt),
    });
  });

  data.bomTrace.downstream.forEach((d) => {
    rows.push({
      "Section / 區塊": "BOM Downstream / BOM 下游",
      "Job Order / 工單": orDash(d.jobOrderCode),
      "Doc No. / 單號": "",
      "Item Code / 貨品編號": orDash(d.finishedItemCode),
      "Lot / 批號": orDash(d.finishedLotNo),
      "Item Name / 品名": "",
      "Step / 步驟": "",
      "Assigned Step / 對應製程": "",
      "Qty / 數量": d.fgQty,
      "Scrap Qty / 損耗數量": "",
      "Defect Qty / 不良數量": "",
      "UOM / 單位": orDash(d.fgUom),
      "Operator / 操作員": "",
      "Equipment / 設備": "",
      "Status / 狀態": "",
      "Date/Time / 時間": "",
    });
  });

  data.bomTrace.bomRecipe.forEach((r) => {
    rows.push({
      "Section / 區塊": "BOM Recipe / BOM 配方",
      "Job Order / 工單": "",
      "Doc No. / 單號": "",
      "Item Code / 貨品編號": orDash(r.materialItemCode),
      "Lot / 批號": "",
      "Item Name / 品名": orDash(r.materialItemName),
      "Step / 步驟": "",
      "Assigned Step / 對應製程": orDash(r.assignedStepName),
      "Qty / 數量": r.qtyPerUnit,
      "Scrap Qty / 損耗數量": "",
      "Defect Qty / 不良數量": "",
      "UOM / 單位": orDash(r.uom),
      "Operator / 操作員": "",
      "Equipment / 設備": "",
      "Status / 狀態": "",
      "Date/Time / 時間": "",
    });
  });

  presentation.joPicks
    .slice()
    .sort((a, b) => (b.pickedAt ?? "").localeCompare(a.pickedAt ?? ""))
    .forEach((p) => {
      rows.push({
        "Section / 區塊": "JO Pick / 工單提料",
        "Job Order / 工單": "",
        "Doc No. / 單號": orDash(p.pickOrderCode),
        "Item Code / 貨品編號": orDash(p.materialItemCode),
        "Lot / 批號": orDash(p.materialLotNo),
        "Item Name / 品名": "",
        "Step / 步驟": "",
        "Assigned Step / 對應製程": orDash(p.assignedStepName),
        "Qty / 數量": p.qty,
        "Scrap Qty / 損耗數量": "",
        "Defect Qty / 不良數量": "",
        "UOM / 單位": orDash(p.uom),
        "Operator / 操作員": "",
        "Equipment / 設備": "",
        "Status / 狀態": "",
        "Date/Time / 時間": orDash(p.pickedAt),
      });
    });

  const productionRows =
    data.productionSteps.length > 0
      ? data.productionSteps
          .slice()
          .sort((a, b) => (a.seqNo ?? 0) - (b.seqNo ?? 0))
          .map((s) => ({
            "Section / 區塊": "Production Step / 生產步驟",
            "Job Order / 工單": orDash(data.joPrelude?.jobOrder.jobOrderCode),
            "Doc No. / 單號": "",
            "Item Code / 貨品編號": orDash(data.lot.itemCode),
            "Lot / 批號": orDash(data.lot.lotNo),
            "Item Name / 品名": orDash(data.lot.itemName),
            "Step / 步驟": orDash(s.stepName),
            "Assigned Step / 對應製程": orDash(s.description),
            "Qty / 數量": s.outputQty,
            "Scrap Qty / 損耗數量": s.scrapQty,
            "Defect Qty / 不良數量": s.defectQty,
            "UOM / 單位": orDash(data.lot.uom),
            "Operator / 操作員": orDash(s.operatorName),
            "Equipment / 設備":
              [s.equipmentCode, s.equipmentName].filter(Boolean).join(" · ") ||
              "—",
            "Status / 狀態": tr.productionStatus(s.status),
            "Date/Time / 時間": orDash(s.endTime || s.startTime),
          }))
      : presentation.production
          .slice()
          .sort((a, b) => (a.seqNo ?? 0) - (b.seqNo ?? 0))
          .map((s) => ({
            "Section / 區塊": "Production Step / 生產步驟",
            "Job Order / 工單": orDash(data.joPrelude?.jobOrder.jobOrderCode),
            "Doc No. / 單號": "",
            "Item Code / 貨品編號": orDash(data.lot.itemCode),
            "Lot / 批號": orDash(s.traceLotNo || data.lot.lotNo),
            "Item Name / 品名": orDash(data.lot.itemName),
            "Step / 步驟": orDash(s.stepName),
            "Assigned Step / 對應製程": orDash(s.description),
            "Qty / 數量": s.outputQty,
            "Scrap Qty / 損耗數量": s.scrapQty,
            "Defect Qty / 不良數量": s.defectQty,
            "UOM / 單位": orDash(data.lot.uom),
            "Operator / 操作員": orDash(s.operatorName),
            "Equipment / 設備": orDash(s.equipmentName || s.equipmentCode),
            "Status / 狀態": orDash(s.status),
            "Date/Time / 時間": orDash(s.endTime || s.startTime),
          }));

  rows.push(...productionRows);

  data.byproductLots.forEach((b) => {
    rows.push({
      "Section / 區塊": "Byproduct / 副產品",
      "Job Order / 工單": orDash(b.jobOrderCode),
      "Doc No. / 單號": "",
      "Item Code / 貨品編號": orDash(b.itemCode),
      "Lot / 批號": orDash(b.lotNo),
      "Item Name / 品名": orDash(b.itemName),
      "Step / 步驟": orDash(b.processStepName),
      "Assigned Step / 對應製程": "",
      "Qty / 數量": b.qty,
      "Scrap Qty / 損耗數量": "",
      "Defect Qty / 不良數量": "",
      "UOM / 單位": orDash(b.uom),
      "Operator / 操作員": "",
      "Equipment / 設備": "",
      "Status / 狀態": "",
      "Date/Time / 時間": orDash(b.producedAt),
    });
  });

  data.lotRelations.forEach((r) => {
    rows.push({
      "Section / 區塊": "Lot Relation / 拆批關聯",
      "Job Order / 工單": "",
      "Doc No. / 單號": orDash(r.relationType),
      "Item Code / 貨品編號": orDash(r.itemCode),
      "Lot / 批號": orDash(r.lotNo),
      "Item Name / 品名": orDash(r.itemName),
      "Step / 步驟": "",
      "Assigned Step / 對應製程": "",
      "Qty / 數量": r.qty,
      "Scrap Qty / 損耗數量": "",
      "Defect Qty / 不良數量": "",
      "UOM / 單位": "",
      "Operator / 操作員": "",
      "Equipment / 設備": "",
      "Status / 狀態": orDash(r.productLotNo),
      "Date/Time / 時間": orDash(r.timestamp),
    });
  });

  return ensureRows(rows, productionEmpty());
};

/** Build the 8 classified sheets without triggering download (for tests). */
export const buildItemLotTraceSheets = (
  data: ItemLotTraceResponse,
  compiledGraph: CompiledTraceGraph,
  t: TFunction,
  exportLabels: ItemLotTraceExportLabels,
  exportAt: string = new Date().toISOString().replace("T", " ").slice(0, 19),
): MultiSheetSpec[] => {
  const tr = createTraceLabelTranslator(t);
  return [
    {
      name: SHEET.summary,
      rows: buildSummarySheet(data, exportLabels, exportAt),
    },
    { name: SHEET.timeline, rows: buildTimelineSheet(compiledGraph, t, tr) },
    { name: SHEET.origins, rows: buildOriginsSheet(data, tr) },
    { name: SHEET.qc, rows: buildQcSheet(data, tr) },
    { name: SHEET.warehouse, rows: buildWarehouseSheet(data, tr) },
    { name: SHEET.outbound, rows: buildOutboundSheet(data, tr) },
    { name: SHEET.stockTake, rows: buildStockTakeSheet(data) },
    {
      name: SHEET.production,
      rows: buildProductionSheet(data, compiledGraph, tr),
    },
  ];
};

export const ITEM_LOT_TRACE_SHEET_NAMES = Object.values(SHEET);

/** FP-MTMS Version Checklist | Functions Ref. No. 15 | v1.0.1 | 2026-07-20 */
export const exportItemLotTraceXlsx = (
  data: ItemLotTraceResponse,
  compiledGraph: CompiledTraceGraph,
  t: TFunction,
): void => {
  const exportLabels: ItemLotTraceExportLabels = {
    bomDirectionFg: t("bomDirectionFG"),
    bomDirectionMaterial: t("bomDirectionMaterial"),
    bomDirectionUnknown: t("bomDirectionUnknown"),
  };
  const sheets = buildItemLotTraceSheets(data, compiledGraph, t, exportLabels);
  exportMultiSheetToXlsx(sheets, buildItemLotTraceFilename(data));
};
