"use client";

import { startTransition, useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
  Alert,
  Box,
  Button,
  Checkbox,
  CircularProgress,
  Grid,
  Modal,
  Paper,
  Stack,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TablePagination,
  TableRow,
  TextField,
  Typography,
} from "@mui/material";
import { useSession } from "next-auth/react";
import { useTranslation } from "react-i18next";
import dayjs from "dayjs";
import arraySupport from "dayjs/plugin/arraySupport";
import { OUTPUT_DATE_FORMAT, arrayToDayjs } from "@/app/utils/formatUtil";
import { SessionWithTokens } from "@/config/authConfig";
import {
  fetchConsumableWorkbenchPickOrderLotsHierarchical,
  reloadConsumableWorkbenchPickOrderLotsHierarchical,
  suggestPickOrderWorkbenchV2,
} from "@/app/api/pickOrder/actions";
import { workbenchScanPick } from "@/app/api/doworkbench/actions";
import { fetchStockInLineInfo } from "@/app/api/po/actions";
import { fetchLotDetail } from "@/app/api/inventory/actions";
import WorkbenchLotLabelPrintModal from "@/components/DoWorkbench/WorkbenchLotLabelPrintModal";
import TestQrCodeProvider from "../QrCodeScannerProvider/TestQrCodeProvider";
import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider";
import ScanStatusAlert from "@/components/common/ScanStatusAlert";
import {
  buildUnpickableScanRowPatch,
  getWorkbenchSourceLotStatusSummary,
  inferUnpickableScanAvailability,
  isExpiredWorkbenchReminderMessage,
  isInventoryLotLineUnavailable,
  isLotAvailabilityExpired,
  isNoLotWorkbenchRow,
  isWorkbenchSourceLotExpired,
  isWorkbenchZeroCompleteLot,
  translateWorkbenchRejectMessage,
  type UnpickableScanAvailability,
} from "@/utils/workbenchPickLotUtils";

dayjs.extend(arraySupport);

type LineRow = {
  rowKey: string;
  pickOrderId: number;
  pickOrderLineId: number;
  pickOrderCode: string;
  itemCode: string;
  itemName: string;
  itemId?: number;
  requiredQty: number;
  pickedQty: number;
  stockUnit: string;
  uomId?: number;
  status: string;
  lotsRaw: unknown[];
};

type PickOrderTopRow = {
  rowKey: string;
  pickOrderId: number;
  pickOrderCode: string;
  targetDate: string;
  status: string;
  lineCount: number;
  completedLineCount: number;
  lines: LineRow[];
};

type LotRow = {
  key: string;
  pickOrderId: number;
  pickOrderLineId: number;
  pickOrderCode: string;
  itemCode: string;
  itemName: string;
  uomDesc: string;
  uomId?: number;
  requiredQty: number;
  pickOrderLineRequiredQty?: number;
  availableQty: number;
  itemTotalAvailableQty?: number | null;
  stockOutLineId: number;
  status: string;
  pickedQty: number;
  stockOutLineQty?: number;
  lotNo: string;
  location: string;
  itemId?: number;
  stockInLineId?: number;
  suggestedPickLotId?: number;
  lotAvailability?: string;
  lotStatus?: string;
  expiryDate?: string;
  noLot?: boolean;
  stockOutLineRejectMessage?: string;
};

type ConfirmLotState = {
  lotNo: string;
  itemCode: string;
  itemName: string;
  stockInLineId?: number;
  row: LotRow;
};

type LotRowIndexes = {
  byItemId: Map<number, LotRow[]>;
  byStockInLineId: Map<number, LotRow[]>;
  activeLotsByItemId: Map<number, LotRow[]>;
};

const ManualLotConfirmationModal: React.FC<{
  open: boolean;
  onClose: () => void;
  onConfirm: (expectedLotNo: string, scannedLotNo: string) => void;
  expectedLot: { lotNo: string; itemCode: string; itemName: string } | null;
  scannedLot: { lotNo: string; itemCode: string; itemName: string } | null;
  isLoading?: boolean;
}> = ({ open, onClose, onConfirm, expectedLot, scannedLot, isLoading = false }) => {
  const { t } = useTranslation("pickOrder");

  const [expectedLotInput, setExpectedLotInput] = useState("");
  const [scannedLotInput, setScannedLotInput] = useState("");
  const [error, setError] = useState("");
  useEffect(() => {
    if (!open) return;
    setExpectedLotInput(expectedLot?.lotNo || "");
    setScannedLotInput(scannedLot?.lotNo || "");
    setError("");
  }, [expectedLot, open, scannedLot]);
  return (
    <Modal open={open} onClose={onClose}>
      <Box sx={{ position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", bgcolor: "background.paper", p: 3, borderRadius: 2, minWidth: 500 }}>
        <Typography variant="h6" gutterBottom color="warning.main">
          {t("Manual Lot Confirmation")}
        </Typography>
        <TextField
          fullWidth
          size="small"
          label={t("Expected Lot Number")}
          value={expectedLotInput}
          onChange={(e) => {
            setExpectedLotInput(e.target.value);
            setError("");
          }}
          sx={{ mb: 2 }}
        />
        <TextField
          fullWidth
          size="small"
          label={t("Scanned Lot Number")}
          value={scannedLotInput}
          onChange={(e) => {
            setScannedLotInput(e.target.value);
            setError("");
          }}
        />
        {error ? (
          <Box sx={{ mt: 2, p: 1, borderRadius: 1, bgcolor: "#ffebee" }}>
            <Typography variant="body2" color="error">
              {error}
            </Typography>
          </Box>
        ) : null}
        <Box sx={{ mt: 2, display: "flex", justifyContent: "flex-end", gap: 2 }}>
          <Button onClick={onClose} variant="outlined" disabled={isLoading}>
            {t("Cancel")}
          </Button>
          <Button
            onClick={() => {
              if (!expectedLotInput.trim() || !scannedLotInput.trim()) {
                setError(t("Please enter both expected and scanned lot numbers."));
                return;
              }
              if (expectedLotInput.trim() === scannedLotInput.trim()) {
                setError(t("Expected and scanned lot numbers cannot be the same."));
                return;
              }
              onConfirm(expectedLotInput.trim(), scannedLotInput.trim());
            }}
            variant="contained"
            color="warning"
            disabled={isLoading}
          >
            {isLoading ? t("Processing...") : t("Confirm")}
          </Button>
        </Box>
      </Box>
    </Modal>
  );
};

interface Props {
  filterArgs?: Record<string, unknown>;
}

const toNum = (v: unknown, d = 0): number => {
  const n = Number(v);
  return Number.isFinite(n) ? n : d;
};

const toStr = (v: unknown): string => (typeof v === "string" ? v : "");

const isCompletedStatus = (status: string | undefined): boolean => {
  const s = String(status || "").toLowerCase();
  return s === "completed" || s === "partially_completed" || s === "partially_complete";
};

const isCheckedStatus = (status: string | undefined): boolean =>
  String(status || "").toLowerCase() === "checked";

const isRejectedStatus = (status: string | undefined): boolean =>
  String(status || "").toLowerCase() === "rejected";

function safeDisplayTargetDate(targetDate: string | number[]): string {
  try {
    if (Array.isArray(targetDate) && targetDate.length >= 3) {
      return arrayToDayjs(targetDate).format(OUTPUT_DATE_FORMAT);
    }
    const value = typeof targetDate === "string" ? targetDate : String(targetDate ?? "");
    const d = dayjs(value);
    return d.isValid() ? d.format(OUTPUT_DATE_FORMAT) : "-";
  } catch {
    return "-";
  }
}

function lineHierarchicalNeedsSuggest(line: LineRow): boolean {
  if (isCompletedStatus(line.status)) return false;
  if (!line.lotsRaw.length) return true;
  return line.lotsRaw.every((lotRaw) => {
    const lot = lotRaw as Record<string, unknown>;
    return toNum(lot.stockOutLineId) <= 0;
  });
}

function flattenLotsFromPickOrder(po: PickOrderTopRow): LotRow[] {
  const rows: LotRow[] = [];
  po.lines.forEach((line) => {
    line.lotsRaw.forEach((lotRaw, i) => {
      const lot = lotRaw as Record<string, unknown>;
      const solId = toNum(lot.stockOutLineId);
      const lotId = toNum(lot.id ?? lot.lotId, i);
      const stockInLineId = toNum(lot.stockInLineId);
      rows.push({
        key: solId > 0 ? `sol:${solId}` : `line:${line.pickOrderLineId}:lot:${lotId}:${i}`,
        pickOrderId: po.pickOrderId,
        pickOrderLineId: line.pickOrderLineId,
        pickOrderCode: po.pickOrderCode,
        itemCode: line.itemCode,
        itemName: line.itemName,
        uomDesc: toStr(lot.stockUnit) || line.stockUnit,
        uomId: line.uomId,
        requiredQty: toNum(lot.requiredQty, line.requiredQty),
        pickOrderLineRequiredQty: line.requiredQty,
        availableQty: toNum(lot.availableQty),
        stockOutLineId: solId,
        status: toStr(lot.stockOutLineStatus ?? lot.processingStatus ?? "pending"),
        pickedQty: toNum(lot.actualPickQty ?? lot.stockOutLineQty),
        stockOutLineQty: toNum(lot.stockOutLineQty ?? lot.actualPickQty),
        lotNo: toStr(lot.lotNo),
        location: toStr(lot.location),
        itemId: line.itemId,
        stockInLineId: stockInLineId > 0 ? stockInLineId : undefined,
        suggestedPickLotId: toNum(lot.suggestedPickLotId) > 0 ? toNum(lot.suggestedPickLotId) : undefined,
        lotAvailability: toStr(lot.lotAvailability),
        lotStatus: toStr(lot.lotStatus),
        expiryDate: toStr(lot.expiryDate),
        noLot: lot.noLot === true || (solId > 0 && !toStr(lot.lotNo)),
      });
    });
  });
  return rows;
}

function sumLinePickedQtyFromLots(lots: unknown[]): number {
  return lots.reduce<number>((acc, lot) => {
    const row = lot as Record<string, unknown>;
    const st = String(row?.stockOutLineStatus ?? row?.processingStatus ?? "").toLowerCase();
    const counted =
      st === "completed" ||
      st === "complete" ||
      st === "partially_completed" ||
      st === "partially_complete";
    if (!counted) return acc;
    return acc + toNum(row?.actualPickQty ?? row?.stockOutLineQty);
  }, 0);
}

function mapHierarchicalToPickOrders(data: unknown): PickOrderTopRow[] {
  const root = data as { pickOrders?: unknown[] };
  const pos = Array.isArray(root?.pickOrders) ? root.pickOrders : [];
  return pos.map((poRaw) => {
    const po = poRaw as Record<string, unknown>;
    const pickOrderId = toNum(po?.pickOrderId);
    const pickOrderCode = toStr(po?.pickOrderCode);
    const rawLines = Array.isArray(po?.pickOrderLines) ? po.pickOrderLines : [];
    const lines: LineRow[] = rawLines.map((lineRaw, idx) => {
      const line = lineRaw as Record<string, unknown>;
      const item = (line?.item as Record<string, unknown>) || {};
      const lots = Array.isArray(line?.lots) ? line.lots : [];
      const pickOrderLineId = toNum(line?.id, idx);
      return {
        rowKey: `po:${pickOrderId}:line:${pickOrderLineId}`,
        pickOrderId,
        pickOrderLineId,
        pickOrderCode,
        itemCode: toStr(item?.code),
        itemName: toStr(item?.name),
        itemId: toNum(item?.id) || undefined,
        requiredQty: toNum(line?.requiredQty),
        pickedQty: sumLinePickedQtyFromLots(lots),
        stockUnit: toStr(item?.uomDesc ?? item?.uomCode),
        uomId: toNum(item?.uomId) > 0 ? toNum(item?.uomId) : undefined,
        status: toStr(line?.status),
        lotsRaw: lots,
      };
    });
    const completedLineCount = lines.filter((l) => isCompletedStatus(l.status)).length;
    return {
      rowKey: `po:${pickOrderId}`,
      pickOrderId,
      pickOrderCode,
      targetDate: toStr(po?.targetDate ?? ""),
      status: toStr(po?.status),
      lineCount: lines.length,
      completedLineCount,
      lines,
    };
  });
}

/** FP-MTMS Version Checklist | Functions Ref. No. 28 | v1.0.2 | 2026-08-03 */
const WorkbenchPickExecution: React.FC<Props> = ({ filterArgs }) => {
  const { t } = useTranslation("pickOrder");
  const { data: session } = useSession() as { data: SessionWithTokens | null };
  const userId = session?.id ? parseInt(session.id, 10) : 0;

  const [pickOrders, setPickOrders] = useState<PickOrderTopRow[]>([]);
  const [pickOrderLoading, setPickOrderLoading] = useState(false);
  const [poPagingController, setPoPagingController] = useState({ pageNum: 1, pageSize: 10 });
  const [totalCountPickOrders, setTotalCountPickOrders] = useState(0);
  const localizeBackendMessage = (msg: unknown, fallbackKey: string) => {
    const text = typeof msg === "string" ? msg.trim() : "";
    if (!text) return t(fallbackKey);
    return t(text, { defaultValue: text });
  };
  const [selectedPickOrderId, setSelectedPickOrderId] = useState<number | null>(null);

  const [lotRows, setLotRows] = useState<LotRow[]>([]);
  const [qtyBySolId, setQtyBySolId] = useState<Record<number, number>>({});
  const [qtyEditableBySolId, setQtyEditableBySolId] = useState<Record<number, boolean>>({});
  const [lotPagingController, setLotPagingController] = useState({ pageNum: 0, pageSize: -1 });
  const [loading, setLoading] = useState(false);
  const [submittingSolId, setSubmittingSolId] = useState<number | null>(null);
  const [message, setMessage] = useState("");
  const [error, setError] = useState("");
  const [workbenchLotLabelModalOpen, setWorkbenchLotLabelModalOpen] = useState(false);
  const [workbenchLotLabelContextLot, setWorkbenchLotLabelContextLot] = useState<LotRow | null>(null);
  const [workbenchLotLabelInitialPayload, setWorkbenchLotLabelInitialPayload] =
    useState<{ itemId: number; stockInLineId: number } | null>(null);
  const [workbenchLotLabelReminderText, setWorkbenchLotLabelReminderText] = useState<string | null>(null);
  const [lotConfirmationOpen, setLotConfirmationOpen] = useState(false);
  const [lotConfirmationError, setLotConfirmationError] = useState<string | null>(null);
  const [expectedLotData, setExpectedLotData] = useState<ConfirmLotState | null>(null);
  const [scannedLotData, setScannedLotData] = useState<ConfirmLotState | null>(null);
  const [isConfirmingLot, setIsConfirmingLot] = useState(false);
  const [manualLotConfirmationOpen, setManualLotConfirmationOpen] = useState(false);
  const [qrScanError, setQrScanError] = useState(false);
  const [qrScanSuccess, setQrScanSuccess] = useState(false);
  const [qrScanErrorMsg, setQrScanErrorMsg] = useState("");
  const [qrScanSuccessMsg, setQrScanSuccessMsg] = useState("");
  const lastProcessedQrRef = useRef<string>("");
  const processedQrCodesRef = useRef<Set<string>>(new Set());
  const lotConfirmLastQrRef = useRef<string>("");
  const lotConfirmSkipNextScanRef = useRef<boolean>(false);
  const lotConfirmOpenedAtRef = useRef<number>(0);

  const { values: qrValues, isScanning, startScan, resetScan } = useQrCodeScannerContext();

  const paginatedPickOrders = useMemo(() => {
    const start = (poPagingController.pageNum - 1) * poPagingController.pageSize;
    return pickOrders.slice(start, start + poPagingController.pageSize);
  }, [pickOrders, poPagingController]);

  const paginatedLotRows = useMemo(() => {
    if (lotPagingController.pageSize === -1) return lotRows;
    const start = lotPagingController.pageNum * lotPagingController.pageSize;
    return lotRows.slice(start, start + lotPagingController.pageSize);
  }, [lotRows, lotPagingController]);

  const lotRowIndexes = useMemo<LotRowIndexes>(() => {
    const byItemId = new Map<number, LotRow[]>();
    const byStockInLineId = new Map<number, LotRow[]>();
    const activeLotsByItemId = new Map<number, LotRow[]>();

    for (const row of lotRows) {
      const itemId = Number(row.itemId);
      const stockInLineId = Number(row.stockInLineId);
      const isRejected = isRejectedStatus(row.status) || String(row.lotAvailability || "").toLowerCase() === "rejected";
      const isUnavailable = isInventoryLotLineUnavailable(row);
      const isExpired = isLotAvailabilityExpired(row) || isWorkbenchSourceLotExpired(row);
      const isActive =
        row.stockOutLineId > 0 &&
        !isCompletedStatus(row.status) &&
        !isCheckedStatus(row.status) &&
        !isRejected &&
        !isUnavailable &&
        !isExpired;

      if (Number.isFinite(itemId) && itemId > 0) {
        if (!byItemId.has(itemId)) byItemId.set(itemId, []);
        byItemId.get(itemId)!.push(row);
        if (isActive) {
          if (!activeLotsByItemId.has(itemId)) activeLotsByItemId.set(itemId, []);
          activeLotsByItemId.get(itemId)!.push(row);
        }
      }

      if (Number.isFinite(stockInLineId) && stockInLineId > 0) {
        if (!byStockInLineId.has(stockInLineId)) byStockInLineId.set(stockInLineId, []);
        byStockInLineId.get(stockInLineId)!.push(row);
      }
    }

    return { byItemId, byStockInLineId, activeLotsByItemId };
  }, [lotRows]);

  const loadHierarchicalPickOrders = useCallback(
    async (reload = false) => {
      if (!userId) return [] as PickOrderTopRow[];
      setPickOrderLoading(true);
      setError("");
      try {
        const data = reload
          ? await reloadConsumableWorkbenchPickOrderLotsHierarchical(userId)
          : await fetchConsumableWorkbenchPickOrderLotsHierarchical(userId);
        const orders = mapHierarchicalToPickOrders(data);
        setPickOrders(orders);
        setTotalCountPickOrders(orders.length);
        return orders;
      } catch (e) {
        console.error(e);
        setError(t("Load released pick orders failed"));
        setPickOrders([]);
        setTotalCountPickOrders(0);
        return [] as PickOrderTopRow[];
      } finally {
        setPickOrderLoading(false);
      }
    },
    [t, userId],
  );

  const applyLotsFromPickOrder = useCallback((po: PickOrderTopRow) => {
    setLotRows(flattenLotsFromPickOrder(po));
    setQtyEditableBySolId({});
  }, []);

  const ensurePickOrderLotsIfNeeded = useCallback(
    async (po: PickOrderTopRow) => {
      if (!userId || !po.lines.some(lineHierarchicalNeedsSuggest)) return;
      setLoading(true);
      setError("");
      try {
        const suggestRes = await suggestPickOrderWorkbenchV2(po.pickOrderId, userId);
        if (suggestRes.code !== "SUCCESS") {
          setError(t("Suggest pick failed"));
          return;
        }
        const data = await reloadConsumableWorkbenchPickOrderLotsHierarchical(userId);
        const orders = mapHierarchicalToPickOrders(data);
        setPickOrders(orders);
        setTotalCountPickOrders(orders.length);
        const refreshed = orders.find((p) => p.pickOrderId === po.pickOrderId);
        if (refreshed) applyLotsFromPickOrder(refreshed);
      } catch (e) {
        console.error(e);
        setError(t("Load workbench data failed"));
      } finally {
        setLoading(false);
      }
    },
    [applyLotsFromPickOrder, t, userId],
  );

  const refreshWorkbenchAfterMutation = useCallback(async () => {
    const latestOrders = (await loadHierarchicalPickOrders(true)) || [];
    if (
      selectedPickOrderId != null &&
      !latestOrders.some((p) => p.pickOrderId === selectedPickOrderId)
    ) {
      setSelectedPickOrderId(null);
      setLotRows([]);
      setQtyBySolId({});
      setQtyEditableBySolId({});
      setLotPagingController({ pageNum: 0, pageSize: -1 });
      return;
    }
    if (selectedPickOrderId != null) {
      const po = latestOrders.find((p) => p.pickOrderId === selectedPickOrderId);
      if (po) applyLotsFromPickOrder(po);
    }
  }, [applyLotsFromPickOrder, loadHierarchicalPickOrders, selectedPickOrderId]);

  useEffect(() => {
    if (userId) void loadHierarchicalPickOrders(false);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [userId, filterArgs, loadHierarchicalPickOrders]);

  const submitRow = useCallback(
    async (row: LotRow, forceQty?: number) => {
      if (!userId) return;
      if (!row.stockOutLineId) {
        setError(t("No stock out line for this lot"));
        return;
      }
      const qtyInput = qtyBySolId[row.stockOutLineId];
      const qtyValue = forceQty ?? (typeof qtyInput === "number" && Number.isFinite(qtyInput) ? qtyInput : undefined);
      setSubmittingSolId(row.stockOutLineId);
      setError("");
      setMessage("");
      try {
        const res = await workbenchScanPick({
          stockOutLineId: row.stockOutLineId,
          lotNo: row.lotNo.trim(),
          ...(Number.isFinite(Number(row.stockInLineId)) && Number(row.stockInLineId) > 0
            ? { stockInLineId: Number(row.stockInLineId) }
            : {}),
          ...(typeof qtyValue === "number" && Number.isFinite(qtyValue) ? { qty: qtyValue } : {}),
          userId,
        });
        const ok = String(res.code || "").toUpperCase() === "SUCCESS";
        if (!ok) {
          const errMsg = localizeBackendMessage(res.message, "Scan pick failed");
          setError(errMsg);
          setQrScanErrorMsg(errMsg);
          startTransition(() => {
            setQrScanError(true);
            setQrScanSuccess(false);
          });
          return;
        }
        const okMsg = localizeBackendMessage(res.message, "Scan pick success");
        setMessage(okMsg);
        setQrScanSuccessMsg(okMsg);
        startTransition(() => {
          setQrScanError(false);
          setQrScanSuccess(true);
        });
        setWorkbenchLotLabelModalOpen(false);
        setWorkbenchLotLabelContextLot(null);
        setWorkbenchLotLabelInitialPayload(null);
        await refreshWorkbenchAfterMutation();
      } catch (e) {
        console.error(e);
        setError(t("Scan pick failed"));
        startTransition(() => {
          setQrScanError(true);
          setQrScanSuccess(false);
          setQrScanErrorMsg(t("Scan pick failed"));
        });
      } finally {
        setSubmittingSolId(null);
      }
    },
    [qtyBySolId, refreshWorkbenchAfterMutation, t, userId],
  );

  const hasQtyOverrideBySolId = useCallback(
    (stockOutLineId: number) => Object.prototype.hasOwnProperty.call(qtyBySolId, stockOutLineId),
    [qtyBySolId],
  );

  const resolveSingleSubmitQty = useCallback(
    (lot: LotRow): number => {
      const override = qtyBySolId[lot.stockOutLineId];
      if (typeof override === "number" && Number.isFinite(override) && override >= 0) {
        return override;
      }
      const st = String(lot.status || "").toLowerCase();
      if (
        st === "completed" ||
        st === "partially_completed" ||
        st === "partially_complete"
      ) {
        return Number(lot.pickedQty ?? lot.stockOutLineQty ?? 0);
      }
      if (isWorkbenchZeroCompleteLot(lot)) return 0;
      return Number(lot.requiredQty) || 0;
    },
    [qtyBySolId],
  );

  const workbenchScanPickQtyFromLot = useCallback(
    (lot: LotRow) => {
      const hasExplicitOverride = hasQtyOverrideBySolId(lot.stockOutLineId);
      const n = Number(resolveSingleSubmitQty(lot));
      if (hasExplicitOverride && Number.isFinite(n) && n === 0) return { qty: 0 } as const;
      if (!Number.isFinite(n) || n <= 0) return {};
      return { qty: n } as const;
    },
    [hasQtyOverrideBySolId, resolveSingleSubmitQty],
  );

  const resolveLockedSubmitQtyDisplay = useCallback(
    (lot: LotRow): number => {
      if (isWorkbenchZeroCompleteLot(lot)) return 0;
      const st = String(lot.status || "").toLowerCase();
      if (
        st === "completed" ||
        st === "partially_completed" ||
        st === "partially_complete"
      ) {
        return Number(lot.pickedQty ?? lot.stockOutLineQty ?? 0);
      }
      return resolveSingleSubmitQty(lot);
    },
    [resolveSingleSubmitQty],
  );

  const workbenchLotLabelStatusBanner = useMemo(() => {
    if (!workbenchLotLabelModalOpen || !workbenchLotLabelContextLot) {
      return {
        text: undefined as string | undefined,
        severity: undefined as "success" | "warning" | "error" | undefined,
      };
    }
    const reminder = workbenchLotLabelReminderText?.trim() ?? "";
    if (reminder && isExpiredWorkbenchReminderMessage(reminder)) {
      return { text: "此批號狀態：已過期", severity: "error" as const };
    }
    const s = getWorkbenchSourceLotStatusSummary(workbenchLotLabelContextLot);
    return { text: s.text, severity: s.severity };
  }, [workbenchLotLabelModalOpen, workbenchLotLabelContextLot, workbenchLotLabelReminderText]);

  const handleJustComplete = useCallback(
    async (row: LotRow) => {
      if (!row.stockOutLineId) {
        setError(t("No stock out line for this lot"));
        return;
      }

      const lotNo = String(row.lotNo || "").trim();
      const isZeroComplete = isWorkbenchZeroCompleteLot(row);
      const isNoLot = isNoLotWorkbenchRow(row);
      const isUnavailable = isInventoryLotLineUnavailable(row);
      const hasExplicitOverride = hasQtyOverrideBySolId(row.stockOutLineId);
      const explicitQty = hasExplicitOverride ? Number(qtyBySolId[row.stockOutLineId]) : NaN;
      const qtyPayload = workbenchScanPickQtyFromLot(row);
      const wbJustQty = qtyPayload.qty;

      const canPostScanPick =
        isZeroComplete ||
        isNoLot ||
        isUnavailable ||
        (lotNo !== "" &&
          ((hasExplicitOverride && Number.isFinite(explicitQty) && explicitQty === 0) ||
            (wbJustQty != null && wbJustQty > 0)));

      if (!canPostScanPick) {
        const msg = t("Just Completed (workbench): requires a valid lot number and quantity.");
        setError(msg);
        startTransition(() => {
          setQrScanError(true);
          setQrScanSuccess(false);
          setQrScanErrorMsg(msg);
        });
        return;
      }

      const qtyToSend =
        isZeroComplete ||
        isNoLot ||
        isUnavailable ||
        (hasExplicitOverride && Number.isFinite(explicitQty) && explicitQty === 0)
          ? 0
          : Number(wbJustQty);

      await submitRow(row, qtyToSend);
    },
    [hasQtyOverrideBySolId, qtyBySolId, submitRow, t, workbenchScanPickQtyFromLot],
  );

  const handlePickOrderSelect = useCallback(
    async (row: PickOrderTopRow, checked: boolean) => {
      if (!checked) {
        if (selectedPickOrderId === row.pickOrderId) {
          setSelectedPickOrderId(null);
          setLotRows([]);
          setQtyBySolId({});
          setQtyEditableBySolId({});
          setLotPagingController({ pageNum: 0, pageSize: -1 });
        }
        return;
      }
      setSelectedPickOrderId(row.pickOrderId);
      setLotRows([]);
      setQtyBySolId({});
      setQtyEditableBySolId({});
      setLotPagingController({ pageNum: 0, pageSize: -1 });
      setMessage("");
      applyLotsFromPickOrder(row);
      await ensurePickOrderLotsIfNeeded(row);
    },
    [applyLotsFromPickOrder, ensurePickOrderLotsIfNeeded, selectedPickOrderId],
  );

  const openWorkbenchLotLabelModalForLot = useCallback(
    (lot: LotRow, reminderText?: string | null) => {
      const itemId = Number(lot.itemId);
      const stockInLineId = Number(lot.stockInLineId);
      const solId = Number(lot.stockOutLineId);
      if (!Number.isFinite(itemId) || itemId <= 0 || !Number.isFinite(solId) || solId <= 0) {
        return;
      }
      setWorkbenchLotLabelContextLot(lot);
      if (Number.isFinite(stockInLineId) && stockInLineId > 0) {
        setWorkbenchLotLabelInitialPayload({ itemId, stockInLineId });
      } else {
        setWorkbenchLotLabelInitialPayload(null);
      }
      setWorkbenchLotLabelReminderText(
        reminderText ? translateWorkbenchRejectMessage(reminderText, t) : null,
      );
      setQrScanSuccess(false);
      setWorkbenchLotLabelModalOpen(true);
    },
    [t],
  );

  const patchLotRowForUnpickableScan = useCallback(
    (
      pickRow: LotRow,
      scannedLot: LotRow | null | undefined,
      availability: UnpickableScanAvailability,
    ) => {
      const solId = Number(pickRow.stockOutLineId);
      if (!solId) return;
      const rowPatch = buildUnpickableScanRowPatch(scannedLot, availability) as Partial<LotRow>;
      setLotRows((prev) =>
        prev.map((row) => (Number(row.stockOutLineId) === solId ? { ...row, ...rowPatch } : row)),
      );
    },
    [],
  );

  const markUnpickableScanSessionHandled = useCallback((latestQr: string) => {
    lastProcessedQrRef.current = latestQr;
    processedQrCodesRef.current.add(latestQr);
  }, []);

  /** Soft-fail / switch-PO: allow the same QR to be processed again. */
  const releaseProcessedQr = useCallback((qr: string) => {
    const key = String(qr || "").trim();
    if (!key) return;
    processedQrCodesRef.current.delete(key);
    if (lastProcessedQrRef.current === key) {
      lastProcessedQrRef.current = "";
    }
  }, []);

  const openUnpickableScanLotLabelModal = useCallback(
    (
      pickRow: LotRow,
      scannedLot: LotRow | null | undefined,
      reminderText: string,
      latestQr: string,
    ) => {
      const fromMsg = inferUnpickableScanAvailability(reminderText);
      const availability =
        fromMsg ??
        (isWorkbenchSourceLotExpired(scannedLot ?? pickRow)
          ? "expired"
          : isInventoryLotLineUnavailable(scannedLot ?? pickRow)
            ? "status_unavailable"
            : null);
      const mergedPickRow =
        availability != null
          ? ({ ...pickRow, ...buildUnpickableScanRowPatch(scannedLot, availability) } as LotRow)
          : pickRow;
      if (availability != null) {
        patchLotRowForUnpickableScan(pickRow, scannedLot, availability);
      }
      openWorkbenchLotLabelModalForLot(mergedPickRow, reminderText);
      markUnpickableScanSessionHandled(latestQr);
    },
    [
      markUnpickableScanSessionHandled,
      openWorkbenchLotLabelModalForLot,
      patchLotRowForUnpickableScan,
    ],
  );

  const handleWorkbenchLotLabelScanPick = useCallback(
    async ({ inventoryLotLineId, lotNo, qty }: { inventoryLotLineId: number; lotNo: string; qty?: number }) => {
      if (!userId) throw new Error(t("User not found"));
      if (!workbenchLotLabelContextLot?.stockOutLineId) {
        throw new Error(t("No stock out line for this lot"));
      }
      const fallbackQty = Number(
        resolveLockedSubmitQtyDisplay(workbenchLotLabelContextLot),
      );
      const res = await workbenchScanPick({
        stockOutLineId: workbenchLotLabelContextLot.stockOutLineId,
        inventoryLotLineId,
        lotNo,
        ...(typeof qty === "number" && Number.isFinite(qty)
          ? { qty }
          : Number.isFinite(fallbackQty) && fallbackQty >= 0
            ? { qty: fallbackQty }
            : {}),
        userId,
      });
      if (res.code !== "SUCCESS") {
        throw new Error((res.message as string) || t("Scan pick failed"));
      }
      await refreshWorkbenchAfterMutation();
      setWorkbenchLotLabelModalOpen(false);
      setWorkbenchLotLabelReminderText(null);
      setWorkbenchLotLabelContextLot(null);
      setWorkbenchLotLabelInitialPayload(null);
    },
    [
      refreshWorkbenchAfterMutation,
      resolveLockedSubmitQtyDisplay,
      t,
      userId,
      workbenchLotLabelContextLot,
    ],
  );

  const handleScanLotByLotNo = useCallback(
    async (lotNo: string) => {
      const normalized = String(lotNo || "").trim();
      if (!normalized) return;
      const target = lotRows.find(
        (r) =>
          String(r.lotNo || "").trim() === normalized &&
          r.stockOutLineId > 0 &&
          !isCompletedStatus(r.status) &&
          !isCheckedStatus(r.status),
      );
      if (!target) {
        setError(t("Lot not found in current line"));
        return;
      }
      await submitRow(target);
    },
    [lotRows, submitRow, t],
  );

  const resolveScanCandidate = useCallback(
    (rawQr: string): ConfirmLotState | null => {
      const latest = String(rawQr || "").trim();
      if (!latest) return null;
      try {
        const parsed = JSON.parse(latest);
        const stockInLineId = toNum(parsed?.stockInLineId);
        if (stockInLineId > 0) {
          const row = lotRows.find((r) => Number(r.stockInLineId) === stockInLineId && r.stockOutLineId > 0);
          if (!row) return null;
          return {
            lotNo: String(row.lotNo || "").trim(),
            itemCode: row.itemCode,
            itemName: row.itemName,
            stockInLineId,
            row,
          };
        }
      } catch {
        // non-json; fallback to lotNo match
      }
      const lotNo = latest.replace(/[{}]/g, "").trim();
      if (!lotNo) return null;
      const row = lotRows.find((r) => String(r.lotNo || "").trim() === lotNo && r.stockOutLineId > 0);
      if (!row) return null;
      return {
        lotNo,
        itemCode: row.itemCode,
        itemName: row.itemName,
        stockInLineId: row.stockInLineId,
        row,
      };
    },
    [lotRows],
  );

  const toConfirmLotState = useCallback((row: LotRow): ConfirmLotState => {
    return {
      lotNo: String(row.lotNo || "").trim(),
      itemCode: row.itemCode,
      itemName: row.itemName,
      stockInLineId: row.stockInLineId,
      row,
    };
  }, []);

  const toConfirmLotStateWithOverrides = useCallback(
    (row: LotRow, override: { lotNo?: string; stockInLineId?: number }): ConfirmLotState => {
      return {
        lotNo: String(override.lotNo ?? row.lotNo ?? "").trim(),
        itemCode: row.itemCode,
        itemName: row.itemName,
        stockInLineId: override.stockInLineId ?? row.stockInLineId,
        row,
      };
    },
    [],
  );

  const pickExpectedRowForSubstitution = useCallback((rows: LotRow[]): LotRow | null => {
    if (!rows.length) return null;
    const withLotNo = rows.filter((r) => String(r.lotNo || "").trim() !== "");
    if (withLotNo.length === 1) return withLotNo[0];
    if (withLotNo.length > 1) {
      const pending = withLotNo.find((r) => String(r.status || "").toLowerCase() === "pending");
      return pending || withLotNo[0];
    }
    return rows[0];
  }, []);

  const clearLotConfirmationState = useCallback((clearProcessedRefs = false) => {
    setLotConfirmationOpen(false);
    setLotConfirmationError(null);
    setExpectedLotData(null);
    setScannedLotData(null);
    lotConfirmLastQrRef.current = "";
    lotConfirmSkipNextScanRef.current = false;
    lotConfirmOpenedAtRef.current = 0;
    if (clearProcessedRefs) {
      setTimeout(() => {
        lastProcessedQrRef.current = "";
        processedQrCodesRef.current.clear();
      }, 100);
    }
  }, []);

  // Workbench no-hold: skip confirmLotSubstitution (classic hold move). Switch + pick via scan-pick only.
  const handleLotConfirmation = useCallback(
    async (overrideScanned?: ConfirmLotState, overrideExpected?: ConfirmLotState) => {
      const expected = overrideExpected ?? expectedLotData;
      const scanned = overrideScanned ?? scannedLotData;
      if (!expected || !scanned) return;
      setIsConfirmingLot(true);
      setLotConfirmationError(null);
      setError("");
      setMessage("");
      try {
        const res = await workbenchScanPick({
          stockOutLineId: expected.row.stockOutLineId,
          lotNo: scanned.lotNo,
          ...(Number.isFinite(Number(scanned.stockInLineId)) && Number(scanned.stockInLineId) > 0
            ? { stockInLineId: Number(scanned.stockInLineId) }
            : {}),
          ...workbenchScanPickQtyFromLot(expected.row),
          userId,
        });
        if (res.code !== "SUCCESS") {
          const msg = (res.message as string) || t("Workbench scan-pick failed.");
          setLotConfirmationError(msg);
          setError(msg);
          startTransition(() => {
            setQrScanError(true);
            setQrScanSuccess(false);
            setQrScanErrorMsg(msg);
          });
          return;
        }
        setMessage(t("Scan pick success"));
        startTransition(() => {
          setQrScanError(false);
          setQrScanSuccess(true);
          setQrScanSuccessMsg(t("Scan pick success"));
        });
        await refreshWorkbenchAfterMutation();
        clearLotConfirmationState(true);
      } catch (e) {
        console.error(e);
        const msg = t("Lot confirmation failed. Please try again.");
        setLotConfirmationError(msg);
        setError(msg);
        startTransition(() => {
          setQrScanError(true);
          setQrScanSuccess(false);
          setQrScanErrorMsg(msg);
        });
      } finally {
        setIsConfirmingLot(false);
      }
    },
    [
      clearLotConfirmationState,
      expectedLotData,
      refreshWorkbenchAfterMutation,
      scannedLotData,
      t,
      userId,
      workbenchScanPickQtyFromLot,
    ],
  );

  const handleLotConfirmationByRescan = useCallback(
    async (rawQr: string): Promise<boolean> => {
      if (!lotConfirmationOpen || !expectedLotData || !scannedLotData) return false;
      const latest = String(rawQr || "").trim();
      if (!latest) return false;

      let parsed: any;
      try {
        parsed = JSON.parse(latest);
      } catch {
        return false;
      }

      const rescannedItemId = toNum(parsed?.itemId);
      const rescannedStockInLineId = toNum(parsed?.stockInLineId);
      if (rescannedItemId <= 0 || rescannedStockInLineId <= 0) return false;

      const expectedItemId = Number(expectedLotData.row.itemId || 0);
      if (expectedItemId > 0 && rescannedItemId !== expectedItemId) return false;

      const expectedStockInLineId = Number(expectedLotData.stockInLineId || expectedLotData.row.stockInLineId || 0);
      const scannedStockInLineId = Number(scannedLotData.stockInLineId || scannedLotData.row.stockInLineId || 0);

      if (expectedStockInLineId > 0 && rescannedStockInLineId === expectedStockInLineId) {
        clearLotConfirmationState(false);
        await submitRow(expectedLotData.row);
        return true;
      }

      if (scannedStockInLineId > 0 && rescannedStockInLineId === scannedStockInLineId) {
        await handleLotConfirmation();
        return true;
      }

      const itemRows = lotRowIndexes.byItemId.get(rescannedItemId) || [];
      const rowByStockInLineId = itemRows.find(
        (r) =>
          Number(r.stockInLineId) === rescannedStockInLineId &&
          r.stockOutLineId > 0 &&
          !isCompletedStatus(r.status) &&
          !isCheckedStatus(r.status),
      );

      if (rowByStockInLineId) {
        await handleLotConfirmation(toConfirmLotState(rowByStockInLineId));
        return true;
      }

      try {
        const info = await fetchStockInLineInfo(rescannedStockInLineId);
        const rescannedLotNo = String(info?.lotNo || "").trim();
        if (!rescannedLotNo) return false;
        await handleLotConfirmation(
          toConfirmLotStateWithOverrides(expectedLotData.row, {
            lotNo: rescannedLotNo,
            stockInLineId: rescannedStockInLineId,
          }),
        );
      } catch {
        return false;
      }
      return true;
    },
    [
      clearLotConfirmationState,
      expectedLotData,
      handleLotConfirmation,
      lotConfirmationOpen,
      lotRowIndexes,
      scannedLotData,
      submitRow,
      toConfirmLotState,
      toConfirmLotStateWithOverrides,
    ],
  );

  const processOutsideQrCode = useCallback(
    async (rawQr: string) => {
      const latest = String(rawQr || "").trim();
      if (!latest) return;
      setError("");
      setMessage("");
      startTransition(() => {
        setQrScanError(false);
        setQrScanSuccess(false);
      });

      if (latest === "{2fic}") {
        setManualLotConfirmationOpen(true);
        return;
      }

      if (lotConfirmationOpen) {
        const handled = await handleLotConfirmationByRescan(latest);
        if (handled) return;
      }

      let parsed: any;
      try {
        parsed = JSON.parse(latest);
      } catch {
        setError(t("Invalid QR format. Expected JSON with itemId and stockInLineId."));
        startTransition(() => {
          setQrScanError(true);
          setQrScanSuccess(false);
          setQrScanErrorMsg(t("Invalid QR format. Expected JSON with itemId and stockInLineId."));
        });
        resetScan();
        return;
      }

      const scannedItemId = toNum(parsed?.itemId);
      const scannedStockInLineId = toNum(parsed?.stockInLineId);
      if (scannedItemId <= 0 || scannedStockInLineId <= 0) {
        setError(t("Invalid QR data. itemId and stockInLineId are required."));
        startTransition(() => {
          setQrScanError(true);
          setQrScanSuccess(false);
          setQrScanErrorMsg(t("Invalid QR data. itemId and stockInLineId are required."));
        });
        resetScan();
        return;
      }

      const activeSuggestedLots = lotRowIndexes.activeLotsByItemId.get(scannedItemId) || [];
      const allLotsForItem = lotRowIndexes.byItemId.get(scannedItemId) || [];
      if (allLotsForItem.length === 0) {
        setError(t("Scanned item is not found in current line"));
        startTransition(() => {
          setQrScanError(true);
          setQrScanSuccess(false);
          setQrScanErrorMsg(t("Scanned item is not found in current line"));
        });
        resetScan();
        return;
      }

      // Case A: reject only if scanned lot UOM matches none of this item's POL UOMs
      // (same item may have multiple lines with different UOMs on one pick order)
      let scannedUomId = 0;
      const allowedUomIds = new Set(
        allLotsForItem
          .map((l) => Number(l?.uomId))
          .filter((id) => Number.isFinite(id) && id > 0),
      );
      const hasLineUoms = allowedUomIds.size > 0;
      try {
        const lotDetail = await fetchLotDetail(scannedStockInLineId);
        scannedUomId = Number(lotDetail?.uomId) || 0;
      } catch (e) {
        if (hasLineUoms) {
          console.warn(
            "[QR PROCESS] lot-detail UOM check failed; rejecting scan",
            e,
          );
          const msg = t(
            "This lot UOM does not match the pick line. Please scan another lot.",
          );
          setError(msg);
          startTransition(() => {
            setQrScanError(true);
            setQrScanSuccess(false);
            setQrScanErrorMsg(msg);
          });
          releaseProcessedQr(latest);
          return;
        }
      }
      if (
        hasLineUoms &&
        (!(scannedUomId > 0) || !allowedUomIds.has(scannedUomId))
      ) {
        const msg = t(
          "This lot UOM does not match the pick line. Please scan another lot.",
        );
        setError(msg);
        startTransition(() => {
          setQrScanError(true);
          setQrScanSuccess(false);
          setQrScanErrorMsg(msg);
        });
        releaseProcessedQr(latest);
        return;
      }

      // Strict UOM filter — never fall back to other UOM lines
      const filterByScannedUom = (rows: LotRow[]) => {
        if (!(scannedUomId > 0) || !hasLineUoms) return rows;
        return rows.filter((r) => Number(r.uomId) === scannedUomId);
      };
      const activeForUom = filterByScannedUom(activeSuggestedLots);
      const allForUom = filterByScannedUom(allLotsForItem);
      if (hasLineUoms && scannedUomId > 0 && allForUom.length === 0) {
        const msg = t(
          "This lot UOM does not match the pick line. Please scan another lot.",
        );
        setError(msg);
        startTransition(() => {
          setQrScanError(true);
          setQrScanSuccess(false);
          setQrScanErrorMsg(msg);
        });
        releaseProcessedQr(latest);
        return;
      }
      const expectedPool = activeForUom.length > 0 ? activeForUom : allForUom;
      let expectedRow = pickExpectedRowForSubstitution(expectedPool) || allForUom[0];
      if (!expectedRow) {
        setError(t("Scanned item is not found in current line"));
        startTransition(() => {
          setQrScanError(true);
          setQrScanSuccess(false);
          setQrScanErrorMsg(t("Scanned item is not found in current line"));
        });
        releaseProcessedQr(latest);
        return;
      }

      const scannedRows = lotRowIndexes.byStockInLineId.get(scannedStockInLineId) || [];
      let scannedRowInItem =
        scannedRows.find(
          (r) =>
            Number(r.itemId) === scannedItemId &&
            r.stockOutLineId > 0 &&
            (!(scannedUomId > 0) || !hasLineUoms || Number(r.uomId) === scannedUomId),
        ) ||
        (!(scannedUomId > 0) || !hasLineUoms
          ? scannedRows.find(
              (r) =>
                Number(r.itemId) === scannedItemId &&
                r.stockOutLineId > 0,
            )
          : undefined) ||
        null;

      if (scannedRowInItem && isRejectedStatus(scannedRowInItem.status)) {
        const rejectMsg =
          scannedRowInItem.stockOutLineRejectMessage ||
          t("This lot is rejected. Please scan another lot.");
        setError(rejectMsg);
        startTransition(() => {
          setQrScanError(true);
          setQrScanSuccess(false);
          setQrScanErrorMsg(rejectMsg);
        });
        return;
      }

      if (scannedRowInItem && isInventoryLotLineUnavailable(scannedRowInItem)) {
        startTransition(() => {
          setQrScanError(false);
          setQrScanSuccess(false);
        });
        setMessage(t("This lot is unavailable, please scan another lot."));
        openUnpickableScanLotLabelModal(
          scannedRowInItem,
          scannedRowInItem,
          t("This lot is not available, please scan another lot."),
          latest,
        );
        return;
      }

      if (scannedRowInItem && isWorkbenchSourceLotExpired(scannedRowInItem)) {
        startTransition(() => {
          setQrScanError(false);
          setQrScanSuccess(false);
        });
        openUnpickableScanLotLabelModal(
          scannedRowInItem,
          scannedRowInItem,
          `Lot is expired (expiry=${scannedRowInItem.expiryDate || "-"})`,
          latest,
        );
        return;
      }

      // Same lot already bound to a completed/checked SOL: only reuse for another
      // pending line with the SAME uomId (never cross UOM A ↔ B).
      if (
        scannedRowInItem &&
        (isCompletedStatus(scannedRowInItem.status) || isCheckedStatus(scannedRowInItem.status))
      ) {
        const completedPolId = Number(scannedRowInItem.pickOrderLineId);
        const completedSolId = Number(scannedRowInItem.stockOutLineId);
        const pendingPoolBase = activeForUom.length > 0 ? activeForUom : allForUom;
        const pendingSameUom = pendingPoolBase.filter((r) => {
          if (!(r.stockOutLineId > 0)) return false;
          if (isCompletedStatus(r.status) || isCheckedStatus(r.status) || isRejectedStatus(r.status)) {
            return false;
          }
          if (Number(r.stockOutLineId) === completedSolId) return false;
          if (completedPolId > 0 && Number(r.pickOrderLineId) === completedPolId) return false;
          // Require explicit same UOM — do not reuse across different UOM lines
          if (scannedUomId > 0 && hasLineUoms) {
            return Number(r.uomId) === scannedUomId;
          }
          // Without line UOM metadata, do not auto-reuse (avoids A/B cross-pick)
          return false;
        });
        const nextExpected =
          pickExpectedRowForSubstitution(pendingSameUom) || pendingSameUom[0] || null;
        if (nextExpected) {
          expectedRow = nextExpected;
          // Force substitution path using scanned SIL, not the completed SOL row.
          scannedRowInItem = null;
        } else {
          const msg = t("This lot has already been picked. Please scan another lot.");
          setError(msg);
          startTransition(() => {
            setQrScanError(true);
            setQrScanSuccess(false);
            setQrScanErrorMsg(msg);
          });
          releaseProcessedQr(latest);
          return;
        }
      }

      let scannedState: ConfirmLotState | null = null;
      if (scannedRowInItem) {
        scannedState = toConfirmLotState(scannedRowInItem);
      } else {
        try {
          const info = await fetchStockInLineInfo(scannedStockInLineId);
          const scannedLotNo = String(info?.lotNo || "").trim();
          if (!scannedLotNo) {
            setError(t("Scanned lot is not found for current item"));
            startTransition(() => {
              setQrScanError(true);
              setQrScanSuccess(false);
              setQrScanErrorMsg(t("Scanned lot is not found for current item"));
            });
            resetScan();
            return;
          }
          scannedState = toConfirmLotStateWithOverrides(expectedRow, {
            lotNo: scannedLotNo,
            stockInLineId: scannedStockInLineId,
          });
        } catch {
          setError(t("Scanned lot is not found for current item"));
          startTransition(() => {
            setQrScanError(true);
            setQrScanSuccess(false);
            setQrScanErrorMsg(t("Scanned lot is not found for current item"));
          });
          resetScan();
          return;
        }
      }

      if (
        Number(expectedRow.stockInLineId) > 0 &&
        Number(scannedState.stockInLineId) > 0 &&
        Number(expectedRow.stockInLineId) === Number(scannedState.stockInLineId)
      ) {
        await submitRow(expectedRow);
        return;
      }

      await handleLotConfirmation(scannedState, toConfirmLotState(expectedRow));
    },
    [
      handleLotConfirmation,
      handleLotConfirmationByRescan,
      lotConfirmationOpen,
      pickExpectedRowForSubstitution,
      lotRowIndexes,
      openUnpickableScanLotLabelModal,
      releaseProcessedQr,
      resetScan,
      submitRow,
      t,
      toConfirmLotState,
      toConfirmLotStateWithOverrides,
    ],
  );

  useEffect(() => {
    if (!userId) return;
    if (!isScanning) startScan();
  }, [isScanning, startScan, userId]);

  useEffect(() => {
    // Clear on any PO selection change (including PO1 → PO2), so the same lot QR can be scanned again.
    lastProcessedQrRef.current = "";
    processedQrCodesRef.current.clear();
  }, [selectedPickOrderId]);

  useEffect(() => {
    if (!qrValues.length || lotRows.length === 0) return;
    const latest = String(qrValues[qrValues.length - 1] || "");
    if (!latest) return;

    if (lotConfirmationOpen) {
      if (isConfirmingLot) return;
      if (lotConfirmSkipNextScanRef.current) {
        lotConfirmSkipNextScanRef.current = false;
        lotConfirmLastQrRef.current = latest;
        return;
      }
      const sameQr = latest === lotConfirmLastQrRef.current;
      const justOpened =
        lotConfirmOpenedAtRef.current > 0 &&
        Date.now() - lotConfirmOpenedAtRef.current < 800;
      if (sameQr && justOpened) return;
      lotConfirmLastQrRef.current = latest;
      void (async () => {
        try {
          const handled = await handleLotConfirmationByRescan(latest);
          if (handled) resetScan();
        } catch (e) {
          console.error("Lot confirmation rescan failed:", e);
        }
      })();
      return;
    }

    if (workbenchLotLabelModalOpen && latest === lastProcessedQrRef.current) return;

    if (latest === lastProcessedQrRef.current || processedQrCodesRef.current.has(latest)) return;
    lastProcessedQrRef.current = latest;
    processedQrCodesRef.current.add(latest);
    if (processedQrCodesRef.current.size > 100) {
      const firstValue = processedQrCodesRef.current.values().next().value;
      if (firstValue !== undefined) processedQrCodesRef.current.delete(firstValue);
    }

    const run = async () => {
      try {
        // JO shortcut: {2fitestx,y} -> simulate JSON qr
        if (
          (latest.startsWith("{2fitest") || latest.startsWith("{2fittest")) &&
          latest.endsWith("}")
        ) {
          let content = "";
          if (latest.startsWith("{2fittest")) content = latest.substring(9, latest.length - 1);
          else content = latest.substring(8, latest.length - 1);
          const parts = content.split(",");
          if (parts.length === 2) {
            const itemId = parseInt(parts[0].trim(), 10);
            const stockInLineId = parseInt(parts[1].trim(), 10);
            if (!Number.isNaN(itemId) && !Number.isNaN(stockInLineId)) {
              await processOutsideQrCode(JSON.stringify({ itemId, stockInLineId }));
              return;
            }
          }
        }
        await processOutsideQrCode(latest);
      } finally {
        resetScan();
      }
    };
    void run();
  }, [
    handleLotConfirmationByRescan,
    isConfirmingLot,
    lotConfirmationOpen,
    lotRows.length,
    processOutsideQrCode,
    qrValues,
    resetScan,
    workbenchLotLabelModalOpen,
  ]);

  return (
    <TestQrCodeProvider
      lotData={lotRows}
      onScanLot={handleScanLotByLotNo}
      filterActive={(lot) =>
        lot.stockOutLineId > 0 &&
        !isCompletedStatus(lot.status) &&
        !isCheckedStatus(lot.status) &&
        String(lot.lotNo || "").trim() !== ""
      }
    >
      <Stack spacing={2}>
      <Paper variant="outlined" sx={{ p: 2 }}>
        <Stack spacing={1}>
          <Typography variant="subtitle2" color="text.secondary">
            {t("Pick Orders")}
          </Typography>
          <Grid container rowGap={1}>
            <Grid item xs={12}>
              {pickOrderLoading ? (
                <CircularProgress size={40} />
              ) : (
                <TableContainer component={Paper}>
                  <Table size="small">
                    <TableHead>
                      <TableRow>
                        <TableCell>{t("Selected")}</TableCell>
                        <TableCell>{t("Pick Order Code")}</TableCell>
                        <TableCell>{t("Pick Order Lines")}</TableCell>
                        <TableCell>{t("Target Date")}</TableCell>
                        <TableCell>{t("Status")}</TableCell>
                      </TableRow>
                    </TableHead>
                    <TableBody>
                      {paginatedPickOrders.length === 0 ? (
                        <TableRow>
                          <TableCell colSpan={5}>
                            <Typography variant="body2" color="text.secondary">
                              {t("No data available")}
                            </Typography>
                          </TableCell>
                        </TableRow>
                      ) : (
                        paginatedPickOrders.map((row) => (
                          <TableRow key={row.rowKey}>
                            <TableCell padding="checkbox">
                              <Checkbox
                                checked={selectedPickOrderId === row.pickOrderId}
                                onChange={(_, checked) => void handlePickOrderSelect(row, checked)}
                              />
                            </TableCell>
                            <TableCell>{row.pickOrderCode || "-"}</TableCell>
                            <TableCell>
                              {row.completedLineCount}/{row.lineCount}
                            </TableCell>
                            <TableCell>{safeDisplayTargetDate(row.targetDate)}</TableCell>
                            <TableCell>{t(row.status || "-")}</TableCell>
                          </TableRow>
                        ))
                      )}
                    </TableBody>
                  </Table>
                </TableContainer>
              )}
            </Grid>
            <Grid item xs={12}>
              <TablePagination
                component="div"
                count={totalCountPickOrders}
                page={poPagingController.pageNum - 1}
                rowsPerPage={poPagingController.pageSize}
                onPageChange={(_e, newPage) =>
                  setPoPagingController((prev) => ({ ...prev, pageNum: newPage + 1 }))
                }
                onRowsPerPageChange={(e) =>
                  setPoPagingController({
                    pageNum: 1,
                    pageSize: parseInt(e.target.value, 10),
                  })
                }
                rowsPerPageOptions={[10, 25, 50, 100]}
                labelRowsPerPage={t("Rows per page")}
              />
            </Grid>
          </Grid>
        </Stack>
      </Paper>

      {loading ? (
        <Stack direction="row" alignItems="center" spacing={1}>
          <CircularProgress size={24} />
          <Typography variant="body2">{t("Loading")}</Typography>
        </Stack>
      ) : null}
      <ScanStatusAlert
        error={qrScanError}
        success={qrScanSuccess}
        errorMessage={qrScanErrorMsg || t("QR code does not match any item in current orders.")}
        successMessage={qrScanSuccessMsg || t("QR code verified.")}
      />
      {error ? <Alert severity="error">{error}</Alert> : null}
      {message ? <Alert severity="success">{message}</Alert> : null}

      <Paper variant="outlined">
        <TableContainer>
          <Table size="small">
            <TableHead>
              <TableRow>
                <TableCell>{t("Index")}</TableCell>
                <TableCell>{t("Item Code")}</TableCell>
                <TableCell>{t("Route")}</TableCell>
                <TableCell>{t("Lot No")}</TableCell>
                <TableCell align="right">{t("Lot Required Pick Qty")}</TableCell>
                <TableCell align="right">{t("Available Qty")}</TableCell>
                <TableCell align="center">{t("Scan Result")}</TableCell>
                <TableCell align="center">{t("Qty will submit")}</TableCell>
                <TableCell align="center">{t("Submit Required Pick Qty")}</TableCell>
              </TableRow>
            </TableHead>
            <TableBody>
              {paginatedLotRows.map((r, idx) => {
                const lockedSubmitQty = resolveLockedSubmitQtyDisplay(r);
                const hasQtyOverride = hasQtyOverrideBySolId(r.stockOutLineId);
                const submitQtyDisplay = hasQtyOverride
                  ? Number(qtyBySolId[r.stockOutLineId])
                  : lockedSubmitQty;
                const rowStatus = String(r.status || "").toLowerCase();
                const isNoLot = isNoLotWorkbenchRow(r);
                const isRowRejected =
                  rowStatus === "rejected" || String(r.lotAvailability || "").toLowerCase() === "rejected";
                const isRowExpired =
                  isWorkbenchSourceLotExpired(r) && !isRowRejected;
                const isRowUnavailable = isInventoryLotLineUnavailable(r);
                const isRowComplete =
                  rowStatus === "completed" ||
                  rowStatus === "checked" ||
                  rowStatus === "partially_completed" ||
                  rowStatus === "partially_complete";

                return (
                <TableRow key={r.key}>
                  <TableCell>
                    {lotPagingController.pageSize === -1
                      ? idx + 1
                      : lotPagingController.pageNum * lotPagingController.pageSize + idx + 1}
                  </TableCell>
                  <TableCell>
                    {r.itemCode || "-"} <br />
                    {r.itemName || "-"} <br />
                    {r.uomDesc || "-"}
                  </TableCell>
                  <TableCell>{r.location || "-"}</TableCell>
                  <TableCell>
                    <Stack direction="row" spacing={1} alignItems="flex-start" justifyContent="space-between">
                      <Typography
                        variant="body2"
                        sx={{
                          color: isRowUnavailable
                            ? "error.main"
                            : isRowExpired || isLotAvailabilityExpired(r)
                              ? "warning.main"
                              : isRowComplete
                                ? "success.main"
                                : "inherit",
                        }}
                      >
                        {r.lotNo ? (
                          isRowExpired || isLotAvailabilityExpired(r) ? (
                            <>
                              {r.lotNo}{" "}
                              {t("is expired. Please check around have available QR code or not.")}
                            </>
                          ) : isRowUnavailable ? (
                            <>
                              {r.lotNo}{" "}
                              {t("is unavable. Please check around have available QR code or not.")}
                            </>
                          ) : (
                            r.lotNo
                          )
                        ) : (
                          <Box component="span" sx={{ fontSize: "0.85rem", lineHeight: 1.4 }}>
                            {t(
                              "Please check around have QR code or not, may be have just now stock in or transfer in or transfer out.",
                            )}
                          </Box>
                        )}
                      </Typography>
                      {r.stockOutLineId > 0 ? (
                        <Button
                          variant="outlined"
                          size="small"
                          onClick={() => openWorkbenchLotLabelModalForLot(r)}
                          sx={{ flexShrink: 0, fontSize: "0.7rem", py: 0.25, minWidth: "auto", px: 1, whiteSpace: "nowrap" }}
                        >
                          {t(" 批號 QR 碼")}
                        </Button>
                      ) : null}
                    </Stack>
                  </TableCell>
                  <TableCell align="right">{`${r.requiredQty.toLocaleString()}(${r.uomDesc || ""})`}</TableCell>
                  <TableCell align="right">
                    {`${Number(
                      r.itemTotalAvailableQty ?? r.availableQty ?? 0,
                    ).toLocaleString()}(${r.uomDesc || ""})`}
                  </TableCell>
                  <TableCell align="center">
                    {(() => {
                      if (isRowRejected && r.lotNo) {
                        return (
                          <Checkbox
                            checked
                            disabled
                            size="small"
                            sx={{
                              color: "error.main",
                              "&.Mui-checked": { color: "error.main" },
                            }}
                          />
                        );
                      }
                      if (isRowExpired) {
                        return (
                          <Checkbox
                            checked
                            disabled
                            size="small"
                            sx={{
                              color: "warning.main",
                              "&.Mui-checked": { color: "warning.main" },
                            }}
                          />
                        );
                      }
                      if (
                        isNoLot &&
                        (rowStatus === "partially_completed" ||
                          rowStatus === "partially_complete" ||
                          rowStatus === "completed")
                      ) {
                        return (
                          <Checkbox
                            checked
                            disabled
                            size="small"
                            sx={{
                              color: "error.main",
                              "&.Mui-checked": { color: "error.main" },
                            }}
                          />
                        );
                      }
                      if (isNoLot) {
                        return null;
                      }
                      return (
                        <Checkbox
                          checked={isCompletedStatus(r.status) || isCheckedStatus(r.status)}
                          disabled
                          size="small"
                          sx={{
                            color: isCompletedStatus(r.status)
                              ? "success.main"
                              : isCheckedStatus(r.status)
                                ? "warning.main"
                                : "action.disabled",
                            "&.Mui-checked": {
                              color: isCompletedStatus(r.status)
                                ? "success.main"
                                : isCheckedStatus(r.status)
                                  ? "warning.main"
                                  : "action.disabled",
                            },
                          }}
                        />
                      );
                    })()}
                  </TableCell>
                  <TableCell align="center">
                    <Stack direction="row" spacing={1} justifyContent="center" alignItems="center">
                      <TextField
                        size="small"
                        type="number"
                        value={
                          qtyEditableBySolId[r.stockOutLineId] === true && hasQtyOverride
                            ? String(qtyBySolId[r.stockOutLineId])
                            : String(submitQtyDisplay)
                        }
                        onKeyDown={(e) => {
                          const editable = qtyEditableBySolId[r.stockOutLineId] === true;
                          if (!editable) return;
                          if (e.key !== "{") return;
                          e.preventDefault();
                          setQtyEditableBySolId((prev) => ({
                            ...prev,
                            [r.stockOutLineId]: false,
                          }));
                          (e.currentTarget as HTMLInputElement).blur();
                        }}
                        onChange={(e) => {
                          const v = e.target.value;
                          setQtyBySolId((prev) => {
                            if (v === "" || v == null) {
                              if (!Object.prototype.hasOwnProperty.call(prev, r.stockOutLineId)) return prev;
                              const next = { ...prev };
                              delete next[r.stockOutLineId];
                              return next;
                            }
                            const n = Number(v);
                            if (!Number.isFinite(n) || n < 0) {
                              if (!Object.prototype.hasOwnProperty.call(prev, r.stockOutLineId)) return prev;
                              const next = { ...prev };
                              delete next[r.stockOutLineId];
                              return next;
                            }
                            return { ...prev, [r.stockOutLineId]: n };
                          });
                        }}
                        sx={{
                          width: 96,
                          "& .MuiInputBase-input": {
                            typography: "body1",
                            py: 0.5,
                            textAlign: "center",
                            "&::-webkit-outer-spin-button, &::-webkit-inner-spin-button": {
                              WebkitAppearance: "none",
                              margin: 0,
                            },
                            MozAppearance: "textfield",
                          },
                        }}
                        disabled={!r.stockOutLineId || qtyEditableBySolId[r.stockOutLineId] !== true}
                        inputProps={{ min: 0, step: 1 }}
                      />
                      <Button
                        variant="outlined"
                        size="small"
                        onClick={() =>
                          setQtyEditableBySolId((prev) => ({
                            ...prev,
                            [r.stockOutLineId]: !(prev[r.stockOutLineId] === true),
                          }))
                        }
                        disabled={!r.stockOutLineId || isCompletedStatus(r.status)}
                        sx={{ fontSize: "0.7rem", py: 0.5, minHeight: "28px", minWidth: "60px", borderColor: "warning.main", color: "warning.main" }}
                      >
                        {t("Edit")}
                      </Button>
                    </Stack>
                  </TableCell>
                  <TableCell align="center">
                    <Stack direction="row" spacing={1} justifyContent="center">
                      <Button
                        size="small"
                        variant="outlined"
                        disabled={!r.stockOutLineId || isCompletedStatus(r.status) || isCheckedStatus(r.status)}
                        onClick={() => void handleJustComplete(r)}
                      >
                        {t("Just Complete")}
                      </Button>
                    </Stack>
                  </TableCell>
                </TableRow>
                );
              })}
              {lotRows.length === 0 ? (
                <TableRow>
                  <TableCell colSpan={9} align="center" sx={{ textAlign: "center" }}>
                    <Typography variant="body2" color="text.secondary" align="center">
                      {t("No lot rows. Select a pick order above.")}
                    </Typography>
                  </TableCell>
                </TableRow>
              ) : null}
            </TableBody>
          </Table>
        </TableContainer>
        <TablePagination
          component="div"
          count={lotRows.length}
          page={lotPagingController.pageNum}
          rowsPerPage={lotPagingController.pageSize}
          onPageChange={(_e, newPage) => setLotPagingController((prev) => ({ ...prev, pageNum: newPage }))}
          onRowsPerPageChange={(e) => {
            const newPageSize = parseInt(e.target.value, 10);
            setLotPagingController({
              pageNum: 0,
              pageSize: newPageSize === -1 ? -1 : newPageSize,
            });
          }}
          rowsPerPageOptions={[10, 25, 50, -1]}
          labelRowsPerPage={t("Rows per page")}
        />
      </Paper>
      <WorkbenchLotLabelPrintModal
        open={workbenchLotLabelModalOpen}
        onClose={() => {
          setWorkbenchLotLabelModalOpen(false);
          setWorkbenchLotLabelReminderText(null);
          setWorkbenchLotLabelContextLot(null);
          setWorkbenchLotLabelInitialPayload(null);
        }}
        initialPayload={workbenchLotLabelInitialPayload}
        initialItemId={workbenchLotLabelContextLot?.itemId ?? null}
        hideScanSection={workbenchLotLabelInitialPayload != null || workbenchLotLabelContextLot != null}
        reminderText={workbenchLotLabelReminderText ?? undefined}
        statusTitleText={workbenchLotLabelStatusBanner.text}
        statusTitleSeverity={workbenchLotLabelStatusBanner.severity}
        triggerLotAvailableQty={workbenchLotLabelContextLot?.availableQty ?? null}
        triggerLotUom={workbenchLotLabelContextLot?.uomDesc ?? null}
        expectedUomId={
          workbenchLotLabelContextLot != null &&
          Number(workbenchLotLabelContextLot.uomId) > 0
            ? Number(workbenchLotLabelContextLot.uomId)
            : null
        }
        submitQty={
          workbenchLotLabelContextLot?.stockOutLineId
            ? Number(resolveLockedSubmitQtyDisplay(workbenchLotLabelContextLot))
            : null
        }
        onSubmitQtyChange={(qty) => {
          const solId = Number(workbenchLotLabelContextLot?.stockOutLineId);
          if (!Number.isFinite(solId) || solId <= 0) return;
          if (!Number.isFinite(qty) || qty < 0) {
            setQtyBySolId((prev) => {
              if (!Object.prototype.hasOwnProperty.call(prev, solId)) return prev;
              const next = { ...prev };
              delete next[solId];
              return next;
            });
            return;
          }
          setQtyBySolId((prev) => ({ ...prev, [solId]: qty }));
        }}
        onWorkbenchScanPick={handleWorkbenchLotLabelScanPick}
      />
      <ManualLotConfirmationModal
        open={manualLotConfirmationOpen}
        onClose={() => setManualLotConfirmationOpen(false)}
        onConfirm={(expectedLotNo, scannedLotNo) => {
          const expected = resolveScanCandidate(expectedLotNo);
          const scanned = resolveScanCandidate(scannedLotNo);
          if (!expected || !scanned) {
            setError(t("Lot not found in current line"));
            return;
          }
          setManualLotConfirmationOpen(false);
          void handleLotConfirmation(scanned, expected);
        }}
        expectedLot={
          expectedLotData
            ? {
                lotNo: expectedLotData.lotNo,
                itemCode: expectedLotData.itemCode,
                itemName: expectedLotData.itemName,
              }
            : null
        }
        scannedLot={
          scannedLotData
            ? {
                lotNo: scannedLotData.lotNo,
                itemCode: scannedLotData.itemCode,
                itemName: scannedLotData.itemName,
              }
            : null
        }
        isLoading={isConfirmingLot}
      />
      </Stack>
    </TestQrCodeProvider>
  );
};

export default WorkbenchPickExecution;
