"use client";

import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
  Box,
  Typography,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
  Paper,
  TextField,
  Alert,
  CircularProgress,
  IconButton,
  Collapse,
  Stack,
  Tooltip,
  Button,
  LinearProgress,
  Chip,
  FormControl,
  FormControlLabel,
  InputLabel,
  MenuItem,
  Select,
  Switch,
} from "@mui/material";
import Link from "next/link";
import dayjs from "dayjs";
import { alpha, useTheme } from "@mui/material/styles";
import ViewKanban from "@mui/icons-material/ViewKanban";
import KeyboardArrowDown from "@mui/icons-material/KeyboardArrowDown";
import KeyboardArrowUp from "@mui/icons-material/KeyboardArrowUp";
import EditCalendar from "@mui/icons-material/EditCalendar";
import Schedule from "@mui/icons-material/Schedule";
import Inventory2 from "@mui/icons-material/Inventory2";
import PrecisionManufacturing from "@mui/icons-material/PrecisionManufacturing";
import Microwave from "@mui/icons-material/Microwave";
import VerifiedUser from "@mui/icons-material/VerifiedUser";
import Warehouse from "@mui/icons-material/Warehouse";
import CheckCircle from "@mui/icons-material/CheckCircle";
import HelpOutline from "@mui/icons-material/HelpOutline";
import { fetchJobOrderBoard, type JobOrderBoardRow } from "@/app/api/chart/client";
import SafeApexCharts from "@/components/charts/SafeApexCharts";
import { CHART_BOARD_REFRESH_INTERVAL_SEC_OPTIONS } from "@/app/(main)/chart/chartBoardRefreshPrefs";
import { useChartBoardRefreshPrefs } from "@/app/(main)/chart/useChartBoardRefreshPrefs";

const PIPELINE = [
  { status: "planning", label: "計劃" },
  { status: "pending", label: "待開" },
  { status: "packaging", label: "包裝" },
  { status: "processing", label: "製程" },
  { status: "pendingqc", label: "QC" },
  { status: "storing", label: "上架" },
  { status: "completed", label: "完成" },
] as const;

const JOB_STATUS_ZH: Record<string, string> = {
  planning: "計劃中",
  pending: "待開工",
  packaging: "包裝",
  processing: "製程中",
  pendingqc: "待質檢",
  storing: "上架中",
  completed: "已完成",
  unknown: "未知",
};

/** 與摘要堆疊長條圖 series 順序一致：QC 中 → 已驗待入 → 已入庫 */
const FG_SUMMARY_SERIES_BUCKETS = ["qc", "ready", "stocked"] as const;
type FgSummaryStockBucket = (typeof FG_SUMMARY_SERIES_BUCKETS)[number];

const FG_SUMMARY_BUCKET_LABEL: Record<FgSummaryStockBucket, string> = {
  qc: "入庫 QC 中（有量）",
  ready: "已驗待入（有量）",
  stocked: "已入庫（有量）",
};

function normStatus(s: string | undefined): string {
  return (s ?? "").trim().toLowerCase();
}

function statusLabelZh(status: string): string {
  return JOB_STATUS_ZH[normStatus(status)] ?? status;
}

function isKnownJobStatus(status: string): boolean {
  return normStatus(status) in JOB_STATUS_ZH;
}

function StatusIcon({ status }: { status: string }) {
  const s = normStatus(status);
  const sx = { fontSize: 28, opacity: 0.95 };
  switch (s) {
    case "planning":
      return <EditCalendar sx={{ ...sx, color: "text.secondary" }} />;
    case "pending":
      return <Schedule sx={{ ...sx, color: "secondary.main" }} />;
    case "packaging":
      return <Inventory2 sx={{ ...sx, color: "info.main" }} />;
    case "processing":
      return <PrecisionManufacturing sx={{ ...sx, color: "info.dark" }} />;
    case "pendingqc":
      return <VerifiedUser sx={{ ...sx, color: "warning.main" }} />;
    case "storing":
      return <Warehouse sx={{ ...sx, color: "warning.dark" }} />;
    case "completed":
      return <CheckCircle sx={{ ...sx, color: "success.main" }} />;
    default:
      return <HelpOutline sx={{ ...sx, color: "text.disabled" }} />;
  }
}

function statusStepIndex(status: string): number {
  const s = normStatus(status);
  const i = PIPELINE.findIndex((p) => p.status === s);
  return i >= 0 ? i : 0;
}

/** Dot chain — current step ring highlight */
function PipelineDots({ status }: { status: string }) {
  const idx = statusStepIndex(status);
  const allDone = normStatus(status) === "completed";
  return (
    <Stack direction="row" alignItems="center" sx={{ flexWrap: "wrap", gap: 0.25 }}>
      {PIPELINE.map((step, i) => {
        const done = allDone || i < idx;
        const current = !allDone && i === idx;
        return (
          <Tooltip key={step.status} title={step.label}>
            <Box
              sx={{
                width: current ? 12 : 9,
                height: current ? 12 : 9,
                borderRadius: "50%",
                bgcolor: done ? "success.main" : current ? "primary.main" : "grey.300",
                boxShadow: current ? 2 : 0,
                border: current ? "2px solid" : "none",
                borderColor: "warning.light",
                flexShrink: 0,
              }}
            />
          </Tooltip>
        );
      })}
    </Stack>
  );
}

function formatQty(n: number): string {
  if (!Number.isFinite(n) || n === 0) return "0";
  const t = Number(n);
  return Math.abs(t - Math.round(t)) < 1e-6 ? String(Math.round(t)) : t.toFixed(2);
}

/** 與工藝流程摘要：開始時間 + 僅生產分鐘（不含備料/轉換） */
function formatAssumeEndMmDdHhMm(processStart: string, planProcessingMins: number): string {
  if (!processStart?.trim() || !planProcessingMins) return "—";
  const d = dayjs(processStart.replace(" ", "T"));
  if (!d.isValid()) return "—";
  return d.add(planProcessingMins, "minute").format("MM-DD HH:mm");
}

/** Backend sends decimal minutes (from summed seconds ÷ 60). */
function formatDurationMins(mins: number): string {
  if (!Number.isFinite(mins) || mins <= 0) return "—";
  const secs = Math.round(mins * 60);
  if (secs < 60) return `${secs} 秒`;
  if (mins < 10) return `${mins.toFixed(1)} 分`;
  return `${Math.round(mins)} 分`;
}

/** Avoid "CODE CODE" or "X X" when DB code/name duplicate or name already includes code. */
function formatCurrentProcessLabel(code: string, name: string): string {
  const c = (code ?? "").trim();
  const n = (name ?? "").trim();
  if (!c && !n) return "—";
  if (!c) return n;
  if (!n) return c;
  const lc = c.toLowerCase();
  const ln = n.toLowerCase();
  if (lc === ln) return n;
  if (ln.startsWith(`${lc} `) || ln.startsWith(`${lc}　`)) return n;
  if (n.length > c.length && ln.startsWith(lc)) {
    const after = n.slice(c.length, c.length + 1);
    if (/[\s\-–—:：·.|／/]/.test(after)) return n;
  }
  return `${c} ${n}`;
}

function MaterialBar({ pending, picked }: { pending: number; picked: number }) {
  const total = pending + picked;
  if (total <= 0) {
    return (
      <Typography variant="caption" color="text.disabled">
        無領料行
      </Typography>
    );
  }
  const pct = (100 * picked) / total;
  return (
    <Box sx={{ minWidth: 100, maxWidth: 160 }}>
      <LinearProgress
        variant="determinate"
        value={pct}
        sx={{ height: 8, borderRadius: 1, bgcolor: "grey.200" }}
        color={pct >= 100 ? "success" : "primary"}
      />
      <Typography variant="caption" color="text.secondary" display="block" sx={{ mt: 0.25 }}>
        已揀 {picked} / 共 {total}
      </Typography>
    </Box>
  );
}

function ProcessBar({ done, total }: { done: number; total: number }) {
  if (total <= 0) {
    return (
      <Typography variant="caption" color="text.disabled">
        無工序
      </Typography>
    );
  }
  const pct = (100 * done) / total;
  return (
    <Box sx={{ minWidth: 100, maxWidth: 160 }}>
      <LinearProgress
        variant="determinate"
        value={pct}
        sx={{ height: 8, borderRadius: 1, bgcolor: "grey.200" }}
        color={done >= total ? "success" : "info"}
      />
      <Typography variant="caption" color="text.secondary" display="block" sx={{ mt: 0.25 }}>
        {done}/{total}
      </Typography>
    </Box>
  );
}

/** 成品／半成品入庫：QC 中 | 已驗待入 | 已入庫 — by qty */
function FgStockBar({ qc, ready, stocked }: { qc: number; ready: number; stocked: number }) {
  const sum = qc + ready + stocked;
  if (sum <= 0) {
    return (
      <Typography variant="caption" color="text.disabled">
        無入庫數量資料
      </Typography>
    );
  }
  const pQc = (100 * qc) / sum;
  const pReady = (100 * ready) / sum;
  const pStocked = 100 - pQc - pReady;
  return (
    <Box sx={{ minWidth: 120, maxWidth: 200 }}>
      <Stack direction="row" sx={{ height: 10, borderRadius: 1, overflow: "hidden", bgcolor: "grey.200" }}>
        <Tooltip title={`QC 中 ${formatQty(qc)}`}>
          <Box sx={{ width: `${pQc}%`, bgcolor: "warning.main" }} />
        </Tooltip>
        <Tooltip title={`已驗待入 ${formatQty(ready)}`}>
          <Box sx={{ width: `${pReady}%`, bgcolor: "info.main" }} />
        </Tooltip>
        <Tooltip title={`已入庫 ${formatQty(stocked)}`}>
          <Box sx={{ width: `${pStocked}%`, bgcolor: "success.main" }} />
        </Tooltip>
      </Stack>
      <Typography variant="caption" color="text.secondary" display="block" sx={{ mt: 0.35 }} noWrap>
        QC {formatQty(qc)} · 待入 {formatQty(ready)} · 已入 {formatQty(stocked)}
      </Typography>
    </Box>
  );
}

const COLSPAN = 8;

function DetailLine({ label, children }: { label: string; children: React.ReactNode }) {
  return (
    <Stack direction="row" spacing={1.5} alignItems="baseline" flexWrap="wrap" useFlexGap sx={{ columnGap: 1.5, rowGap: 0.25 }}>
      <Typography component="span" variant="body2" color="text.secondary" sx={{ minWidth: 108, flexShrink: 0 }}>
        {label}
      </Typography>
      <Typography component="span" variant="body2" color="text.primary">
        {children}
      </Typography>
    </Stack>
  );
}

export default function JobOrderBoardPage() {
  const theme = useTheme();
  const [targetDate, setTargetDate] = useState(() => dayjs().format("YYYY-MM-DD"));
  /** true: load all non-completed JOs (no plan-start date); false: filter by targetDate */
  const [allIncompleteOpen, setAllIncompleteOpen] = useState(false);
  const [rows, setRows] = useState<JobOrderBoardRow[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [lastUpdated, setLastUpdated] = useState("");
  const [openId, setOpenId] = useState<number | null>(null);
  /** Normalized status key from donut (e.g. pending); null = not filtering by stage */
  const [tableStatusKey, setTableStatusKey] = useState<string | null>(null);
  /** Summary bar: filter rows that have FG/WIP stock-in in that bucket; mutually exclusive with tableStatusKey in UI */
  const [tableStockBucket, setTableStockBucket] = useState<FgSummaryStockBucket | null>(null);
  const { autoRefreshOn, setAutoRefreshOn, refreshIntervalSec, setRefreshIntervalSec } =
    useChartBoardRefreshPrefs("joborder");

  const load = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const data = allIncompleteOpen
        ? await fetchJobOrderBoard(undefined, { incompleteOnly: true })
        : await fetchJobOrderBoard(targetDate);
      setRows(data);
      setLastUpdated(dayjs().format("YYYY-MM-DD HH:mm:ss"));
    } catch (e) {
      setError(e instanceof Error ? e.message : "Request failed");
    } finally {
      setLoading(false);
    }
  }, [allIncompleteOpen, targetDate]);

  useEffect(() => {
    void load();
  }, [load]);

  useEffect(() => {
    if (!autoRefreshOn || refreshIntervalSec < 10) return;
    const t = setInterval(() => void load(), refreshIntervalSec * 1000);
    return () => clearInterval(t);
  }, [load, autoRefreshOn, refreshIntervalSec]);

  useEffect(() => {
    setTableStatusKey(null);
    setTableStockBucket(null);
  }, [targetDate, allIncompleteOpen]);

  useEffect(() => {
    setOpenId(null);
  }, [tableStatusKey, tableStockBucket]);

  useEffect(() => {
    const onKey = (ev: KeyboardEvent) => {
      const t = ev.target as HTMLElement | null;
      if (
        t &&
        (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT" || t.isContentEditable)
      ) {
        return;
      }
      if (ev.ctrlKey || ev.metaKey || ev.altKey) return;
      if (ev.key === "t" || ev.key === "T") {
        ev.preventDefault();
        setTargetDate(dayjs().format("YYYY-MM-DD"));
      }
      if (ev.key === "y" || ev.key === "Y") {
        ev.preventDefault();
        setTargetDate(dayjs().subtract(1, "day").format("YYYY-MM-DD"));
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  const statusDonut = useMemo(() => {
    const map = new Map<string, number>();
    rows.forEach((r) => {
      const k = normStatus(r.status) || "unknown";
      map.set(k, (map.get(k) ?? 0) + 1);
    });
    const keysAll = Array.from(map.keys());
    const pairs = keysAll
      .map((k) => {
        const v = map.get(k) ?? 0;
        const n = Number.isFinite(v) && v > 0 ? v : 0;
        return { key: k, label: JOB_STATUS_ZH[k] ?? k, v: n };
      })
      .filter((p) => p.v > 0);
    return {
      keys: pairs.map((p) => p.key),
      labels: pairs.map((p) => p.label),
      series: pairs.map((p) => p.v),
    };
  }, [rows]);

  const displayRows = useMemo(() => {
    const fin = (n: number) => (Number.isFinite(n) ? n : 0);
    if (tableStockBucket) {
      return rows.filter((r) => {
        if (tableStockBucket === "qc") return fin(r.fgInQcQty) > 0 || (r.fgInQcLineCount ?? 0) > 0;
        if (tableStockBucket === "ready") return fin(r.fgReadyToStockInQty) > 0 || (r.fgReadyToStockInCount ?? 0) > 0;
        if (tableStockBucket === "stocked") return fin(r.fgStockedQty) > 0;
        return true;
      });
    }
    if (tableStatusKey) {
      return rows.filter((r) => (normStatus(r.status) || "unknown") === tableStatusKey);
    }
    return rows;
  }, [rows, tableStatusKey, tableStockBucket]);

  const donutSliceClickRef = useRef<(index: number) => void>(() => {});
  donutSliceClickRef.current = (index: number) => {
    setTableStockBucket(null);
    const key = statusDonut.keys[index];
    if (key == null) return;
    setTableStatusKey((prev) => (prev === key ? null : key));
  };

  const fgBarSeriesClickRef = useRef<(seriesIndex: number) => void>(() => {});
  fgBarSeriesClickRef.current = (seriesIndex: number) => {
    if (typeof seriesIndex !== "number" || seriesIndex < 0 || seriesIndex >= FG_SUMMARY_SERIES_BUCKETS.length) return;
    setTableStatusKey(null);
    const bucket = FG_SUMMARY_SERIES_BUCKETS[seriesIndex];
    setTableStockBucket((prev) => (prev === bucket ? null : bucket));
  };

  const statusDonutChartOptions = useMemo(
    () => ({
      chart: {
        type: "donut" as const,
        toolbar: { show: false },
        events: {
          dataPointSelection: (_e: unknown, _ctx: unknown, cfg: { dataPointIndex?: number }) => {
            const i = cfg?.dataPointIndex;
            if (typeof i !== "number" || i < 0) return;
            donutSliceClickRef.current(i);
          },
          /** Donut/pie: legend index matches slice index */
          legendClick: (_chartContext: unknown, seriesIndex: number) => {
            if (typeof seriesIndex !== "number" || seriesIndex < 0) return false;
            donutSliceClickRef.current(seriesIndex);
            return false;
          },
        },
      },
      labels: statusDonut.labels,
      legend: {
        position: "bottom" as const,
        onItemClick: { toggleDataSeries: false },
      },
      plotOptions: { pie: { donut: { size: "62%" } } },
      dataLabels: { enabled: true },
    }),
    [statusDonut.labels],
  );

  const fgSummaryChartOptions = useMemo(
    () => ({
      chart: {
        type: "bar" as const,
        stacked: true,
        toolbar: { show: false },
        events: {
          dataPointSelection: (_e: unknown, _ctx: unknown, cfg: { seriesIndex?: number; dataPointIndex?: number }) => {
            const si = cfg?.seriesIndex;
            if (typeof si !== "number") return;
            fgBarSeriesClickRef.current(si);
          },
          legendClick: (_chartContext: unknown, seriesIndex: number) => {
            if (typeof seriesIndex !== "number" || seriesIndex < 0) return false;
            fgBarSeriesClickRef.current(seriesIndex);
            return false;
          },
        },
      },
      plotOptions: { bar: { horizontal: true, barHeight: "48%" } },
      xaxis: { categories: ["合計"] },
      colors: ["#ed6c02", "#0288d1", "#2e7d32"],
      legend: {
        position: "top" as const,
        onItemClick: { toggleDataSeries: false },
      },
      dataLabels: { enabled: false },
    }),
    [],
  );

  const materialSummary = useMemo(() => {
    let p = 0;
    let k = 0;
    rows.forEach((r) => {
      p += r.materialPendingCount;
      k += r.materialPickedCount;
    });
    return { pending: p, picked: k };
  }, [rows]);

  const fgTotals = useMemo(() => {
    const fin = (n: number) => (Number.isFinite(n) ? n : 0);
    let qc = 0;
    let ready = 0;
    let stocked = 0;
    rows.forEach((r) => {
      qc += fin(r.fgInQcQty);
      ready += fin(r.fgReadyToStockInQty);
      stocked += fin(r.fgStockedQty);
    });
    return { qc, ready, stocked };
  }, [rows]);

  const isPlanToday = targetDate === dayjs().format("YYYY-MM-DD");
  const planWeekdayZh = ["日", "一", "二", "三", "四", "五", "六"][dayjs(targetDate).day()] ?? "";

  return (
    <Box
      sx={{
        width: "100%",
        maxWidth: "100%",
        mx: "auto",
        p: { xs: 0.5, sm: 1 },
        boxSizing: "border-box",
      }}
    >
      <Typography variant="h5" sx={{ mb: 1, fontWeight: 600, display: "flex", alignItems: "center", gap: 1 }}>
        <ViewKanban /> 工單即時看板
      </Typography>
      <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
        圖示化階段與進度條；入庫欄僅統計<strong>成品／半成品</strong>（系統 FG/WIP）工單之入庫明細（依驗收數量）。「已驗待入」= 狀態為 receiving /
        received（QC 完成待上架）。
        {autoRefreshOn
          ? ` 已開啟自動重新整理（每 ${refreshIntervalSec} 秒）`
          : " 預設不自動更新，可開啟「自動重新整理」並選擇間隔。"}
        {" 設定自動儲存在本機（登入時依帳號；未登入則為此分頁工作階段）。"}
        {lastUpdated ? ` · 最後更新 ${lastUpdated}` : ""}
        <strong> 快捷鍵</strong>（不在輸入框內時）：
        <Box
          component="kbd"
          sx={{ px: 0.75, py: 0.125, borderRadius: 0.5, border: 1, borderColor: "divider", fontSize: "0.75rem" }}
        >
          T
        </Box>{" "}
        今日、
        <Box
          component="kbd"
          sx={{ px: 0.75, py: 0.125, borderRadius: 0.5, border: 1, borderColor: "divider", fontSize: "0.75rem" }}
        >
          Y
        </Box>{" "}
        昨日（僅更新計劃開始日）。
      </Typography>
      {error && (
        <Alert severity="error" sx={{ mb: 2 }} onClose={() => setError(null)}>
          {error}
        </Alert>
      )}

      <Stack
        direction={{ xs: "column", lg: "row" }}
        spacing={2}
        alignItems={{ xs: "stretch", lg: "stretch" }}
        justifyContent={{ lg: "space-between" }}
        sx={{ mb: 2 }}
      >
        <Paper
          variant="outlined"
          sx={{
            p: 1.5,
            flex: { lg: "1 1 0" },
            minWidth: { xs: "100%", lg: 280 },
            borderColor: "divider",
            bgcolor: alpha(theme.palette.primary.main, 0.03),
          }}
        >
          <Typography
            variant="overline"
            color="text.secondary"
            sx={{ display: "block", mb: 1, lineHeight: 1.4, letterSpacing: 0.6 }}
          >
            查詢與列表
          </Typography>
          <Stack spacing={1.25}>
            <Typography variant="body2" color="text.secondary" sx={{ fontWeight: 600 }}>
              計劃開始日 {targetDate}（週{planWeekdayZh}）
              {!isPlanToday && <Chip size="small" label="非今日" sx={{ ml: 1 }} variant="outlined" />}
            </Typography>
            <Stack direction="row" flexWrap="wrap" useFlexGap alignItems="center" gap={1.25}>
              <Tooltip title="快捷鍵 T">
                <span>
                  <Button
                    size="small"
                    variant={isPlanToday && !allIncompleteOpen ? "contained" : "outlined"}
                    onClick={() => setTargetDate(dayjs().format("YYYY-MM-DD"))}
                    disabled={allIncompleteOpen}
                  >
                    今日
                  </Button>
                </span>
              </Tooltip>
              <Tooltip title="快捷鍵 Y">
                <span>
                  <Button
                    size="small"
                    variant="outlined"
                    onClick={() => setTargetDate(dayjs().subtract(1, "day").format("YYYY-MM-DD"))}
                    disabled={allIncompleteOpen}
                  >
                    昨日
                  </Button>
                </span>
              </Tooltip>
              <TextField
                size="small"
                label="計劃開始日"
                type="date"
                value={targetDate}
                onChange={(e) => setTargetDate(e.target.value)}
                disabled={allIncompleteOpen}
                InputLabelProps={{ shrink: true }}
                sx={{ minWidth: 178 }}
              />
              <Button variant="outlined" size="small" onClick={() => void load()} disabled={loading}>
                重新整理
              </Button>
            </Stack>
            <Tooltip
              title={
                <>
                  <Typography variant="caption" component="span" display="block" sx={{ mb: 0.5 }}>
                    載入<strong>全部未完成</strong>工單（階段≠已完成），不篩「計劃開始日」。筆數多時仍可能較慢。
                  </Typography>
                  <Typography variant="caption" component="span" display="block">
                    再按一次可切回依「計劃開始日」載入。
                  </Typography>
                </>
              }
              arrow
              placement="top-start"
            >
              <span>
                <Button
                  variant={allIncompleteOpen ? "contained" : "outlined"}
                  color={allIncompleteOpen ? "warning" : "primary"}
                  size="small"
                  onClick={() => setAllIncompleteOpen((v) => !v)}
                  disabled={loading}
                  aria-pressed={allIncompleteOpen}
                  sx={{ alignSelf: "flex-start" }}
                >
                  {allIncompleteOpen ? "改依計劃開始日" : "顯示所有未完成工單"}
                </Button>
              </span>
            </Tooltip>
          </Stack>
        </Paper>

        <Paper
          variant="outlined"
          sx={{
            p: 1.5,
            flex: { lg: "0 0 auto" },
            width: { xs: "100%", lg: "auto" },
            minWidth: { lg: 200 },
            borderColor: "divider",
            bgcolor: alpha(theme.palette.grey[500], 0.06),
          }}
        >
          <Typography
            variant="overline"
            color="text.secondary"
            sx={{ display: "block", mb: 1, lineHeight: 1.4, letterSpacing: 0.6 }}
          >
            其他看板
          </Typography>
          <Stack direction="column" spacing={1} sx={{ maxWidth: 220 }}>
            <Button component={Link} href="/chart/equipment/board" size="small" variant="outlined" fullWidth startIcon={<Microwave />}>
              設備使用看板
            </Button>
            <Button component={Link} href="/chart/process/board" size="small" variant="outlined" fullWidth>
              工序即時看板
            </Button>
            <Button component={Link} href="/chart/joborder" size="small" variant="outlined" fullWidth>
              工單圖表
            </Button>
          </Stack>
        </Paper>

        <Paper
          variant="outlined"
          sx={{
            p: 1.5,
            flex: { lg: "0 0 auto" },
            width: { xs: "100%", lg: "auto" },
            minWidth: { xs: "100%", lg: 300 },
            borderColor: "divider",
            bgcolor: alpha(theme.palette.info.main, 0.04),
          }}
        >
          <Typography
            variant="overline"
            color="text.secondary"
            sx={{ display: "block", mb: 1, lineHeight: 1.4, letterSpacing: 0.6 }}
          >
            自動重新整理
          </Typography>
          <Stack direction="row" flexWrap="wrap" useFlexGap alignItems="center" gap={1.25}>
            <FormControlLabel
              control={
                <Switch
                  size="small"
                  checked={autoRefreshOn}
                  onChange={(_, v) => setAutoRefreshOn(v)}
                  inputProps={{ "aria-label": "自動重新整理" }}
                />
              }
              label="開啟"
              sx={{ ml: 0, mr: 0 }}
            />
            <FormControl size="small" sx={{ minWidth: 124 }} disabled={!autoRefreshOn}>
              <InputLabel id="jo-board-refresh-interval-label">間隔（秒）</InputLabel>
              <Select
                labelId="jo-board-refresh-interval-label"
                label="間隔（秒）"
                value={refreshIntervalSec}
                onChange={(e) => setRefreshIntervalSec(Number(e.target.value))}
              >
                {CHART_BOARD_REFRESH_INTERVAL_SEC_OPTIONS.map((sec) => (
                  <MenuItem key={sec} value={sec}>
                    {sec} 秒
                  </MenuItem>
                ))}
              </Select>
            </FormControl>
          </Stack>
        </Paper>
      </Stack>

      {!loading && rows.length > 0 && (
        <Stack direction={{ xs: "column", md: "row" }} spacing={2} sx={{ mb: 3 }}>
          <Paper variant="outlined" sx={{ p: 2, flex: 1, minWidth: 280 }}>
            <Typography variant="subtitle2" fontWeight={600} gutterBottom>
              工單狀態分佈
            </Typography>
            <Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}>
              點擊圓環或下方圖例可依<strong>階段</strong>篩選列表；再點同一項或表格上方「清除列表篩選」可還原。
            </Typography>
            {statusDonut.series.length > 0 ? (
              <SafeApexCharts
                chartRevision={JSON.stringify(statusDonut.keys)}
                options={statusDonutChartOptions}
                series={statusDonut.series}
                type="donut"
                height={280}
              />
            ) : (
              <Typography color="text.secondary">無資料</Typography>
            )}
          </Paper>
          <Paper variant="outlined" sx={{ p: 2, flex: 1, minWidth: 280 }}>
            <Typography variant="subtitle2" fontWeight={600} gutterBottom>
              本頁摘要
            </Typography>
            <Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
              領料：待領 {materialSummary.pending} / 已揀 {materialSummary.picked}
            </Typography>
            <MaterialBar pending={materialSummary.pending} picked={materialSummary.picked} />
            <Typography variant="body2" color="text.secondary" sx={{ mt: 2, mb: 0.5 }}>
              成品／半成品入庫（驗收數量）— QC 中／已驗待入／已入庫
            </Typography>
            <Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 0.5 }}>
              點擊長條色塊或上方圖例，篩選該入庫區塊有資料的工單；再點一次可還原。
            </Typography>
            <FgStockBar qc={fgTotals.qc} ready={fgTotals.ready} stocked={fgTotals.stocked} />
            {fgTotals.qc + fgTotals.ready + fgTotals.stocked > 0 ? (
              <>
                <SafeApexCharts
                  chartRevision={`fg-bar-${fgTotals.qc}-${fgTotals.ready}-${fgTotals.stocked}`}
                  options={fgSummaryChartOptions}
                  series={[
                    { name: "QC 中", data: [Math.max(0, fgTotals.qc)] },
                    { name: "已驗待入", data: [Math.max(0, fgTotals.ready)] },
                    { name: "已入庫", data: [Math.max(0, fgTotals.stocked)] },
                  ]}
                  type="bar"
                  height={140}
                />
                <Button
                  size="small"
                  variant="text"
                  sx={{ mt: 0.5, p: 0, minHeight: "auto" }}
                  onClick={() => {
                    setTableStockBucket(null);
                    setTableStatusKey((p) => (p === "processing" ? null : "processing"));
                  }}
                >
                  {tableStatusKey === "processing" ? "取消「製程中」篩選" : "篩選製程中工單（階段）"}
                </Button>
              </>
            ) : (
              <>
                <Typography variant="caption" color="text.secondary" sx={{ display: "block", mt: 1 }}>
                  驗收數量合計為 0，不顯示長條圖
                </Typography>
                <Button
                  size="small"
                  variant="text"
                  sx={{ mt: 0.5, p: 0, minHeight: "auto" }}
                  onClick={() => {
                    setTableStockBucket(null);
                    setTableStatusKey((p) => (p === "processing" ? null : "processing"));
                  }}
                >
                  {tableStatusKey === "processing" ? "取消「製程中」篩選" : "篩選製程中工單（階段）"}
                </Button>
              </>
            )}
          </Paper>
        </Stack>
      )}

      {loading && rows.length === 0 ? (
        <Box sx={{ display: "flex", justifyContent: "center", py: 6 }}>
          <CircularProgress />
        </Box>
      ) : rows.length === 0 ? (
        <Paper variant="outlined" sx={{ p: 4, textAlign: "center" }}>
          <Typography color="text.secondary">此條件下沒有工單</Typography>
        </Paper>
      ) : (
        <TableContainer component={Paper} variant="outlined">
          {(tableStatusKey || tableStockBucket) && (
            <Box sx={{ px: 2, py: 1.5, borderBottom: 1, borderColor: "divider", bgcolor: "action.hover" }}>
              <Stack direction="row" alignItems="center" spacing={1} flexWrap="wrap" useFlexGap>
                <Typography variant="body2" color="text.secondary">
                  列表篩選中：
                </Typography>
                {tableStockBucket && (
                  <Chip
                    label={FG_SUMMARY_BUCKET_LABEL[tableStockBucket]}
                    size="small"
                    color="secondary"
                    variant="outlined"
                    onDelete={() => setTableStockBucket(null)}
                  />
                )}
                {tableStatusKey && (
                  <Chip
                    label={`階段：${statusLabelZh(tableStatusKey)}`}
                    size="small"
                    color="primary"
                    variant="outlined"
                    onDelete={() => setTableStatusKey(null)}
                  />
                )}
                <Button
                  size="small"
                  variant="text"
                  onClick={() => {
                    setTableStatusKey(null);
                    setTableStockBucket(null);
                  }}
                >
                  清除列表篩選
                </Button>
              </Stack>
            </Box>
          )}
          <Table size="small" stickyHeader>
            <TableHead>
              <TableRow>
                <TableCell padding="checkbox" />
                <TableCell>工單號</TableCell>
                <TableCell sx={{ minWidth: 200 }}>階段</TableCell>
                <TableCell>領料進度</TableCell>
                <TableCell>工序進度</TableCell>
                <TableCell>當前製程</TableCell>
                <TableCell sx={{ minWidth: 200 }}>成品／半成品入庫</TableCell>
                <TableCell align="center">開啟</TableCell>
              </TableRow>
            </TableHead>
            <TableBody>
              {displayRows.length === 0 ? (
                <TableRow>
                  <TableCell colSpan={COLSPAN} align="center" sx={{ py: 4 }}>
                    <Typography color="text.secondary">
                      {tableStockBucket
                        ? "目前沒有符合此入庫區塊的工單（可點「清除列表篩選」）。"
                        : tableStatusKey
                          ? "此階段下目前沒有工單（可點「清除列表篩選」）。"
                          : "沒有資料"}
                    </Typography>
                  </TableCell>
                </TableRow>
              ) : (
                displayRows.map((r) => (
                <React.Fragment key={r.jobOrderId}>
                  <TableRow hover>
                    <TableCell>
                      <IconButton
                        size="small"
                        aria-label="expand row"
                        onClick={() => setOpenId((id) => (id === r.jobOrderId ? null : r.jobOrderId))}
                      >
                        {openId === r.jobOrderId ? <KeyboardArrowUp /> : <KeyboardArrowDown />}
                      </IconButton>
                    </TableCell>
                    <TableCell sx={{ fontWeight: 600 }}>{r.code}</TableCell>
                    <TableCell>
                      <Stack direction="row" spacing={1} alignItems="center" flexWrap="wrap" useFlexGap>
                        <StatusIcon status={r.status} />
                        <Box>
                          <Typography variant="body2" fontWeight={700}>
                            {statusLabelZh(r.status)}
                          </Typography>
                          <PipelineDots status={r.status} />
                        </Box>
                      </Stack>
                    </TableCell>
                    <TableCell>
                      <MaterialBar pending={r.materialPendingCount} picked={r.materialPickedCount} />
                    </TableCell>
                    <TableCell>
                      <ProcessBar done={r.processCompletedCount} total={r.processTotalCount} />
                    </TableCell>
                    <TableCell sx={{ maxWidth: 200 }}>
                      <Typography variant="body2" noWrap title={formatCurrentProcessLabel(r.currentProcessCode, r.currentProcessName)}>
                        {formatCurrentProcessLabel(r.currentProcessCode, r.currentProcessName)}
                      </Typography>
                    </TableCell>
                    <TableCell>
                      <FgStockBar qc={r.fgInQcQty} ready={r.fgReadyToStockInQty} stocked={r.fgStockedQty} />
                      <Typography variant="caption" color="text.secondary" display="block" sx={{ mt: 0.5 }}>
                        已驗待入 <strong>{r.fgReadyToStockInCount}</strong> 行，驗收數量 {formatQty(r.fgReadyToStockInQty)}；全單驗收合計{" "}
                        {formatQty(r.stockInAcceptedQtyTotal)}
                      </Typography>
                    </TableCell>
                    <TableCell align="center">
                      <Button
                        component={Link}
                        href={`/jo/edit?id=${r.jobOrderId}`}
                        target="_blank"
                        rel="noopener noreferrer"
                        size="small"
                      >
                        開啟
                      </Button>
                    </TableCell>
                  </TableRow>
                  <TableRow>
                    <TableCell sx={{ py: 0, borderBottom: openId === r.jobOrderId ? undefined : 0 }} colSpan={COLSPAN}>
                      <Collapse in={openId === r.jobOrderId} timeout="auto" unmountOnExit>
                        <Box sx={{ py: 2, px: 1 }}>
                          <Typography variant="subtitle2" gutterBottom>
                            明細
                          </Typography>
                          <Stack spacing={1} sx={{ pl: 0.5 }}>
                            <Typography variant="caption" color="primary" fontWeight={700} display="block" sx={{ mb: 0.5 }}>
                              生產流程摘要（與工單·工藝流程一致，來自產線彙總）
                            </Typography>
                            <Stack
                              direction={{ xs: "column", sm: "row" }}
                              spacing={2}
                              alignItems="flex-start"
                              sx={{ pl: 0.5 }}
                            >
                              <Stack spacing={1} sx={{ flex: 1, minWidth: 0 }}>
                                <Typography variant="body2" fontWeight={600} sx={{ lineHeight: 1.35 }}>
                                  {r.itemCode || r.itemName
                                    ? `${r.itemCode || ""}${r.itemCode && r.itemName ? "-" : ""}${r.itemName || ""}`
                                    : "—"}
                                </Typography>
                                <DetailLine label="工單類型">{r.jobTypeName?.trim() ? r.jobTypeName : "—"}</DetailLine>
                                <DetailLine label="數量">
                                  {formatQty(r.reqQty)}
                                  {r.outputQtyUom ? `（${r.outputQtyUom}）` : ""}
                                </DetailLine>
                                <DetailLine label="生產日期">{r.productionDate || "—"}</DetailLine>
                                <DetailLine label="實際耗時（各線起訖加總）">{formatDurationMins(r.actualLineMinsTotal)}</DetailLine>
                              </Stack>
                              <Stack
                                spacing={1}
                                sx={{
                                  flex: 1,
                                  minWidth: 0,
                                  pl: { sm: 2 },
                                  borderLeft: { sm: 1 },
                                  borderTop: { xs: 1, sm: 0 },
                                  borderColor: "divider",
                                  pt: { xs: 1.5, sm: 0 },
                                }}
                              >
                                <DetailLine label="預計生產時間">
                                  {r.planProcessingMinsTotal > 0 ? `${Math.round(r.planProcessingMinsTotal)} 分鐘` : "—"}
                                  {r.planSetupChangeoverMinsTotal > 0
                                    ? ` · 備料/轉換合計 ${Math.round(r.planSetupChangeoverMinsTotal)} 分鐘`
                                    : ""}
                                </DetailLine>
                                <DetailLine label="開始時間（產程）">{r.productProcessStart || "—"}</DetailLine>
                                <DetailLine label="預計完成時間">
                                  {formatAssumeEndMmDdHhMm(r.productProcessStart, r.planProcessingMinsTotal)}
                                </DetailLine>
                              </Stack>
                            </Stack>
                            <Typography variant="caption" color="text.secondary" fontWeight={700} display="block" sx={{ mt: 1.5, mb: 0.25 }}>
                              工單計劃／實際
                            </Typography>
                            <DetailLine label="計劃開始">{r.planStart || "—"}</DetailLine>
                            <DetailLine label="實際開始">{r.actualStart || "—"}</DetailLine>
                            <DetailLine label="計劃結束">{r.planEnd || "—"}</DetailLine>
                            <DetailLine label="實際結束">{r.actualEnd || "—"}</DetailLine>
                            <DetailLine label="當前製程開始">{r.currentProcessStartTime || "—"}</DetailLine>
                            <DetailLine label="階段">
                              <Tooltip
                                title={
                                  isKnownJobStatus(r.status)
                                    ? `系統狀態碼：${r.status}`
                                    : `未定義對應中文，原始值：${r.status}`
                                }
                                arrow
                                enterDelay={400}
                              >
                                <Box component="span" sx={{ cursor: "default" }}>
                                  {statusLabelZh(r.status)}
                                </Box>
                              </Tooltip>
                            </DetailLine>
                            <Box sx={{ pt: 0.5 }}>
                              <Typography variant="caption" color="text.secondary" fontWeight={600} display="block" sx={{ mb: 0.75 }}>
                                成品／半成品入庫狀況
                              </Typography>
                              <Stack spacing={0.75} sx={{ pl: 0.5 }}>
                                <DetailLine label="QC 中">
                                  {formatQty(r.fgInQcQty)}（{r.fgInQcLineCount} 行）
                                </DetailLine>
                                <DetailLine label="已驗待入">
                                  {formatQty(r.fgReadyToStockInQty)}（{r.fgReadyToStockInCount} 行）
                                </DetailLine>
                                <DetailLine label="已入庫">{formatQty(r.fgStockedQty)}</DetailLine>
                                <DetailLine label="全單驗收合計">{formatQty(r.stockInAcceptedQtyTotal)}</DetailLine>
                              </Stack>
                            </Box>
                          </Stack>
                        </Box>
                      </Collapse>
                    </TableCell>
                  </TableRow>
                </React.Fragment>
              ))
              )}
            </TableBody>
          </Table>
        </TableContainer>
      )}
    </Box>
  );
}
