"use client";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
  Box,
  Button,
  Card,
  Stack,
  Typography,
  Chip,
  CircularProgress,
  Grid,
  FormControl,
  InputLabel,
  Select,
  MenuItem,
  Checkbox,
  ListItemText,
  SelectChangeEvent,
  Dialog,
  DialogTitle,
  DialogContent,
  DialogActions,
  Tabs,
  Tab,
  Tooltip,
  IconButton,
  Avatar,
} from "@mui/material";
import ChevronLeft from "@mui/icons-material/ChevronLeft";
import ChevronRight from "@mui/icons-material/ChevronRight";
import { useTranslation } from "react-i18next";
import { fetchItemForPutAway } from "@/app/api/stockIn/actions";
import QcStockInModal from "../Qc/QcStockInModal";
import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import dayjs from "dayjs";
import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import SearchBox, { Criterion } from "@/components/SearchBox/SearchBox";
import { AUTH, hasAbility } from "@/authorities";


import {
  AllJoborderProductProcessInfoResponse,
  updateJo,
  fetchProductProcessesByJobOrderId,
  completeProductProcessLine,
  assignJobOrderPickOrder,
  fetchJoborderProductProcessesPage,
  JobOrderProductProcessBucketCounts,
} from "@/app/api/jo/actions";
import { StockInLineInput } from "@/app/api/stockIn";
import { PrinterCombo } from "@/app/api/settings/printer";
import JobPickExecutionsecondscan from "../Jodetail/JobPickExecutionsecondscan";
export type ProductionProcessListTab =
  | "needs_action"
  | "pending"
  | "processing"
  | "carried_over"
  | "pending_qc"
  | "putawayed";

export type ProductionProcessListPersistedState = {
  date: string;
  itemCode: string | null;
  jobOrderCode: string | null;
  filter: "all" | "drink" | "Powder_Mixture" | "other";
  page: number;
  selectedItemCodes: string[];
  /**
   * Unified list tabs:
   * needs_action | pending | processing | carried_over | pending_qc | putawayed
   * Legacy: all → needs_action; fine pick buckets remapped to pending/processing.
   */
  pickBucket: ProductionProcessListTab | string;
};

interface ProductProcessListProps {
  onSelectProcess: (jobOrderId: number|undefined, productProcessId: number|undefined) => void;
  onSelectMatchingStock: (jobOrderId: number|undefined, productProcessId: number|undefined,pickOrderId: number|undefined) => void;
  printerCombo: PrinterCombo[];
  /** @deprecated Derived from pickBucket when unified tabs are used; kept for compatibility. */
  qcReady?: boolean;
  includePutaway?: boolean | null;
  /** all | completed | notCompleted */
  putawayStatus?: string | null;
  disableDateFilter?: boolean;
  listPersistedState: ProductionProcessListPersistedState;
  onListPersistedStateChange: React.Dispatch<
    React.SetStateAction<ProductionProcessListPersistedState>
  >;
}
export type SearchParam = "date" | "itemCode" | "jobOrderCode" | "processType";

/** Cards per visible page: 2 rows × 3 columns. */
const CARDS_PER_PAGE = 6;
/** Fetch once; client slides pages of CARDS_PER_PAGE (no refetch on page change). */
const FETCH_SIZE = 200;
/** Include unfinished from (searchDate - LOOKBACK_DAYS) .. searchDate; picked_not_started capped at search date on backend. */
const PRODUCTION_LOOKBACK_DAYS = 4;

const PENDING_FINE_BUCKETS = new Set([
  "not_picked_not_started",
  "picked_not_started",
]);
const PROCESSING_FINE_BUCKETS = new Set([
  "picked_started",
  "not_picked_started",
]);

const EMPTY_BUCKET_COUNTS: JobOrderProductProcessBucketCounts = {
  notPickedNotStarted: 0,
  pickedNotStarted: 0,
  pickedStarted: 0,
  notPickedStarted: 0,
};

/** 預設依 JobOrder.planStart 搜索：今天往前 3 天～往後 3 天（含當日） */
function defaultPlanStartRange() {
  return {
    from: dayjs().subtract(0, "day").format("YYYY-MM-DD"),
    to: dayjs().add(0, "day").format("YYYY-MM-DD"),
  };
}

export function createDefaultProductionProcessListPersistedState(): ProductionProcessListPersistedState {
  return {
    date: dayjs().format("YYYY-MM-DD"),
    itemCode: null,
    jobOrderCode: null,
    filter: "all",
    page: 0,
    selectedItemCodes: [],
    pickBucket: "needs_action",
  };
}

function normalizeListTab(raw: string | undefined | null): ProductionProcessListTab {
  const v = (raw || "needs_action").trim();
  if (
    v === "pending" ||
    v === "processing" ||
    v === "carried_over" ||
    v === "pending_qc" ||
    v === "putawayed" ||
    v === "needs_action"
  ) {
    return v;
  }
  if (v === "all") return "needs_action";
  if (v === "not_picked_not_started" || v === "picked_not_started") return "pending";
  if (v === "picked_started" || v === "not_picked_started") return "processing";
  return "needs_action";
}

function isProcessCarriedOver(
  p: AllJoborderProductProcessInfoResponse,
  searchDay: ReturnType<typeof dayjs> | null,
): boolean {
  if (!searchDay || !p.date || !dayjs(p.date).isValid()) return false;
  return dayjs(p.date).startOf("day").isBefore(searchDay);
}

function isPutawayCompleted(
  p: AllJoborderProductProcessInfoResponse,
): boolean {
  return String(p.stockInLineStatus ?? "").trim().toLowerCase() === "completed";
}

/** Waiting QC put-away (has SIL, not completed/rejected). */
function isWaitingQcPutAway(
  p: AllJoborderProductProcessInfoResponse,
): boolean {
  if (p.stockInLineId == null) return false;
  const s = String(p.stockInLineStatus ?? "").trim().toLowerCase();
  return s !== "completed" && s !== "rejected";
}

/** FP-MTMS Version Checklist | Functions Ref. No. 40 | v1.0.3 | 2026-08-05 */
const ProductProcessList: React.FC<ProductProcessListProps> = ({
  onSelectProcess,
  printerCombo,
  onSelectMatchingStock,
  disableDateFilter = false,
  listPersistedState,
  onListPersistedStateChange,
}) => {
  const { t } = useTranslation( ["common", "productionProcess","purchaseOrder","dashboard"]);
  const { data: session } = useSession() as { data: SessionWithTokens | null };
  const sessionToken = session as SessionWithTokens | null;
  const [loading, setLoading] = useState(false);
  const [processes, setProcesses] = useState<AllJoborderProductProcessInfoResponse[]>([]);
  const [productionCache, setProductionCache] = useState<
    AllJoborderProductProcessInfoResponse[]
  >([]);
  const [pendingQcCache, setPendingQcCache] = useState<
    AllJoborderProductProcessInfoResponse[]
  >([]);
  const [bucketCounts, setBucketCounts] =
    useState<JobOrderProductProcessBucketCounts>(EMPTY_BUCKET_COUNTS);
  const [pendingQcCount, setPendingQcCount] = useState(0);
  const [putawayedCount, setPutawayedCount] = useState(0);
  const [carriedOverCount, setCarriedOverCount] = useState(0);
  const [openModal, setOpenModal] = useState<boolean>(false);
  const [modalInfo, setModalInfo] = useState<StockInLineInput>();
  const currentUserId = session?.id ? parseInt(session.id) : undefined; 
  const abilities = session?.abilities ?? session?.user?.abilities ?? [];
  /** 完成工單：僅 PRODUCT_PROCESS（工單 生產流程 完成工單）；不含 ADMIN 以免群組 ADMIN 繞過勾選 */
  const canManageUpdateJo = hasAbility(abilities, AUTH.PRODUCT_PROCESS);
  type ProcessFilter = "all" | "drink" | "Powder_Mixture" | "other";

  const listTab = normalizeListTab(listPersistedState.pickBucket);
  const isProductionTab =
    listTab === "needs_action" || listTab === "pending" || listTab === "processing";
  const isCarriedOverTab = listTab === "carried_over";
  const qcReady = listTab === "pending_qc" || listTab === "putawayed";
  const putawayStatus =
    listTab === "putawayed"
      ? "completed"
      : listTab === "pending_qc"
        ? "notCompleted"
        : null;
  const includePutaway = qcReady ? true : null;
  /** Production unfinished tabs + pending QC + off-plan: carry-over window. */
  const enableCarryOver =
    !disableDateFilter &&
    (isProductionTab || listTab === "pending_qc" || isCarriedOverTab);

  const appliedSearch = useMemo(
    () => ({
      date: listPersistedState.date,
      itemCode: listPersistedState.itemCode,
      jobOrderCode: listPersistedState.jobOrderCode,
    }),
    [
      listPersistedState.date,
      listPersistedState.itemCode,
      listPersistedState.jobOrderCode,
    ],
  );
  const filter = listPersistedState.filter;
  const page = listPersistedState.page;
  const selectedItemCodes = listPersistedState.selectedItemCodes;

  const searchDay = useMemo(
    () => (appliedSearch.date ? dayjs(appliedSearch.date).startOf("day") : null),
    [appliedSearch.date],
  );

  const [totalJobOrders, setTotalJobOrders] = useState(0);

  // Generic confirm dialog for actions (update job order / etc.)
  const [confirmOpen, setConfirmOpen] = useState(false);
  const [confirmMessage, setConfirmMessage] = useState("");
  const [confirmLoading, setConfirmLoading] = useState(false);
  const [pendingConfirmAction, setPendingConfirmAction] = useState<null | (() => Promise<void>)>(null);

  // QC 的业务判定：同一个 jobOrder 下，所有 productProcess 的所有 lines 都必须是 Completed/Pass
  // 才允许打开 QcStockInModal（避免仅某个 productProcess 完成就提前出现 view stockin）。
  const jobOrderQcReadyById = useMemo(() => {
    const lineDone = (status: unknown) => {
      const s = String(status ?? "").trim().toLowerCase();
      return s === "completed" || s === "pass";
    };

    const byJobOrder = new Map<number, AllJoborderProductProcessInfoResponse[]>();
    for (const p of processes) {
      if (p.jobOrderId == null) continue;
      const arr = byJobOrder.get(p.jobOrderId) ?? [];
      arr.push(p);
      byJobOrder.set(p.jobOrderId, arr);
    }

    const result = new Map<number, boolean>();
    const isDone = (status: unknown) => {
      const s = String(status ?? "").trim().toLowerCase();
      return s === "completed" || s === "pass";
    };
    
    byJobOrder.forEach((jobOrderProcesses, jobOrderId) => {
      const hasStockInLine = jobOrderProcesses.some((p) => p.stockInLineId != null);
    
      const packingProcesses = jobOrderProcesses.filter(
        (p) => String((p as any).code ?? "").trim() === "包裝"
      );
      const nonPackingProcesses = jobOrderProcesses.filter(
        (p) => String((p as any).code ?? "").trim() !== "包裝"
      );
    
      const allNonPackingDone =
      nonPackingProcesses.length === 0 ||
      nonPackingProcesses.every((p) => {
        const lines = p.lines ?? [];
        return lines.length > 0 && lines.every((l) => isDone(l.status));
      });
    
      const hasOnePackingDone =
        packingProcesses.length > 0 &&
        packingProcesses.some((p) => {
          const lines = p.lines ?? [];
          return lines.some((l) => isDone(l.status));
        });
    
      const packingOk = packingProcesses.length === 0 ? true : hasOnePackingDone;
    
      result.set(jobOrderId, hasStockInLine && allNonPackingDone && packingOk);
    });

    return result;
  }, [processes]);
  const handleAssignPickOrder = useCallback(async (pickOrderId: number, jobOrderId?: number, productProcessId?: number) => {
    if (!currentUserId) {
      alert(t("Unable to get user ID"));
      return;
    }

    try {
      console.log("🔄 Assigning pick order:", pickOrderId, "to user:", currentUserId);
      
      // 调用分配 API 并读取响应
      const assignResult = await assignJobOrderPickOrder(pickOrderId, currentUserId);
      
      console.log("📦 Assign result:", assignResult);
      
      // 检查分配是否成功
      if (assignResult.message === "Successfully assigned") {
        console.log("✅ Successfully assigned pick order");
        console.log("✅ Pick order ID:", assignResult.id);
        console.log("✅ Pick order code:", assignResult.code);
        
        // 分配成功后，导航到 second scan 页面
        if (onSelectMatchingStock && jobOrderId) {
          onSelectMatchingStock(jobOrderId, productProcessId,pickOrderId);
        } else {
          alert(t("Assignment successful"));
        }
      } else {
        // 分配失败
        console.error("Assignment failed:", assignResult.message);
        alert(t(`Assignment failed: ${assignResult.message || "Unknown error"}`));
      }
    } catch (error: any) {
      console.error(" Error assigning pick order:", error);
      alert(t(`Unknown error: ${error?.message || "Unknown error"}。Please try again later.`));
    }
  }, [currentUserId, t, onSelectMatchingStock]);

  const handleViewStockIn = useCallback((process: AllJoborderProductProcessInfoResponse) => {
    if (!process.stockInLineId) {
      alert(t("Invalid Stock In Line Id"));
      return;
    }
  
    setModalInfo({
      id: process.stockInLineId,
     //itemId: process.itemId, // 如果 process 中有 itemId，添加这一行
      //expiryDate: dayjs().add(1, "month").format(OUTPUT_DATE_FORMAT),
    });
    setOpenModal(true);
  }, [t]);

  const handleApplySearch = useCallback(
    (inputs: Record<SearchParam | `${SearchParam}To`, string>) => {
      const selectedProcessType = (inputs.processType || "all") as ProcessFilter;
      onListPersistedStateChange((prev) => ({
        ...prev,
        filter: selectedProcessType,
        date: disableDateFilter ? "" : (inputs.date || "").trim(),
        itemCode: inputs.itemCode?.trim() ? inputs.itemCode.trim() : null,
        jobOrderCode: inputs.jobOrderCode?.trim() ? inputs.jobOrderCode.trim() : null,
        selectedItemCodes: [],
        page: 0,
      }));
    },
    [disableDateFilter, onListPersistedStateChange],
  );

  const handleResetSearch = useCallback(() => {
    onListPersistedStateChange((prev) => ({
      ...prev,
      filter: "all",
      date: disableDateFilter ? "" : defaultPlanStartRange().from,
      itemCode: null,
      jobOrderCode: null,
      selectedItemCodes: [],
      page: 0,
      pickBucket: "needs_action",
    }));
  }, [disableDateFilter, onListPersistedStateChange]);

  const fetchProcesses = useCallback(async () => {
    setLoading(true);
    try {
      const typeParam = filter === "all" ? undefined : filter;
      const base = {
        date: disableDateFilter ? undefined : appliedSearch.date,
        itemCode: appliedSearch.itemCode,
        jobOrderCode: appliedSearch.jobOrderCode,
        type: typeParam,
        page: 0,
        size: FETCH_SIZE,
      };

      if (isCarriedOverTab) {
        const [prod, pendingQc] = await Promise.all([
          fetchJoborderProductProcessesPage({
            ...base,
            qcReady: false,
            lookbackDays: enableCarryOver ? PRODUCTION_LOOKBACK_DAYS : undefined,
            bucket: "all",
          }),
          fetchJoborderProductProcessesPage({
            ...base,
            qcReady: true,
            includePutaway: true,
            putawayStatus: "notCompleted",
            lookbackDays: enableCarryOver ? PRODUCTION_LOOKBACK_DAYS : undefined,
          }),
        ]);
        const prodContent = (prod?.content || []).filter((p) => !isPutawayCompleted(p));
        const qcContent = (pendingQc?.content || []).filter((p) => !isPutawayCompleted(p));
        setProductionCache(prodContent);
        setPendingQcCache(qcContent);
        if (prod?.bucketCounts) setBucketCounts(prod.bucketCounts);
        setPendingQcCount(pendingQc?.totalJobOrders || 0);
        setCarriedOverCount(prod?.carriedOverCount ?? 0);
        const search = appliedSearch.date
          ? dayjs(appliedSearch.date).startOf("day")
          : null;
        const merged = [...qcContent, ...prodContent].filter((p) =>
          isProcessCarriedOver(p, search),
        );
        // Dedupe by jobOrderId (prefer waiting-QC row when it has stockInLineId)
        const byJo = new Map<number, AllJoborderProductProcessInfoResponse>();
        for (const p of merged) {
          if (p.jobOrderId == null) continue;
          const existing = byJo.get(p.jobOrderId);
          if (!existing) {
            byJo.set(p.jobOrderId, p);
          } else if (existing.stockInLineId == null && p.stockInLineId != null) {
            byJo.set(p.jobOrderId, p);
          }
        }
        const content = Array.from(byJo.values());
        setProcesses(content);
        setTotalJobOrders(content.length);
        return;
      }

      // Production tabs share one fetch (bucket=all); pending/processing filter client-side.
      const data = await fetchJoborderProductProcessesPage({
        ...base,
        qcReady,
        includePutaway,
        putawayStatus,
        lookbackDays: enableCarryOver ? PRODUCTION_LOOKBACK_DAYS : undefined,
        bucket: isProductionTab ? "all" : undefined,
      });

      const content = data?.content || [];
      setProcesses(content);
      setTotalJobOrders(data?.totalJobOrders || 0);
      if (isProductionTab) {
        setProductionCache(content);
        if (data?.bucketCounts) setBucketCounts(data.bucketCounts);
      }
      if (qcReady && putawayStatus === "notCompleted") {
        setPendingQcCache(content);
        setPendingQcCount(data?.totalJobOrders || 0);
      }
      if (qcReady && putawayStatus === "completed") {
        setPutawayedCount(data?.totalJobOrders || 0);
      }
      setCarriedOverCount(data?.carriedOverCount ?? 0);
    } catch (e) {
      console.error(e);
      setProcesses([]);
      setTotalJobOrders(0);
      if (isProductionTab) setBucketCounts(EMPTY_BUCKET_COUNTS);
      setCarriedOverCount(0);
    } finally {
      setLoading(false);
    }
  }, [
    appliedSearch,
    disableDateFilter,
    filter,
    qcReady,
    includePutaway,
    putawayStatus,
    enableCarryOver,
    isProductionTab,
    isCarriedOverTab,
  ]);

  useEffect(() => {
    fetchProcesses();
  }, [fetchProcesses]);

  /** Keep production + QC tab badges fresh even when not on that tab. */
  useEffect(() => {
    let cancelled = false;
    const typeParam = filter === "all" ? undefined : filter;
    const base = {
      date: disableDateFilter ? undefined : appliedSearch.date,
      itemCode: appliedSearch.itemCode,
      jobOrderCode: appliedSearch.jobOrderCode,
      type: typeParam,
      page: 0,
      size: FETCH_SIZE,
    };

    (async () => {
      try {
        const [prod, pendingQc, putawayed] = await Promise.all([
          fetchJoborderProductProcessesPage({
            ...base,
            qcReady: false,
            lookbackDays: disableDateFilter ? undefined : PRODUCTION_LOOKBACK_DAYS,
            bucket: "all",
          }),
          fetchJoborderProductProcessesPage({
            ...base,
            qcReady: true,
            includePutaway: true,
            putawayStatus: "notCompleted",
            lookbackDays: disableDateFilter ? undefined : PRODUCTION_LOOKBACK_DAYS,
          }),
          fetchJoborderProductProcessesPage({
            ...base,
            size: 1,
            qcReady: true,
            includePutaway: true,
            putawayStatus: "completed",
          }),
        ]);
        if (cancelled) return;
        if (prod?.bucketCounts) setBucketCounts(prod.bucketCounts);
        if (prod?.content) {
          setProductionCache(prod.content.filter((p) => !isPutawayCompleted(p)));
        }
        if (pendingQc?.content) {
          setPendingQcCache(
            pendingQc.content.filter((p) => !isPutawayCompleted(p)),
          );
        }
        setPendingQcCount(pendingQc?.totalJobOrders || 0);
        setPutawayedCount(putawayed?.totalJobOrders || 0);
        if (prod?.carriedOverCount != null) setCarriedOverCount(prod.carriedOverCount);
      } catch (e) {
        console.error(e);
      }
    })();

    return () => {
      cancelled = true;
    };
  }, [appliedSearch, disableDateFilter, filter]);

  const handleListTabChange = useCallback(
    (_: React.SyntheticEvent, value: string) => {
      const next = normalizeListTab(value);
      onListPersistedStateChange((prev) => ({
        ...prev,
        pickBucket: next,
        page: 0,
      }));
    },
    [onListPersistedStateChange],
  );

  const pendingCount =
    bucketCounts.notPickedNotStarted + bucketCounts.pickedNotStarted;
  const processingCount =
    bucketCounts.pickedStarted + bucketCounts.notPickedStarted;
  const needsActionCount = pendingCount + processingCount;

  const offPlanTabCount = useMemo(() => {
    if (!searchDay) return 0;
    const ids = new Set<number>();
    for (const p of productionCache) {
      if (
        !isPutawayCompleted(p) &&
        isProcessCarriedOver(p, searchDay) &&
        p.jobOrderId != null
      ) {
        ids.add(p.jobOrderId);
      }
    }
    for (const p of pendingQcCache) {
      if (
        !isPutawayCompleted(p) &&
        isProcessCarriedOver(p, searchDay) &&
        p.jobOrderId != null
      ) {
        ids.add(p.jobOrderId);
      }
    }
    return ids.size;
  }, [productionCache, pendingQcCache, searchDay]);

  const filteredProcesses = useMemo(() => {
    let list = processes;
    if (listTab === "pending") {
      list = list.filter((p) =>
        PENDING_FINE_BUCKETS.has(String(p.pickProcessBucket ?? "")),
      );
    } else if (listTab === "processing") {
      list = list.filter((p) =>
        PROCESSING_FINE_BUCKETS.has(String(p.pickProcessBucket ?? "")),
      );
    }
    // carried_over: fetch already filtered to off-plan only
    if (selectedItemCodes.length === 0) return list;
    return list.filter((p) => selectedItemCodes.includes(p.itemCode));
  }, [processes, selectedItemCodes, listTab]);

  const displayTotalJobOrders = isCarriedOverTab
    ? filteredProcesses.length
    : isProductionTab
      ? listTab === "pending"
        ? pendingCount
        : listTab === "processing"
          ? processingCount
          : totalJobOrders || needsActionCount
      : totalJobOrders;

  const displayCarriedOverCount = useMemo(() => {
    if (!enableCarryOver || !searchDay) return 0;
    if (!isProductionTab || listTab === "needs_action") return carriedOverCount;
    return filteredProcesses.filter((p) => {
      if (!p.date || !dayjs(p.date).isValid()) return false;
      return dayjs(p.date).startOf("day").isBefore(searchDay);
    }).length;
  }, [
    enableCarryOver,
    searchDay,
    isProductionTab,
    listTab,
    carriedOverCount,
    filteredProcesses,
  ]);

  const pageChunks = useMemo(() => {
    const chunks: AllJoborderProductProcessInfoResponse[][] = [];
    for (let i = 0; i < filteredProcesses.length; i += CARDS_PER_PAGE) {
      chunks.push(filteredProcesses.slice(i, i + CARDS_PER_PAGE));
    }
    return chunks.length > 0 ? chunks : [[]];
  }, [filteredProcesses]);

  const totalPages = pageChunks.length;
  const safePage = Math.min(page, Math.max(0, totalPages - 1));

  const scrollerRef = useRef<HTMLDivElement | null>(null);
  const scrollSyncLockRef = useRef(false);

  const scrollToPage = useCallback(
    (targetPage: number, behavior: ScrollBehavior = "smooth") => {
      const el = scrollerRef.current;
      if (!el) return;
      const clamped = Math.max(0, Math.min(targetPage, totalPages - 1));
      scrollSyncLockRef.current = true;
      el.scrollTo({ left: clamped * el.clientWidth, behavior });
      onListPersistedStateChange((prev) =>
        prev.page === clamped ? prev : { ...prev, page: clamped },
      );
      window.setTimeout(() => {
        scrollSyncLockRef.current = false;
      }, behavior === "smooth" ? 450 : 50);
    },
    [totalPages, onListPersistedStateChange],
  );

  const goPrevPage = useCallback(() => {
    if (safePage <= 0) return;
    scrollToPage(safePage - 1);
  }, [safePage, scrollToPage]);

  const goNextPage = useCallback(() => {
    if (safePage + 1 >= totalPages) return;
    scrollToPage(safePage + 1);
  }, [safePage, totalPages, scrollToPage]);

  const handleScrollerScroll = useCallback(() => {
    if (scrollSyncLockRef.current) return;
    const el = scrollerRef.current;
    if (!el || el.clientWidth <= 0) return;
    const nextPage = Math.round(el.scrollLeft / el.clientWidth);
    const clamped = Math.max(0, Math.min(nextPage, totalPages - 1));
    if (clamped !== page) {
      onListPersistedStateChange((prev) =>
        prev.page === clamped ? prev : { ...prev, page: clamped },
      );
    }
  }, [page, totalPages, onListPersistedStateChange]);

  // After data reload, jump to persisted page without animation.
  useEffect(() => {
    const el = scrollerRef.current;
    if (!el || loading) return;
    const clamped = Math.min(page, Math.max(0, totalPages - 1));
    scrollSyncLockRef.current = true;
    el.scrollTo({ left: clamped * el.clientWidth, behavior: "auto" });
    window.setTimeout(() => {
      scrollSyncLockRef.current = false;
    }, 50);
  }, [loading, filteredProcesses, totalPages]); // eslint-disable-line react-hooks/exhaustive-deps

  const renderBucketTabLabel = useCallback(
    (labelKey: string, count: number) =>
      count > 0 ? `${t(labelKey)} (${count})` : t(labelKey),
    [t],
  );
  const handleUpdateJo = useCallback(async (process: AllJoborderProductProcessInfoResponse) => {
    if (!canManageUpdateJo) return;
    if (!process.jobOrderId) {
      alert(t("Invalid Job Order Id"));
      return;
    }
    try {
      setLoading(true); // 可选：已有 loading state 可复用
      // 1) 拉取该 JO 的所有 process，取出全部 lineId
      const processes = await fetchProductProcessesByJobOrderId(process.jobOrderId);
      const lineIds = (processes ?? [])
        .flatMap(p => (p as any).productProcessLines ?? [])
        .map(l => l.id)
        .filter(Boolean);
  
      // 2) 逐个调用 completeProductProcessLine
      for (const lineId of lineIds) {
        try {
          await completeProductProcessLine(lineId);
        } catch (e) {
          console.error("completeProductProcessLine failed for lineId:", lineId, e);
        }
      }
  
      // 3) 更新 JO 状态
     // await updateJo({ id: process.jobOrderId, status: "completed" });
  
      // 4) 刷新列表
      await fetchProcesses();
    } catch (e) {
      console.error(e);
      alert(t("An error has occurred. Please try again later."));
    } finally {
      setLoading(false);
    }
  }, [t, fetchProcesses, canManageUpdateJo]);

  const openConfirm = useCallback((message: string, action: () => Promise<void>) => {
    setConfirmMessage(message);
    setPendingConfirmAction(() => action);
    setConfirmOpen(true);
  }, []);

  const closeConfirm = useCallback(() => {
    setConfirmOpen(false);
    setPendingConfirmAction(null);
    setConfirmMessage("");
    setConfirmLoading(false);
  }, []);

  const onConfirm = useCallback(async () => {
    if (!pendingConfirmAction) return;
    setConfirmLoading(true);
    try {
      await pendingConfirmAction();
    } finally {
      closeConfirm();
    }
  }, [pendingConfirmAction, closeConfirm]);
  const closeNewModal = useCallback(() => {
    // const response = updateJo({ id: 1, status: "storing" });
    setOpenModal(false); // Close the modal first
   // fetchProcesses();
    // setTimeout(() => {
    //   }, 300); // Add a delay to avoid immediate re-trigger of useEffect
}, [fetchProcesses]);

  const searchedItemOptions = useMemo(
    () =>
      Array.from(
        new Map(
          processes
            .filter((p) => !!p.itemCode)
            .map((p) => [p.itemCode, { itemCode: p.itemCode, itemName: p.itemName }]),
        ).values(),
      ),
    [processes],
  );

  /** Reset 用 ±3 天；preFilled 用目前已套用的條件（與列表查詢一致） */
  const searchCriteria: Criterion<SearchParam>[] = useMemo(() => {
    const base: Criterion<SearchParam>[] = [
      ...(disableDateFilter
        ? []
        : [{
        type: "date",
        label: t("Search date"),
        paramName: "date",
        defaultValue: appliedSearch.date,
        preFilledValue: appliedSearch.date,
      } as Criterion<SearchParam>]),
      {
        type: "text",
        label: t("Item Code"),
        paramName: "itemCode",
        preFilledValue: appliedSearch.itemCode ?? "",
      },
      {
        type: "text",
        label: t("Job Order Code"),
        paramName: "jobOrderCode",
        preFilledValue: appliedSearch.jobOrderCode ?? "",
      },
      {
        type: "select",
        label: t("Process Type"),
        paramName: "processType",
        options: ["all", "drink", "Powder_Mixture", "other"],
        preFilledValue: filter,
      },
    ];
    return base;
  }, [appliedSearch, disableDateFilter, filter, t]);

  /** SearchBox 內部 state 只在掛載時讀 preFilled；套用搜索後需 remount 才會與 appliedSearch 一致 */
  const searchBoxKey = useMemo(
    () =>
      [
        disableDateFilter ? "" : appliedSearch.date,
        appliedSearch.itemCode ?? "",
        appliedSearch.jobOrderCode ?? "",
        filter,
      ].join("|"),
    [appliedSearch, disableDateFilter, filter],
  );

  const handleSelectedItemCodesChange = useCallback(
    (e: SelectChangeEvent<string[]>) => {
      const nextValue = e.target.value;
      const codes = typeof nextValue === "string" ? nextValue.split(",") : nextValue;
      onListPersistedStateChange((prev) => ({ ...prev, selectedItemCodes: codes }));
    },
    [onListPersistedStateChange],
  );

  return (
    <Box>
      {loading ? (
        <Box sx={{ display: "flex", justifyContent: "center", p: 3 }}>
          <CircularProgress />
        </Box>
      ) : (
        <Box>
          <SearchBox<SearchParam>
            key={searchBoxKey}
            criteria={searchCriteria}
            onSearch={handleApplySearch}
            onReset={handleResetSearch}
            extraActions={
              <FormControl size="small" sx={{ minWidth: 260 }}>
                <InputLabel>{t("Searched Item")}</InputLabel>
                <Select
                  multiple
                  value={selectedItemCodes}
                  label={t("Item Code")}
                  renderValue={(selected) =>
                    (selected as string[]).length === 0 ? t("All") : (selected as string[]).join(", ")
                  }
                  onChange={handleSelectedItemCodesChange}
                >
                  {searchedItemOptions.map((item) => (
                    <MenuItem key={item.itemCode} value={item.itemCode}>
                      <Checkbox checked={selectedItemCodes.includes(item.itemCode)} />
                      <ListItemText primary={[item.itemCode, item.itemName].filter(Boolean).join(" - ")} />
                    </MenuItem>
                  ))}
                </Select>
              </FormControl>
            }
          />
          <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
            {!disableDateFilter && (
              <>
                {t("Search date")}:{" "}
                {appliedSearch.date && dayjs(appliedSearch.date).isValid()
                  ? dayjs(appliedSearch.date).format(OUTPUT_DATE_FORMAT)
                  : "-"}
                {" | "}
              </>
            )}
            {t("Total job orders")}: {displayTotalJobOrders}
            {enableCarryOver && displayCarriedOverCount > 0
              ? ` | ${t("Including carried over")}: ${displayCarriedOverCount}`
              : ""}
            {selectedItemCodes.length > 0 ? ` | ${t("Filtered")}: ${filteredProcesses.length}` : ""}
          </Typography>

          <Tabs
              value={listTab}
              onChange={handleListTabChange}
              variant="scrollable"
              scrollButtons="auto"
              sx={{
                mb: 2,
                borderBottom: 1,
                borderColor: "divider",
                "& .MuiTabs-flexContainer": {
                  columnGap: 2,
                  rowGap: 1,
                },
                "& .MuiTab-root": {
                  overflow: "visible",
                  minWidth: "auto",
                  px: 2,
                },
              }}
            >
              <Tab
                value="needs_action"
                label={renderBucketTabLabel("Needs action", needsActionCount)}
              />
              <Tab
                value="pending"
                label={renderBucketTabLabel("pending", pendingCount)}
              />
              <Tab
                value="processing"
                label={renderBucketTabLabel("Processing", processingCount)}
              />
              <Tab
                value="carried_over"
                label={renderBucketTabLabel(
                  "Off-plan unfinished",
                  offPlanTabCount,
                )}
              />
              <Tab
                value="pending_qc"
                label={renderBucketTabLabel(
                  "Waiting QC Put Away",
                  pendingQcCount,
                )}
              />
              <Tab
                value="putawayed"
                label={renderBucketTabLabel("Put Awayed", putawayedCount)}
              />
            </Tabs>

          <Box
            sx={{
              display: "flex",
              alignItems: "stretch",
              gap: 1,
            }}
            onKeyDown={(e) => {
              if (e.key === "ArrowLeft") goPrevPage();
              if (e.key === "ArrowRight") goNextPage();
            }}
          >
            <IconButton
              aria-label={t("Previous page")}
              onClick={goPrevPage}
              disabled={safePage <= 0 || filteredProcesses.length === 0}
              sx={{ alignSelf: "center" }}
            >
              <ChevronLeft />
            </IconButton>

            <Box
              ref={scrollerRef}
              onScroll={handleScrollerScroll}
              sx={{
                flex: 1,
                minWidth: 0,
                display: "flex",
                overflowX: "auto",
                scrollSnapType: "x mandatory",
                scrollBehavior: "smooth",
                WebkitOverflowScrolling: "touch",
                scrollbarWidth: "none",
                msOverflowStyle: "none",
                "&::-webkit-scrollbar": { display: "none" },
              }}
            >
              {pageChunks.map((chunk, pageIndex) => (
                <Box
                  key={`page-${pageIndex}`}
                  sx={{
                    minWidth: "100%",
                    width: "100%",
                    flexShrink: 0,
                    scrollSnapAlign: "start",
                    scrollSnapStop: "always",
                    px: 0.5,
                    boxSizing: "border-box",
                  }}
                >
                  <Grid container spacing={2} alignItems="stretch">
                    {chunk.map((process) => {
                      const status = String(process.status || "");
                      const statusLower = status.toLowerCase();
                      const displayStatus =
                        statusLower === "in_progress" ? "processing" : status;
                      const bucket = String(process.pickProcessBucket ?? "");
                      const waitingQc = isWaitingQcPutAway(process);

                      // Avoid t("completed") → purchaseOrder「已上架」; use explicit keys.
                      let chipLabel: string;
                      let statusColor: "success" | "warning" | "primary" | "default";
                      if (listTab === "putawayed") {
                        chipLabel = t("Put Awayed");
                        statusColor = "success";
                      } else if (listTab === "pending_qc" || (isCarriedOverTab && waitingQc)) {
                        chipLabel = t("Waiting QC Put Away");
                        statusColor = "warning";
                      } else if (isCarriedOverTab && PENDING_FINE_BUCKETS.has(bucket)) {
                        chipLabel = t("pending");
                        statusColor = "default";
                      } else if (isCarriedOverTab && PROCESSING_FINE_BUCKETS.has(bucket)) {
                        chipLabel = t("processing");
                        statusColor = "primary";
                      } else if (qcReady) {
                        chipLabel =
                          putawayStatus === "completed"
                            ? t("Put Awayed")
                            : t("Waiting QC Put Away");
                        statusColor =
                          putawayStatus === "completed" ? "success" : "warning";
                      } else if (statusLower === "completed") {
                        chipLabel = t("Completed");
                        statusColor = "success";
                      } else {
                        chipLabel = t(displayStatus);
                        statusColor =
                          statusLower === "in_progress" ||
                          statusLower === "processing"
                            ? "primary"
                            : "default";
                      }

                      const jobOrderCode =
                        (process as any).jobOrderCode ??
                        (process.jobOrderId ? `JO-${process.jobOrderId}` : "N/A");

                      const canQc =
                        process.jobOrderId != null &&
                        process.stockInLineId != null &&
                        jobOrderQcReadyById.get(process.jobOrderId) === true;

                      const joDay = process.date
                        ? dayjs(process.date).startOf("day")
                        : null;
                      const isCarriedOver =
                        isCarriedOverTab ||
                        Boolean(
                          enableCarryOver &&
                            searchDay?.isValid() &&
                            joDay?.isValid() &&
                            joDay.isBefore(searchDay),
                        );

                      const bomDescription = process.bomDescription
                        ? String(process.bomDescription).trim()
                        : "";
                      const bomType = process.bomType
                        ? String(process.bomType).trim()
                        : "";

                      const chipSx = {
                        flexShrink: 0,
                        height: 28,
                        borderRadius: "14px",
                        "& .MuiChip-label": {
                          typography: "body2",
                          px: 1.25,
                          lineHeight: 1.2,
                        },
                      } as const;

                      return (
                        <Grid
                          key={process.id}
                          item
                          xs={12}
                          sm={6}
                          md={4}
                          sx={{ display: "flex" }}
                        >
                          <Card
                            sx={{
                              width: "100%",
                              height: "100%",
                              display: "flex",
                              flexDirection: "column",
                              border: "1px solid",
                              borderColor: isCarriedOver ? "warning.main" : "divider",
                              borderRadius: 2,
                              boxShadow: "none",
                              bgcolor: isCarriedOver
                                ? "warning.light"
                                : "background.paper",
                            }}
                          >
                            <Box
                              sx={{
                                p: 2,
                                flexGrow: 1,
                                display: "flex",
                                flexDirection: "column",
                                minHeight: 0,
                              }}
                            >
                              <Stack
                                direction="row"
                                alignItems="flex-start"
                                spacing={0.75}
                              >
                                <Typography
                                  variant="body1"
                                  color="text.primary"
                                  fontWeight={600}
                                  title={
                                    [process.itemCode, process.itemName]
                                      .filter(Boolean)
                                      .join(" ") || undefined
                                  }
                                  sx={{
                                    flex: 1,
                                    minWidth: 0,
                                    display: "-webkit-box",
                                    WebkitLineClamp: 2,
                                    WebkitBoxOrient: "vertical",
                                    overflow: "hidden",
                                    lineHeight: 1.35,
                                  }}
                                >
                                  {[process.itemCode, process.itemName].filter(Boolean).join(" ") || "-"}
                                </Typography>
                                {isCarriedOver ? (
                                  <Tooltip title={t("Carried over from past day")}>
                                    <Avatar
                                      aria-label={t("Carried over from past day")}
                                      sx={{
                                        width: 28,
                                        height: 28,
                                        flexShrink: 0,
                                        bgcolor: "error.main",
                                        color: "error.contrastText",
                                        fontSize: "1rem",
                                        fontWeight: 800,
                                      }}
                                    >
                                      !
                                    </Avatar>
                                  </Tooltip>
                                ) : null}
                              </Stack>

                              <Stack
                                direction="row"
                                alignItems="center"
                                spacing={0.75}
                                flexWrap="nowrap"
                                useFlexGap
                                sx={{
                                  mt: 0.75,
                                  minWidth: 0,
                                  overflowX: "auto",
                                  scrollbarWidth: "none",
                                  "&::-webkit-scrollbar": { display: "none" },
                                }}
                              >
                                <Tooltip title={t("Production Priority")}>
                                  <Avatar
                                    aria-label={`${t("Production Priority")}: ${process.productionPriority ?? "-"}`}
                                    sx={{
                                      width: 28,
                                      height: 28,
                                      flexShrink: 0,
                                      bgcolor: "primary.main",
                                      color: "primary.contrastText",
                                      fontSize: "0.8rem",
                                      fontWeight: 700,
                                    }}
                                  >
                                    {process.productionPriority ?? "-"}
                                  </Avatar>
                                </Tooltip>

                                {bomDescription ? (
                                  <Chip
                                    size="small"
                                    label={t(bomDescription)}
                                    variant="outlined"
                                    sx={chipSx}
                                  />
                                ) : null}
                                {bomType ? (
                                  <Chip
                                    size="small"
                                    label={t(bomType)}
                                    color="info"
                                    variant="outlined"
                                    sx={chipSx}
                                  />
                                ) : null}
                                <Chip
                                  size="small"
                                  label={chipLabel}
                                  color={statusColor as any}
                                  sx={chipSx}
                                />
                              </Stack>

                              <Stack
                                spacing={0.35}
                                sx={{ mt: 0.75, color: "text.secondary" }}
                              >
                                <Typography variant="body2">
                                  {t("Required Qty")}: {process.requiredQty} (
                                  {process.uom})
                                </Typography>
                                <Typography variant="body2">
                                  {t("Production date")}:{" "}
                                  {process.date
                                    ? dayjs(process.date as any).format(
                                        OUTPUT_DATE_FORMAT,
                                      )
                                    : "-"}
                                </Typography>
                                <Typography variant="body2">
                                  {t("Assume Time Need")}:{" "}
                                  {process.timeNeedToComplete} {t("minutes")}
                                </Typography>
                              </Stack>

                              <Stack
                                direction="row"
                                flexWrap="wrap"
                                useFlexGap
                                spacing={1}
                                sx={{ mt: "auto", pt: 1.5 }}
                              >
                                <Button
                                  variant="contained"
                                  size="small"
                                  onClick={() =>
                                    onSelectProcess(process.jobOrderId, process.id)
                                  }
                                >
                                  {t("View Details")}
                                </Button>
                                <Button
                                  variant="contained"
                                  size="small"
                                  disabled={
                                    process.assignedTo != null ||
                                    process.matchStatus == "completed" ||
                                    process.pickOrderStatus != "completed"
                                  }
                                  onClick={() =>
                                    handleAssignPickOrder(
                                      process.pickOrderId,
                                      process.jobOrderId,
                                      process.id,
                                    )
                                  }
                                >
                                  {t("Matching Stock")}
                                </Button>

                                {statusLower !== "completed" && (
                                  <Button
                                    variant="contained"
                                    size="small"
                                    disabled={!canManageUpdateJo}
                                    onClick={() =>
                                      canManageUpdateJo
                                        ? openConfirm(
                                            t("Confirm to update this Job Order?"),
                                            async () => {
                                              await handleUpdateJo(process);
                                            },
                                          )
                                        : undefined
                                    }
                                  >
                                    {t("Update Job Order")}
                                  </Button>
                                )}

                                {canQc && (
                                  <Button
                                    variant="contained"
                                    size="small"
                                    onClick={() => handleViewStockIn(process)}
                                  >
                                    {t("view stockin")}
                                  </Button>
                                )}
                              </Stack>

                              <Typography
                                variant="caption"
                                color="text.secondary"
                                sx={{ mt: 1.25 }}
                              >
                                {jobOrderCode}
                                {" · "}
                                {t("Lot No")}: {process.lotNo ?? "-"}
                              </Typography>
                            </Box>
                          </Card>
                        </Grid>
                      );
                    })}
                  </Grid>
                </Box>
              ))}
            </Box>

            <IconButton
              aria-label={t("Next page")}
              onClick={goNextPage}
              disabled={
                safePage + 1 >= totalPages || filteredProcesses.length === 0
              }
              sx={{ alignSelf: "center" }}
            >
              <ChevronRight />
            </IconButton>
          </Box>
          <QcStockInModal
            session={sessionToken}
            open={openModal}
            onClose={closeNewModal}
            inputDetail={modalInfo}
            printerCombo={printerCombo}
            warehouse={[]}
            printSource="productionProcess"
            uiMode="default"
          />
          <Dialog open={confirmOpen} onClose={closeConfirm} maxWidth="xs" fullWidth>
            <DialogTitle>{t("Confirm")}</DialogTitle>
            <DialogContent>
              <Typography variant="body2">{confirmMessage}</Typography>
            </DialogContent>
            <DialogActions>
              <Button onClick={closeConfirm} disabled={confirmLoading}>
                {t("Cancel")}
              </Button>
              <Button
                variant="contained"
                onClick={onConfirm}
                disabled={confirmLoading || !pendingConfirmAction}
              >
                {confirmLoading ? t("Processing...") : t("Confirm")}
              </Button>
            </DialogActions>
          </Dialog>
          {filteredProcesses.length > 0 && (
            <Typography
              variant="body2"
              color="text.secondary"
              align="center"
              sx={{ mt: 2 }}
            >
              {safePage + 1} / {totalPages}
            </Typography>
          )}
        </Box>
      )}
    </Box>
    
  );
};

export default ProductProcessList;