"use client";

/**
 * pop-card-list-v2 셀 타입별 렌더러
 *
 * 각 셀 타입은 독립 함수로 구현되어 CardV2Grid에서 type별 dispatch로 호출된다.
 * 기존 pop-card-list의 카드 내부 렌더링과 pop-string-list의 CardModeView 패턴을 결합.
 */

import React, { useMemo, useState } from "react";
import {
  X, Package, Truck, Box, Archive, Heart, Star,
  Loader2, CheckCircle2, CircleDot, Clock, Check, ChevronRight,
  type LucideIcon,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import {
  Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription,
} from "@/components/ui/dialog";
import { cn } from "@/lib/utils";
import type { CardCellDefinitionV2, PackageEntry, TimelineProcessStep, ActionButtonDef } from "../types";
import { DEFAULT_CARD_IMAGE, VIRTUAL_SUB_STATUS, VIRTUAL_SUB_SEMANTIC } from "../types";
import type { ButtonVariant } from "../pop-button";

type RowData = Record<string, unknown>;

// ===== 공통 유틸 =====

const LUCIDE_ICON_MAP: Record<string, LucideIcon> = {
  Package, Truck, Box, Archive, Heart, Star,
};

function formatValue(value: unknown): string {
  if (value === null || value === undefined) return "-";
  if (typeof value === "number") return value.toLocaleString();
  if (typeof value === "boolean") return value ? "예" : "아니오";
  if (value instanceof Date) return value.toLocaleDateString();
  if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}/.test(value)) {
    const date = new Date(value);
    if (!isNaN(date.getTime())) {
      return `${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
    }
  }
  return String(value);
}

const FONT_SIZE_MAP = { xs: "10px", sm: "11px", md: "12px", lg: "14px" } as const;
const FONT_WEIGHT_MAP = { normal: 400, medium: 500, bold: 700 } as const;

// ===== 셀 렌더러 Props =====

export interface CellRendererProps {
  cell: CardCellDefinitionV2;
  row: RowData;
  inputValue?: number;
  isButtonLoading?: boolean;
  onInputClick?: (e: React.MouseEvent) => void;
  onButtonClick?: (cell: CardCellDefinitionV2, row: RowData) => void;
  onActionButtonClick?: (taskPreset: string, row: RowData, buttonConfig?: Record<string, unknown>) => void;
  onEnterSelectMode?: (whenStatus: string, buttonConfig: Record<string, unknown>) => void;
  packageEntries?: PackageEntry[];
  inputUnit?: string;
  currentUserId?: string;
}

// ===== 메인 디스패치 =====

export function renderCellV2(props: CellRendererProps): React.ReactNode {
  switch (props.cell.type) {
    case "text":
      return <TextCell {...props} />;
    case "field":
      return <FieldCell {...props} />;
    case "image":
      return <ImageCell {...props} />;
    case "badge":
      return <BadgeCell {...props} />;
    case "button":
      return <ButtonCell {...props} />;
    case "number-input":
      return <NumberInputCell {...props} />;
    case "package-summary":
      return <PackageSummaryCell {...props} />;
    case "status-badge":
      return <StatusBadgeCell {...props} />;
    case "timeline":
      return <TimelineCell {...props} />;
    case "action-buttons":
      return <ActionButtonsCell {...props} />;
    case "footer-status":
      return <FooterStatusCell {...props} />;
    case "process-qty-summary":
      return <ProcessQtySummaryCell {...props} />;
    case "mes-process-card":
      return <MesProcessCardCell {...props} />;
    default:
      return <span className="text-[10px] text-muted-foreground">알 수 없는 셀 타입</span>;
  }
}

// ===== 1. text =====

function TextCell({ cell, row }: CellRendererProps) {
  const value = cell.columnName ? row[cell.columnName] : "";
  const fs = FONT_SIZE_MAP[cell.fontSize || "md"];
  const fw = FONT_WEIGHT_MAP[cell.fontWeight || "normal"];

  return (
    <span
      className="truncate"
      style={{ fontSize: fs, fontWeight: fw, color: cell.textColor || undefined }}
    >
      {formatValue(value)}
    </span>
  );
}

// ===== 2. field (라벨+값) =====

function FieldCell({ cell, row, inputValue }: CellRendererProps) {
  const valueType = cell.valueType || "column";
  const fs = FONT_SIZE_MAP[cell.fontSize || "md"];

  const displayValue = useMemo(() => {
    if (valueType !== "formula") {
      const raw = cell.columnName ? row[cell.columnName] : undefined;
      const formatted = formatValue(raw);
      return cell.unit ? `${formatted} ${cell.unit}` : formatted;
    }

    if (cell.formulaLeft && cell.formulaOperator) {
      const rightVal =
        (cell.formulaRightType || "input") === "input"
          ? (inputValue ?? 0)
          : Number(row[cell.formulaRight || ""] ?? 0);
      const leftVal = Number(row[cell.formulaLeft] ?? 0);

      let result: number | null = null;
      switch (cell.formulaOperator) {
        case "+": result = leftVal + rightVal; break;
        case "-": result = leftVal - rightVal; break;
        case "*": result = leftVal * rightVal; break;
        case "/": result = rightVal !== 0 ? leftVal / rightVal : null; break;
      }

      if (result !== null && isFinite(result)) {
        const formatted = (Math.round(result * 100) / 100).toLocaleString();
        return cell.unit ? `${formatted} ${cell.unit}` : formatted;
      }
      return "-";
    }
    return "-";
  }, [valueType, cell, row, inputValue]);

  const isFormula = valueType === "formula";
  const isLabelLeft = cell.labelPosition === "left";

  return (
    <div
      className={isLabelLeft ? "flex items-baseline gap-1" : "flex flex-col"}
      style={{ fontSize: fs }}
    >
      {cell.label && (
        <span className="shrink-0 text-[10px] text-muted-foreground">
          {cell.label}{isLabelLeft ? ":" : ""}
        </span>
      )}
      <span
        className="truncate font-medium"
        style={{ color: cell.textColor || (isFormula ? "#ea580c" : undefined) }}
      >
        {displayValue}
      </span>
    </div>
  );
}

// ===== 3. image =====

function ImageCell({ cell, row }: CellRendererProps) {
  const value = cell.columnName ? row[cell.columnName] : "";
  const imageUrl = value ? String(value) : (cell.defaultImage || DEFAULT_CARD_IMAGE);

  return (
    <div className="flex h-full w-full items-center justify-center overflow-hidden rounded-md border bg-muted/30">
      <img
        src={imageUrl}
        alt={cell.label || ""}
        className="h-full w-full object-contain p-1"
        onError={(e) => {
          const target = e.target as HTMLImageElement;
          if (target.src !== DEFAULT_CARD_IMAGE) target.src = DEFAULT_CARD_IMAGE;
        }}
      />
    </div>
  );
}

// ===== 4. badge =====

function BadgeCell({ cell, row }: CellRendererProps) {
  const value = cell.columnName ? row[cell.columnName] : "";
  return (
    <span className="inline-flex items-center rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-medium text-primary">
      {formatValue(value)}
    </span>
  );
}

// ===== 5. button =====

function ButtonCell({ cell, row, isButtonLoading, onButtonClick }: CellRendererProps) {
  return (
    <Button
      variant={cell.buttonVariant || "outline"}
      size="sm"
      className="h-7 text-[10px]"
      disabled={isButtonLoading}
      onClick={(e) => {
        e.stopPropagation();
        onButtonClick?.(cell, row);
      }}
    >
      {isButtonLoading ? (
        <Loader2 className="mr-1 h-3 w-3 animate-spin" />
      ) : null}
      {cell.label || formatValue(cell.columnName ? row[cell.columnName] : "")}
    </Button>
  );
}

// ===== 6. number-input =====

function NumberInputCell({ cell, row, inputValue, onInputClick }: CellRendererProps) {
  const unit = cell.inputUnit || "EA";
  return (
    <button
      type="button"
      onClick={(e) => {
        e.stopPropagation();
        onInputClick?.(e);
      }}
      className="w-full rounded-lg border-2 border-input bg-background px-2 py-1.5 text-center hover:border-primary active:bg-muted"
    >
      <span className="block text-lg font-bold leading-tight">
        {(inputValue ?? 0).toLocaleString()}
      </span>
      <span className="block text-[12px] text-muted-foreground">{unit}</span>
    </button>
  );
}

// ===== 7. package-summary =====

function PackageSummaryCell({ cell, packageEntries, inputUnit }: CellRendererProps) {
  if (!packageEntries || packageEntries.length === 0) return null;

  return (
    <div className="w-full border-t bg-emerald-50">
      {packageEntries.map((entry, idx) => (
        <div key={idx} className="flex items-center justify-between px-3 py-1.5">
          <div className="flex items-center gap-2">
            <span className="rounded-full bg-emerald-500 px-2 py-0.5 text-[10px] font-bold text-white">
              포장완료
            </span>
            <Package className="h-4 w-4 text-emerald-600" />
            <span className="text-xs font-medium text-emerald-700">
              {entry.packageCount}{entry.unitLabel} x {entry.quantityPerUnit}
            </span>
          </div>
          <span className="text-xs font-bold text-emerald-700">
            = {entry.totalQuantity.toLocaleString()}{inputUnit || "EA"}
          </span>
        </div>
      ))}
    </div>
  );
}

// ===== 9. status-badge =====

const STATUS_COLORS: Record<string, { bg: string; text: string }> = {
  waiting: { bg: "#94a3b820", text: "#64748b" },
  accepted: { bg: "#3b82f620", text: "#2563eb" },
  in_progress: { bg: "#f59e0b20", text: "#d97706" },
  completed: { bg: "#10b98120", text: "#059669" },
};

function StatusBadgeCell({ cell, row }: CellRendererProps) {
  const hasSubStatus = row[VIRTUAL_SUB_STATUS] !== undefined;
  const effectiveValue = hasSubStatus
    ? row[VIRTUAL_SUB_STATUS]
    : (cell.statusColumn ? row[cell.statusColumn] : (cell.columnName ? row[cell.columnName] : ""));
  const strValue = String(effectiveValue || "");
  const mapped = cell.statusMap?.find((m) => m.value === strValue);

  if (mapped) {
    return (
      <span
        className="inline-flex items-center rounded-full px-2.5 py-0.5 text-[10px] font-semibold"
        style={{ backgroundColor: `${mapped.color}20`, color: mapped.color }}
      >
        {mapped.label}
      </span>
    );
  }

  // in_progress + 접수분 전부 생산 완료 → "접수분완료" 표시
  const displayValue = strValue;

  const defaultColors = STATUS_COLORS[displayValue];
  if (defaultColors) {
    const labelMap: Record<string, string> = {
      waiting: "대기", accepted: "접수", in_progress: "진행중",
      completed: "완료",
    };
    return (
      <span
        className="inline-flex items-center rounded-full px-2.5 py-0.5 text-[10px] font-semibold"
        style={{ backgroundColor: defaultColors.bg, color: defaultColors.text }}
      >
        {labelMap[displayValue] || displayValue}
      </span>
    );
  }

  return (
    <span className="inline-flex items-center rounded-full bg-muted px-2.5 py-0.5 text-[10px] font-medium text-muted-foreground">
      {formatValue(effectiveValue)}
    </span>
  );
}

// ===== 10. timeline =====

type TimelineStyle = { chipBg: string; chipText: string; icon: React.ReactNode };

const TIMELINE_SEMANTIC_STYLES: Record<string, TimelineStyle> = {
  done: { chipBg: "#10b981", chipText: "#ffffff", icon: <CheckCircle2 className="h-2.5 w-2.5" /> },
  active: { chipBg: "#3b82f6", chipText: "#ffffff", icon: <CircleDot className="h-2.5 w-2.5 animate-pulse" /> },
  pending: { chipBg: "#e2e8f0", chipText: "#64748b", icon: <Clock className="h-2.5 w-2.5" /> },
};

// 레거시 status 값 → semantic 매핑 (기존 데이터 호환)
const LEGACY_STATUS_TO_SEMANTIC: Record<string, string> = {
  completed: "done", in_progress: "active", accepted: "active", waiting: "pending",
};

function getTimelineStyle(step: TimelineProcessStep): TimelineStyle {
  if (step.semantic) return TIMELINE_SEMANTIC_STYLES[step.semantic] || TIMELINE_SEMANTIC_STYLES.pending;
  const fallback = LEGACY_STATUS_TO_SEMANTIC[step.status];
  return TIMELINE_SEMANTIC_STYLES[fallback || "pending"];
}

function TimelineCell({ cell, row }: CellRendererProps) {
  const processFlow = row.__processFlow__ as TimelineProcessStep[] | undefined;

  if (!processFlow || processFlow.length === 0) {
    const fallback = cell.processColumn ? row[cell.processColumn] : "";
    return (
      <span className="text-[10px] text-muted-foreground">
        {formatValue(fallback)}
      </span>
    );
  }

  const maxVisible = cell.visibleCount || 5;
  const currentIdx = processFlow.findIndex((s) => s.isCurrent);

  type DisplayItem =
    | { kind: "step"; step: TimelineProcessStep }
    | { kind: "count"; count: number; side: "before" | "after" };

  // 현재 항목 기준으로 앞뒤 배분하여 축약
  const displayItems = useMemo((): DisplayItem[] => {
    if (processFlow.length <= maxVisible) {
      return processFlow.map((s) => ({ kind: "step" as const, step: s }));
    }

    const effectiveIdx = Math.max(0, currentIdx);
    const priority = cell.timelinePriority || "before";
    // 숫자칩 2개를 제외한 나머지를 앞뒤로 배분 (priority에 따라 여분 슬롯 방향 결정)
    const slotForSteps = maxVisible - 2;
    const half = Math.floor(slotForSteps / 2);
    const extra = slotForSteps - half - 1;
    const beforeSlots = priority === "before" ? Math.max(half, extra) : Math.min(half, extra);
    const afterSlots = slotForSteps - beforeSlots - 1;

    let startIdx = effectiveIdx - beforeSlots;
    let endIdx = effectiveIdx + afterSlots;

    // 경계 보정
    if (startIdx < 0) {
      endIdx = Math.min(processFlow.length - 1, endIdx + Math.abs(startIdx));
      startIdx = 0;
    }
    if (endIdx >= processFlow.length) {
      startIdx = Math.max(0, startIdx - (endIdx - processFlow.length + 1));
      endIdx = processFlow.length - 1;
    }

    const items: DisplayItem[] = [];
    const beforeCount = startIdx;
    const afterCount = processFlow.length - 1 - endIdx;

    if (beforeCount > 0) {
      items.push({ kind: "count", count: beforeCount, side: "before" });
    }
    for (let i = startIdx; i <= endIdx; i++) {
      items.push({ kind: "step", step: processFlow[i] });
    }
    if (afterCount > 0) {
      items.push({ kind: "count", count: afterCount, side: "after" });
    }

    return items;
  }, [processFlow, maxVisible, currentIdx]);

  const [modalOpen, setModalOpen] = useState(false);

  const completedCount = processFlow.filter((s) => (s.semantic || LEGACY_STATUS_TO_SEMANTIC[s.status]) === "done").length;
  const totalCount = processFlow.length;

  return (
    <>
      <div
        className={cn(
          "flex w-full items-center gap-0.5 overflow-hidden px-0.5",
          cell.showDetailModal !== false && "cursor-pointer",
          cell.align === "center" ? "justify-center" : cell.align === "right" ? "justify-end" : "justify-start",
        )}
        onClick={cell.showDetailModal !== false ? (e) => { e.stopPropagation(); setModalOpen(true); } : undefined}
        title={cell.showDetailModal !== false ? "클릭하여 전체 현황 보기" : undefined}
      >
        {displayItems.map((item, idx) => {
          const isLast = idx === displayItems.length - 1;

          if (item.kind === "count") {
            return (
              <React.Fragment key={`cnt-${item.side}`}>
                <div
                  className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-muted text-[8px] font-bold text-muted-foreground"
                  title={item.side === "before" ? `이전 ${item.count}개` : `이후 ${item.count}개`}
                >
                  {item.count}
                </div>
                {!isLast && <div className="h-px w-1.5 shrink-0 border-t border-dashed border-muted-foreground/40" />}
              </React.Fragment>
            );
          }

          const styles = getTimelineStyle(item.step);

          return (
            <React.Fragment key={item.step.seqNo}>
              <div
                className="flex shrink-0 items-center gap-0.5 rounded-full px-1.5 py-0.5"
                style={{
                  backgroundColor: styles.chipBg,
                  color: styles.chipText,
                  outline: item.step.isCurrent ? "2px solid #2563eb" : "none",
                  outlineOffset: "1px",
                }}
                title={`${item.step.seqNo}. ${item.step.processName} (${item.step.status})`}
              >
                {styles.icon}
                <span className="max-w-[48px] truncate text-[9px] font-medium leading-tight">
                  {item.step.processName}
                </span>
              </div>
              {!isLast && <div className="h-px w-1.5 shrink-0 border-t border-dashed border-muted-foreground/40" />}
            </React.Fragment>
          );
        })}
      </div>

      {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
      <div onClick={(e) => e.stopPropagation()} onPointerDown={(e) => e.stopPropagation()}>
      <Dialog open={modalOpen} onOpenChange={setModalOpen}>
        <DialogContent className="max-w-[95vw] sm:max-w-[500px]">
          <DialogHeader>
            <DialogTitle className="text-base sm:text-lg">전체 현황</DialogTitle>
            <DialogDescription className="text-xs sm:text-sm">
              총 {totalCount}개 중 {completedCount}개 완료
            </DialogDescription>
          </DialogHeader>

          <div className="space-y-0">
            {processFlow.map((step, idx) => {
              const styles = getTimelineStyle(step);
              const sem = step.semantic || LEGACY_STATUS_TO_SEMANTIC[step.status] || "pending";
              const statusLabel = sem === "done" ? "완료" : sem === "active" ? "진행" : "대기";

              return (
                <div key={step.seqNo} className="flex items-center">
                  {/* 세로 연결선 + 아이콘 */}
                  <div className="flex w-8 shrink-0 flex-col items-center">
                    {idx > 0 && <div className="h-3 w-px bg-border" />}
                    <div
                      className="flex h-6 w-6 items-center justify-center rounded-full"
                      style={{ backgroundColor: styles.chipBg, color: styles.chipText }}
                    >
                      {styles.icon}
                    </div>
                    {idx < processFlow.length - 1 && <div className="h-3 w-px bg-border" />}
                  </div>

                  {/* 항목 정보 */}
                  <div className={cn(
                    "ml-2 flex flex-1 items-center justify-between rounded-md px-3 py-2",
                    step.isCurrent && "bg-primary/5 ring-1 ring-primary/30",
                  )}>
                    <div className="flex items-center gap-2">
                      <span className="text-xs text-muted-foreground">{step.seqNo}</span>
                      <span className={cn(
                        "text-sm",
                        step.isCurrent ? "font-semibold" : "font-medium",
                      )}>
                        {step.processName}
                      </span>
                      {step.isCurrent && (
                        <Star className="h-3 w-3 fill-primary text-primary" />
                      )}
                    </div>
                    <span
                      className="rounded-full px-2 py-0.5 text-[10px] font-medium"
                      style={{ backgroundColor: styles.chipBg, color: styles.chipText }}
                    >
                      {statusLabel}
                    </span>
                  </div>
                </div>
              );
            })}
          </div>

          {/* 하단 진행률 바 */}
          <div className="space-y-1 pt-2">
            <div className="flex justify-between text-xs text-muted-foreground">
              <span>진행률</span>
              <span>{totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0}%</span>
            </div>
            <div className="h-2 w-full overflow-hidden rounded-full bg-secondary">
              <div
                className="h-full rounded-full bg-primary transition-all duration-300"
                style={{ width: `${totalCount > 0 ? (completedCount / totalCount) * 100 : 0}%` }}
              />
            </div>
          </div>
        </DialogContent>
      </Dialog>
      </div>
    </>
  );
}

// ===== 11. action-buttons =====

function evaluateShowCondition(btn: ActionButtonDef, row: RowData, currentUserId?: string): "visible" | "disabled" | "hidden" {
  const cond = btn.showCondition;
  if (!cond || cond.type === "always") return "visible";

  let matched = false;

  if (cond.type === "timeline-status") {
    const subStatus = row[VIRTUAL_SUB_STATUS];
    if (Array.isArray(cond.value)) {
      matched = subStatus !== undefined && cond.value.includes(String(subStatus));
    } else {
      matched = subStatus !== undefined && String(subStatus) === cond.value;
    }
  } else if (cond.type === "column-value" && cond.column) {
    matched = String(row[cond.column] ?? "") === (cond.value ?? "");
  } else if (cond.type === "owner-match" && cond.column) {
    const ownerValue = String(row[cond.column] ?? "");
    matched = !!currentUserId && ownerValue === currentUserId;
  } else {
    return "visible";
  }

  if (matched) return "visible";
  return cond.unmatchBehavior === "disabled" ? "disabled" : "hidden";
}

function ActionButtonsCell({ cell, row, onActionButtonClick, onEnterSelectMode, currentUserId }: CellRendererProps) {
  const processFlow = row.__processFlow__ as { isCurrent: boolean; processId?: string | number }[] | undefined;
  const currentProcess = processFlow?.find((s) => s.isCurrent);
  const currentProcessId = (row.__splitProcessId ?? row.__process_id ?? currentProcess?.processId) as string | number | undefined;

  if (cell.actionButtons && cell.actionButtons.length > 0) {
    const evaluated = cell.actionButtons.map((btn) => {
      let state = evaluateShowCondition(btn, row, currentUserId);
      // 접수가능 조건 버튼이 원본 카드의 in_progress에서 보이지 않도록 차단
      // (접수는 접수가능 탭의 클론 카드에서만 가능)
      if (state === "visible" && !row.__isAcceptClone) {
        const cond = btn.showCondition;
        if (cond?.type === "timeline-status") {
          const condValues = Array.isArray(cond.value) ? cond.value : [cond.value];
          const currentSubStatus = String(row[VIRTUAL_SUB_STATUS] ?? "");
          if (condValues.includes("acceptable") && currentSubStatus === "in_progress") {
            state = "hidden";
          }
        }
      }
      return { btn, state };
    });

    const activeBtn = evaluated.find((e) => e.state === "visible");
    const disabledBtn = activeBtn ? null : evaluated.find((e) => e.state === "disabled");
    const pick = activeBtn || disabledBtn;
    if (!pick) return null;

    const { btn, state } = pick;

    // in_progress 상태 + 미소진 접수분 존재 시 접수취소 버튼 추가
    const subStatus = row[VIRTUAL_SUB_STATUS];
    const effectiveStatus = subStatus !== undefined ? String(subStatus) : "";
    const rowInputQty = parseInt(String(row.__process_input_qty ?? row.input_qty ?? "0"), 10) || 0;
    const totalProduced = parseInt(String(row.__process_total_production_qty ?? row.total_production_qty ?? "0"), 10) || 0;
    const hasUnproduced = rowInputQty > totalProduced;
    const showCancelBtn = effectiveStatus === "in_progress" && hasUnproduced && currentProcessId;

    return (
      <div className="flex items-center gap-1">
        <Button
          variant={btn.variant || "outline"}
          size="sm"
          disabled={state === "disabled"}
          className="h-7 text-[10px]"
          onClick={(e) => {
            e.stopPropagation();
            const actions = (btn.clickActions && btn.clickActions.length > 0) ? btn.clickActions : [btn.clickAction];
            const firstAction = actions[0];
            if (!firstAction) return;

            const config: Record<string, unknown> = {
              ...firstAction,
              __allActions: actions,
              selectModeConfig: firstAction.selectModeButtons
                ? { filterStatus: btn.showCondition?.value || "", buttons: firstAction.selectModeButtons }
                : undefined,
            };
            if (currentProcessId !== undefined) config.__processId = currentProcessId;

            if (firstAction.type === "select-mode" && onEnterSelectMode) {
              onEnterSelectMode(btn.showCondition?.value || "", config);
              return;
            }

            onActionButtonClick?.(btn.label, row, config);
          }}
        >
          {btn.label}
        </Button>
        {showCancelBtn && (
          <Button
            variant="ghost"
            size="sm"
            className="h-7 text-[10px] text-destructive hover:text-destructive"
            onClick={(e) => {
              e.stopPropagation();
              onActionButtonClick?.("__cancelAccept", row, {
                __processId: currentProcessId,
                type: "cancel-accept",
              });
            }}
          >
            취소
          </Button>
        )}
      </div>
    );
  }

  // 기존 구조 (actionRules) 폴백
  const hasSubStatus = row[VIRTUAL_SUB_STATUS] !== undefined;
  const statusValue = hasSubStatus
    ? String(row[VIRTUAL_SUB_STATUS] || "")
    : (cell.statusColumn ? String(row[cell.statusColumn] || "") : (cell.columnName ? String(row[cell.columnName] || "") : ""));
  const rules = cell.actionRules || [];
  const matchedRule = rules.find((r) => r.whenStatus === statusValue);
  if (!matchedRule) return null;

  return (
    <div className="flex items-center gap-1">
      {matchedRule.buttons.map((btn, idx) => (
        <Button
          key={idx}
          variant={btn.variant || "outline"}
          size="sm"
          className="h-7 text-[10px]"
          onClick={(e) => {
            e.stopPropagation();
            const config = { ...(btn as Record<string, unknown>) };
            if (currentProcessId !== undefined) config.__processId = currentProcessId;
            if (btn.clickMode === "select-mode" && onEnterSelectMode) {
              onEnterSelectMode(matchedRule.whenStatus, config);
              return;
            }
            onActionButtonClick?.(btn.taskPreset, row, config);
          }}
        >
          {btn.label}
        </Button>
      ))}
    </div>
  );
}

// ===== 12. process-qty-summary =====

function ProcessQtySummaryCell({ cell, row }: CellRendererProps) {
  const processFlow = row.__processFlow__ as TimelineProcessStep[] | undefined;
  const status = String(row[VIRTUAL_SUB_STATUS] ?? row.status ?? "");
  const isClone = !!row.__isAcceptClone;

  const instructionQty = parseInt(String(row.instruction_qty ?? row.qty ?? "0"), 10) || 0;
  const inputQty = parseInt(String(row.__process_input_qty ?? row.input_qty ?? "0"), 10) || 0;
  const totalProd = parseInt(String(row.__process_total_production_qty ?? row.total_production_qty ?? "0"), 10) || 0;
  const goodQty = parseInt(String(row.__process_good_qty ?? row.good_qty ?? "0"), 10) || 0;
  const defectQty = parseInt(String(row.__process_defect_qty ?? row.defect_qty ?? "0"), 10) || 0;
  const availableQty = parseInt(String(row.__availableQty ?? "0"), 10) || 0;
  const prevGoodQty = parseInt(String(row.__prevGoodQty ?? String(instructionQty)), 10) || 0;

  const currentStep = processFlow?.find((s) => s.isCurrent);
  const currentIdx = processFlow?.findIndex((s) => s.isCurrent) ?? -1;
  const isFirstProcess = currentIdx === 0;
  const totalSteps = processFlow?.length ?? 0;

  const remainingQty = Math.max(0, inputQty - totalProd);
  const progressPct = inputQty > 0 ? Math.min(100, Math.round((totalProd / inputQty) * 100)) : 0;
  const yieldRate = totalProd > 0 ? Math.round((goodQty / totalProd) * 100) : 0;

  // 접수가능 탭 (클론 카드) - 접수 가능 수량 중심
  if (isClone || status === "acceptable" || status === "waiting") {
    const showQty = isClone ? availableQty : (status === "acceptable" ? availableQty || prevGoodQty : 0);
    return (
      <div className="flex w-full flex-col gap-1 px-1">
        {/* 미니 공정 흐름 바 */}
        {processFlow && processFlow.length > 1 && (
          <MiniProcessBar steps={processFlow} currentIdx={currentIdx} />
        )}
        {/* 핵심 수량 */}
        <div className="flex items-center justify-between gap-1">
          <div className="flex items-baseline gap-1">
            <span className="text-[10px] text-muted-foreground">지시</span>
            <span className="text-xs font-semibold">{instructionQty.toLocaleString()}</span>
          </div>
          {!isFirstProcess && (
            <div className="flex items-baseline gap-1">
              <span className="text-[10px] text-muted-foreground">전공정양품</span>
              <span className="text-xs font-medium text-emerald-600">{prevGoodQty.toLocaleString()}</span>
            </div>
          )}
          <div className="flex items-baseline gap-1">
            <span className="text-[10px] text-muted-foreground">접수가능</span>
            <span className="text-xs font-bold text-primary">{(showQty || prevGoodQty).toLocaleString()}</span>
          </div>
        </div>
      </div>
    );
  }

  // 진행중 / 접수분완료 - 작업 현황 중심
  if (status === "in_progress") {
    const isBatchDone = inputQty > 0 && totalProd >= inputQty;
    return (
      <div className="flex w-full flex-col gap-1 px-1">
        {/* 미니 공정 흐름 바 */}
        {processFlow && processFlow.length > 1 && (
          <MiniProcessBar steps={processFlow} currentIdx={currentIdx} />
        )}
        {/* 프로그레스 바 */}
        <div className="flex items-center gap-1.5">
          <div className="h-1.5 flex-1 overflow-hidden rounded-full bg-secondary">
            <div
              className={cn(
                "h-full rounded-full transition-all duration-300",
                isBatchDone ? "bg-violet-500" : "bg-primary",
              )}
              style={{ width: `${progressPct}%` }}
            />
          </div>
        </div>
        {/* 수량 상세 */}
        <div className="flex items-center justify-between gap-0.5">
          <QtyChip label="접수" value={inputQty} color="#3b82f6" />
          <QtyChip label="양품" value={goodQty} color="#10b981" />
          <QtyChip label="불량" value={defectQty} color="#ef4444" showZero={false} />
          <QtyChip label="잔여" value={remainingQty} color={remainingQty > 0 ? "#f59e0b" : "#10b981"} />
        </div>
        {/* 추가접수가능 수량 (있을 때만) */}
        {availableQty > 0 && (
          <div className="flex items-center justify-end gap-1">
            <span className="text-[9px] text-muted-foreground">추가접수가능</span>
            <span className="text-[10px] font-semibold text-violet-600">{availableQty.toLocaleString()}</span>
          </div>
        )}
      </div>
    );
  }

  // 완료 상태 - 최종 결과 요약
  if (status === "completed") {
    return (
      <div className="flex w-full flex-col gap-1 px-1">
        {/* 미니 공정 흐름 바 */}
        {processFlow && processFlow.length > 1 && (
          <MiniProcessBar steps={processFlow} currentIdx={currentIdx} />
        )}
        {/* 완료 프로그레스 */}
        <div className="flex items-center gap-1.5">
          <div className="h-1.5 flex-1 overflow-hidden rounded-full bg-secondary">
            <div className="h-full rounded-full bg-emerald-500" style={{ width: "100%" }} />
          </div>
          <span className="text-[10px] font-bold text-emerald-600">완료</span>
        </div>
        {/* 최종 수량 */}
        <div className="flex items-center justify-between gap-0.5">
          <QtyChip label="총생산" value={totalProd} color="#059669" />
          <QtyChip label="양품" value={goodQty} color="#10b981" />
          <QtyChip label="불량" value={defectQty} color="#ef4444" showZero={false} />
          {totalProd > 0 && (
            <div className="flex items-baseline gap-0.5">
              <span className="text-[9px] text-muted-foreground">수율</span>
              <span className="text-[10px] font-bold text-emerald-600">{yieldRate}%</span>
            </div>
          )}
        </div>
      </div>
    );
  }

  // fallback
  return (
    <div className="flex w-full items-center justify-between px-1">
      <span className="text-[10px] text-muted-foreground">지시수량</span>
      <span className="text-xs font-semibold">{instructionQty.toLocaleString()}</span>
    </div>
  );
}

// --- 미니 공정 흐름 바 ---
function MiniProcessBar({ steps, currentIdx }: { steps: TimelineProcessStep[]; currentIdx: number }) {
  return (
    <div className="flex items-center gap-px">
      {steps.map((step, idx) => {
        const sem = step.semantic || LEGACY_STATUS_TO_SEMANTIC[step.status] || "pending";
        const isCurrent = idx === currentIdx;
        let bg = "#e2e8f0"; // pending
        if (sem === "done") bg = "#10b981";
        else if (sem === "active") bg = "#3b82f6";

        const pct = step.totalProductionQty && step.inputQty && step.inputQty > 0
          ? Math.round((step.totalProductionQty / step.inputQty) * 100)
          : undefined;

        return (
          <div
            key={step.seqNo}
            className={cn(
              "relative flex-1 overflow-hidden rounded-sm",
              isCurrent ? "h-2.5" : "h-1.5",
            )}
            style={{ backgroundColor: `${bg}30` }}
            title={`${step.processName}: ${step.status}${pct !== undefined ? ` (${pct}%)` : ""}`}
          >
            <div
              className="absolute inset-y-0 left-0 rounded-sm transition-all duration-300"
              style={{
                backgroundColor: bg,
                width: sem === "done" ? "100%" : pct !== undefined ? `${pct}%` : "0%",
              }}
            />
          </div>
        );
      })}
    </div>
  );
}

// --- 수량 칩 ---
function QtyChip({
  label, value, color, showZero = true,
}: {
  label: string; value: number; color: string; showZero?: boolean;
}) {
  if (!showZero && value === 0) return null;
  return (
    <div className="flex items-baseline gap-0.5">
      <span className="text-[9px] text-muted-foreground">{label}</span>
      <span
        className="text-[10px] font-semibold tabular-nums"
        style={{ color }}
      >
        {value.toLocaleString()}
      </span>
    </div>
  );
}

// ===== 13. footer-status =====

function FooterStatusCell({ cell, row }: CellRendererProps) {
  const value = cell.footerStatusColumn ? row[cell.footerStatusColumn] : "";
  const strValue = String(value || "");
  const mapped = cell.footerStatusMap?.find((m) => m.value === strValue);

  if (!strValue && !cell.footerLabel) return null;

  return (
    <div
      className="flex w-full items-center justify-between px-2 py-1"
      style={{ borderTop: cell.showTopBorder !== false ? "1px solid hsl(var(--border))" : "none" }}
    >
      {cell.footerLabel && (
        <span className="text-[10px] text-muted-foreground">{cell.footerLabel}</span>
      )}
      {mapped ? (
        <span
          className="inline-flex items-center rounded-full px-2 py-0.5 text-[9px] font-semibold"
          style={{ backgroundColor: `${mapped.color}20`, color: mapped.color }}
        >
          {mapped.label}
        </span>
      ) : strValue ? (
        <span className="text-[10px] font-medium text-muted-foreground">
          {strValue}
        </span>
      ) : null}
    </div>
  );
}

// ===== 14. mes-process-card (MES 공정 전용 카드) =====

const MES_STATUS: Record<string, { label: string; color: string; bg: string }> = {
  waiting:     { label: "대기",       color: "#94a3b8", bg: "#f8fafc" },
  acceptable:  { label: "접수가능",   color: "#2563eb", bg: "#eff6ff" },
  in_progress: { label: "진행중",     color: "#d97706", bg: "#fffbeb" },
  completed:   { label: "완료",       color: "#059669", bg: "#ecfdf5" },
};

function MesProcessCardCell({ cell, row, onActionButtonClick, currentUserId }: CellRendererProps) {
  const processFlow = row.__processFlow__ as TimelineProcessStep[] | undefined;
  const rawStatus = String(row[VIRTUAL_SUB_STATUS] ?? row.status ?? "");
  const isClone = !!row.__isAcceptClone;
  const [flowModalOpen, setFlowModalOpen] = useState(false);

  const instrQty = parseInt(String(row.qty ?? row.instruction_qty ?? "0"), 10) || 0;
  const inputQty = parseInt(String(row.__process_input_qty ?? row.input_qty ?? "0"), 10) || 0;
  const totalProd = parseInt(String(row.__process_total_production_qty ?? row.total_production_qty ?? "0"), 10) || 0;
  const goodQty = parseInt(String(row.__process_good_qty ?? row.good_qty ?? "0"), 10) || 0;
  const defectQty = parseInt(String(row.__process_defect_qty ?? row.defect_qty ?? "0"), 10) || 0;
  const concessionQty = parseInt(String(row.__process_concession_qty ?? row.concession_qty ?? "0"), 10) || 0;
  const isRework = String(row.__process_is_rework ?? row.is_rework ?? "N") === "Y";
  const availableQty = parseInt(String(row.__availableQty ?? "0"), 10) || 0;
  const prevGoodQty = parseInt(String(row.__prevGoodQty ?? String(instrQty)), 10) || 0;
  const resultStatus = String(row.__process_result_status ?? "");

  const currentStep = processFlow?.find((s) => s.isCurrent);
  const currentIdx = processFlow?.findIndex((s) => s.isCurrent) ?? -1;
  const isFirstProcess = currentIdx === 0;
  const processId = currentStep?.processId;

  const remainingQty = Math.max(0, inputQty - totalProd);
  const progressPct = inputQty > 0 ? Math.min(100, Math.round((totalProd / inputQty) * 100)) : 0;
  const yieldRate = totalProd > 0 ? Math.round((goodQty / totalProd) * 100) : 0;

  const displayStatus = rawStatus;
  const st = MES_STATUS[displayStatus] || MES_STATUS.waiting;

  const processName = currentStep?.processName || String(row.__process_process_name ?? "");
  const woNo = String(row.work_instruction_no ?? "");
  const itemId = String(row.item_id ?? "");
  const itemName = String(row.item_name ?? "");

  // MES 워크플로우 상태 기반 버튼 결정
  const acceptBtn = (cell.actionButtons || []).find((b) => b.showCondition?.type === "timeline-status");
  const cancelBtn = (cell.actionButtons || []).find((b) => b.showCondition?.type === "owner-match");

  let activeBtn: ActionButtonDef | undefined;
  let showManualComplete = false;
  const isFullyProduced = inputQty > 0 && totalProd >= inputQty;
  if (isClone) {
    activeBtn = acceptBtn;
  } else if (rawStatus === "acceptable") {
    activeBtn = acceptBtn;
  } else if (rawStatus === "in_progress") {
    if (isFullyProduced) {
      if (availableQty > 0) activeBtn = acceptBtn;
    } else if (totalProd > 0) {
      showManualComplete = true;
    } else {
      activeBtn = cancelBtn;
    }
  }
  return (
    <>
      <div
        className="flex h-full w-full flex-col overflow-hidden"
        style={{ borderLeft: `4px solid ${st.color}`, backgroundColor: st.bg }}
      >
        {/* ── 헤더 ── */}
        <div className="flex items-start justify-between px-5 pt-5 pb-1">
          <div className="min-w-0 flex-1">
            <div className="flex items-center gap-2 flex-wrap">
              <span className="text-[14px] font-medium text-muted-foreground">{woNo}</span>
              {processName && (
                <span className="text-[14px] font-semibold" style={{ color: st.color }}>
                  {processName}
                  {processFlow && processFlow.length > 1 && ` (${currentIdx + 1}/${processFlow.length})`}
                </span>
              )}
            </div>
            <div className="mt-1">
              <span className="text-[20px] font-bold leading-tight">{itemName || itemId || "-"}</span>
            </div>
          </div>
          <div className="ml-3 flex shrink-0 items-center gap-2">
            {isRework && (
              <span className="rounded-md bg-amber-500 px-2.5 py-1 text-[12px] font-bold text-white">
                재작업
              </span>
            )}
            <span
              className="rounded-full px-3.5 py-1.5 text-[14px] font-bold"
              style={{ backgroundColor: st.color, color: "#fff" }}
            >
              {st.label}
            </span>
          </div>
        </div>

        {/* ── 수량 메트릭 (상태별) ── */}
        <div className="flex-1 px-5 py-3">
          {(isClone || rawStatus === "acceptable" || rawStatus === "waiting") && (
            <MesAcceptableMetrics
              instrQty={instrQty}
              prevGoodQty={prevGoodQty}
              availableQty={availableQty}
              inputQty={inputQty}
              isFirstProcess={isFirstProcess}
              isClone={isClone}
              isRework={isRework}
            />
          )}
          {rawStatus === "in_progress" && (
            <MesInProgressMetrics
              inputQty={inputQty}
              totalProd={totalProd}
              goodQty={goodQty}
              defectQty={defectQty}
              concessionQty={concessionQty}
              remainingQty={remainingQty}
              progressPct={progressPct}
              availableQty={availableQty}
              isBatchDone={false}
              statusColor={st.color}
            />
          )}
          {rawStatus === "completed" && (
            <MesCompletedMetrics
              instrQty={instrQty}
              goodQty={goodQty}
              defectQty={defectQty}
              concessionQty={concessionQty}
              yieldRate={yieldRate}
            />
          )}
        </div>

        {/* ── 공정 흐름 스트립 (클릭 시 모달) ── */}
        {processFlow && processFlow.length > 0 && (
          <div
            className="cursor-pointer border-t px-5 py-3 transition-colors hover:bg-black/3"
            style={{ borderColor: `${st.color}20` }}
            onClick={(e) => { e.stopPropagation(); setFlowModalOpen(true); }}
            title="클릭하여 공정 상세 보기"
          >
            <ProcessFlowStrip steps={processFlow} currentIdx={currentIdx} instrQty={instrQty} />
          </div>
        )}

        {/* ── 부가정보 ── */}
        {(row.end_date || row.equipment_id || row.work_team) && (
          <div
            className="border-t px-5 py-2"
            style={{ borderColor: `${st.color}20` }}
          >
            <div className="flex items-center gap-4 text-[14px] text-muted-foreground">
              {row.end_date && <span>납기 <b className="text-foreground">{formatValue(row.end_date)}</b></span>}
              {row.equipment_id && <span>{String(row.equipment_id)}</span>}
              {row.work_team && <span>{String(row.work_team)}</span>}
            </div>
          </div>
        )}

        {/* ── 액션 버튼 ── */}
        {(activeBtn || showManualComplete) && (
          <div
            className="mt-auto border-t px-5 py-3"
            style={{ borderColor: `${st.color}20` }}
          >
            <div className="flex gap-3">
              {activeBtn && (
                <Button
                  variant={activeBtn.variant || "default"}
                  className="h-14 flex-1 rounded-xl text-[17px] font-bold"
                  onClick={(e) => {
                    e.stopPropagation();
                    const actions = activeBtn.clickActions?.length ? activeBtn.clickActions : [activeBtn.clickAction];
                    const firstAction = actions[0];
                    const config: Record<string, unknown> = { ...firstAction, __allActions: actions };
                    if (processId !== undefined) config.__processId = processId;
                    onActionButtonClick?.(activeBtn.label, row, config);
                  }}
                >
                  {activeBtn.label}
                </Button>
              )}
              {showManualComplete && (
                <Button
                  variant="outline"
                  className="h-14 flex-1 rounded-xl text-[17px] font-bold"
                  onClick={(e) => {
                    e.stopPropagation();
                    onActionButtonClick?.("__manualComplete", row, { __processId: processId });
                  }}
                >
                  수동 완료
                </Button>
              )}
            </div>
          </div>
        )}
      </div>

      {/* ── 공정 상세 모달 ── */}
      <Dialog open={flowModalOpen} onOpenChange={setFlowModalOpen}>
        <DialogContent className="max-w-[95vw] sm:max-w-[560px]">
          <DialogHeader>
            <DialogTitle className="text-base">{woNo} 공정 현황</DialogTitle>
            <DialogDescription className="text-xs">
              {processFlow?.length ?? 0}개 공정 중 {processFlow?.filter(s => (s.semantic || LEGACY_STATUS_TO_SEMANTIC[s.status]) === "done").length ?? 0}개 완료
            </DialogDescription>
          </DialogHeader>
          <div className="space-y-0">
            {processFlow?.map((step, idx) => {
              const sem = step.semantic || LEGACY_STATUS_TO_SEMANTIC[step.status] || "pending";
              const styles = getTimelineStyle(step);
              const sInstr = instrQty;
              const sInput = step.inputQty || 0;
              const sProd = step.totalProductionQty || 0;
              const sGood = step.goodQty || 0;
              const sDefect = step.defectQty || 0;
              const sYield = step.yieldRate || 0;
              const sPct = sInput > 0 ? Math.round((sProd / sInput) * 100) : (sem === "done" ? 100 : 0);
              const statusLabel = sem === "done" ? "완료" : sem === "active" ? "진행" : "대기";

              return (
                <div key={step.seqNo} className="flex items-center">
                  <div className="flex w-8 shrink-0 flex-col items-center">
                    {idx > 0 && <div className="h-3 w-px bg-border" />}
                    <div
                      className="flex h-6 w-6 items-center justify-center rounded-full text-[10px] font-bold"
                      style={{ backgroundColor: styles.chipBg, color: styles.chipText }}
                    >
                      {step.seqNo}
                    </div>
                    {idx < (processFlow?.length ?? 0) - 1 && <div className="h-3 w-px bg-border" />}
                  </div>
                  <div className={cn(
                    "ml-2 flex flex-1 items-center justify-between rounded-md px-3 py-2",
                    step.isCurrent && "ring-1 ring-primary/30 bg-primary/5",
                  )}>
                    <div className="min-w-0">
                      <div className="flex items-center gap-1.5">
                        <span className={cn("text-sm", step.isCurrent ? "font-bold" : "font-medium")}>
                          {step.processName}
                        </span>
                        <span className="rounded px-1.5 py-0.5 text-[9px] font-medium"
                          style={{ backgroundColor: `${styles.chipBg}30`, color: styles.chipBg }}>
                          {statusLabel}
                        </span>
                      </div>
                      {(sInput > 0 || sem === "done") && (
                        <div className="mt-1 flex items-center gap-3 text-[10px] text-muted-foreground">
                          <span>양품 <b className="text-foreground">{sGood.toLocaleString()}</b></span>
                          {sDefect > 0 && <span>불량 <b className="text-destructive">{sDefect.toLocaleString()}</b></span>}
                          <span>수율 <b style={{ color: sYield >= 95 ? "#059669" : sYield >= 80 ? "#d97706" : "#ef4444" }}>{sYield}%</b></span>
                        </div>
                      )}
                    </div>
                    <div className="ml-3 flex w-16 flex-col items-end">
                      <span className="text-[11px] font-bold tabular-nums">{sProd}/{sInput || sInstr}</span>
                      <div className="mt-0.5 h-1.5 w-full overflow-hidden rounded-full bg-secondary">
                        <div className="h-full rounded-full transition-all" style={{
                          width: `${sPct}%`,
                          backgroundColor: styles.chipBg,
                        }} />
                      </div>
                    </div>
                  </div>
                </div>
              );
            })}
          </div>
        </DialogContent>
      </Dialog>
    </>
  );
}

// ── 공정 흐름 스트립 (노드 기반: 지나온 + 이전 + 현재 + 다음 + 남은) ──
function ProcessFlowStrip({ steps, currentIdx, instrQty }: {
  steps: TimelineProcessStep[]; currentIdx: number; instrQty: number;
}) {
  const safeIdx = currentIdx >= 0 && currentIdx < steps.length ? currentIdx : -1;
  const prevStep = safeIdx > 0 ? steps[safeIdx - 1] : null;
  const currStep = safeIdx >= 0 ? steps[safeIdx] : null;
  const nextStep = safeIdx >= 0 && safeIdx < steps.length - 1 ? steps[safeIdx + 1] : null;

  const hiddenBefore = safeIdx > 1 ? safeIdx - 1 : 0;
  const hiddenAfter = safeIdx >= 0 && safeIdx < steps.length - 2 ? steps.length - safeIdx - 2 : 0;

  const allBeforeDone = hiddenBefore > 0 && safeIdx > 1 && steps.slice(0, safeIdx - 1).every(s => {
    const sem = s.semantic || LEGACY_STATUS_TO_SEMANTIC[s.status] || "pending";
    return sem === "done";
  });

  const renderNode = (step: TimelineProcessStep, isCurrent: boolean) => {
    const sem = step.semantic || LEGACY_STATUS_TO_SEMANTIC[step.status] || "pending";
    return (
      <div className="flex flex-col items-center gap-1">
        <div className={cn(
          "flex h-10 w-10 shrink-0 items-center justify-center rounded-[10px] border-2 text-[14px] font-bold",
          isCurrent
            ? "border-primary bg-primary text-primary-foreground shadow-sm shadow-primary/20"
            : sem === "done"
              ? "border-emerald-200 bg-emerald-50 text-emerald-600"
              : "border-border bg-muted text-muted-foreground",
        )}>
          {sem === "done" && !isCurrent ? <Check className="h-4 w-4" /> : step.seqNo}
        </div>
        <span className={cn(
          "max-w-[56px] truncate text-center text-[11px] font-medium",
          isCurrent ? "font-bold text-primary" : "text-muted-foreground",
        )}>
          {step.processName}
        </span>
      </div>
    );
  };

  const connDone = "mt-[18px] h-[3px] w-5 shrink-0 bg-emerald-400";
  const connPending = "mt-[18px] h-[3px] w-5 shrink-0 bg-border";

  return (
    <div className="flex items-start">
      {hiddenBefore > 0 && (
        <>
          <div className="flex flex-col items-center gap-1">
            <div className={cn(
              "flex h-10 w-10 shrink-0 items-center justify-center rounded-[10px] border-2 text-[13px] font-bold tabular-nums",
              allBeforeDone
                ? "border-emerald-200 bg-emerald-50 text-emerald-600"
                : "border-border bg-muted text-muted-foreground",
            )}>
              +{hiddenBefore}
            </div>
          </div>
          <div className={allBeforeDone ? connDone : connPending} />
        </>
      )}

      {prevStep && (
        <>
          {renderNode(prevStep, false)}
          <div className={connDone} />
        </>
      )}

      {currStep && renderNode(currStep, true)}

      {nextStep && (
        <>
          <div className={connPending} />
          {renderNode(nextStep, false)}
        </>
      )}

      {hiddenAfter > 0 && (
        <>
          <div className={connPending} />
          <div className="flex flex-col items-center gap-1">
            <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-[10px] border-2 border-amber-200 bg-amber-50 text-[13px] font-bold tabular-nums text-amber-600">
              +{hiddenAfter}
            </div>
          </div>
        </>
      )}
    </div>
  );
}

// ── 접수가능 메트릭 ──
function MesAcceptableMetrics({ instrQty, prevGoodQty, availableQty, inputQty, isFirstProcess, isClone, isRework }: {
  instrQty: number; prevGoodQty: number; availableQty: number; inputQty: number; isFirstProcess: boolean; isClone: boolean; isRework?: boolean;
}) {
  if (isRework) {
    return (
      <div className="space-y-3">
        <div className="flex items-center gap-3 text-[14px]">
          <span className="font-medium text-amber-600">불량 재작업 대상</span>
        </div>
        <div className="flex items-center justify-center gap-2.5 rounded-lg py-3.5" style={{ backgroundColor: "rgba(245,158,11,0.08)" }}>
          <span className="text-[16px] font-medium text-muted-foreground">재작업 수량</span>
          <span className="text-[36px] font-extrabold tabular-nums leading-none tracking-tight text-amber-600">{inputQty.toLocaleString()}</span>
          <span className="text-[16px] font-medium text-muted-foreground">EA</span>
        </div>
      </div>
    );
  }
  const displayAvail = isClone ? availableQty : (availableQty || prevGoodQty);
  return (
    <div className="space-y-3">
      <div className="flex items-center gap-4 text-[14px]">
        <span className="text-muted-foreground">지시 <b className="text-foreground">{instrQty.toLocaleString()}</b></span>
        {!isFirstProcess && (
          <span className="text-muted-foreground">전공정양품 <b className="text-emerald-600">{prevGoodQty.toLocaleString()}</b></span>
        )}
        {inputQty > 0 && (
          <span className="text-muted-foreground">기접수 <b className="text-foreground">{inputQty.toLocaleString()}</b></span>
        )}
      </div>
      <div className="flex items-center justify-center gap-2.5 rounded-lg py-3.5" style={{ backgroundColor: "rgba(37,99,235,0.06)" }}>
        <span className="text-[16px] font-medium text-muted-foreground">접수가능</span>
        <span className="text-[36px] font-extrabold tabular-nums leading-none tracking-tight text-primary">{displayAvail.toLocaleString()}</span>
        <span className="text-[16px] font-medium text-muted-foreground">EA</span>
      </div>
    </div>
  );
}

// ── 진행중 메트릭 ──
function MesInProgressMetrics({ inputQty, totalProd, goodQty, defectQty, concessionQty, remainingQty, progressPct, availableQty, isBatchDone, statusColor }: {
  inputQty: number; totalProd: number; goodQty: number; defectQty: number; concessionQty: number; remainingQty: number; progressPct: number; availableQty: number; isBatchDone: boolean; statusColor: string;
}) {
  return (
    <div className="space-y-3">
      <div className="flex items-center gap-4 text-[14px]">
        <span className="text-muted-foreground">접수 <b className="text-foreground">{inputQty.toLocaleString()}</b></span>
        {availableQty > 0 && (
          <span className="text-muted-foreground">추가접수가능 <b className="text-violet-600">{availableQty.toLocaleString()}</b></span>
        )}
      </div>
      <div className="flex items-center justify-center gap-2.5 rounded-lg py-3.5" style={{ backgroundColor: `${statusColor}0F` }}>
        <span className="text-[16px] font-medium text-muted-foreground">생산</span>
        <span className="text-[36px] font-extrabold tabular-nums leading-none tracking-tight" style={{ color: statusColor }}>
          {totalProd.toLocaleString()}
        </span>
        <span className="text-[20px] font-normal text-muted-foreground">/ {inputQty.toLocaleString()}</span>
        <span className="text-[16px] font-medium text-muted-foreground">EA</span>
      </div>
      <div className="flex flex-wrap gap-2">
        <span className="inline-flex items-center gap-1.5 rounded-lg bg-emerald-50 px-3 py-1.5 text-[14px] font-semibold text-emerald-600">
          <span className="font-medium opacity-70">양품</span> {goodQty.toLocaleString()}
        </span>
        {defectQty > 0 && (
          <span className="inline-flex items-center gap-1.5 rounded-lg bg-red-50 px-3 py-1.5 text-[14px] font-semibold text-red-600">
            <span className="font-medium opacity-70">불량</span> {defectQty.toLocaleString()}
          </span>
        )}
        {concessionQty > 0 && (
          <span className="inline-flex items-center gap-1.5 rounded-lg bg-violet-50 px-3 py-1.5 text-[14px] font-semibold text-violet-600">
            <span className="font-medium opacity-70">특채</span> {concessionQty.toLocaleString()}
          </span>
        )}
        <span className={cn(
          "inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-[14px] font-semibold",
          remainingQty > 0 ? "bg-amber-50 text-amber-600" : "bg-emerald-50 text-emerald-600",
        )}>
          <span className="font-medium opacity-70">잔여</span> {remainingQty.toLocaleString()}
        </span>
      </div>
    </div>
  );
}

// ── 완료 메트릭 ──
function MesCompletedMetrics({ instrQty, goodQty, defectQty, concessionQty, yieldRate }: {
  instrQty: number; goodQty: number; defectQty: number; concessionQty: number; yieldRate: number;
}) {
  return (
    <div className="space-y-3">
      <div className="flex items-center gap-4 text-[14px]">
        <span className="text-muted-foreground">지시 <b className="text-foreground">{instrQty.toLocaleString()}</b></span>
        <span
          className="ml-auto rounded-full px-3.5 py-1 text-[14px] font-bold"
          style={{
            backgroundColor: yieldRate >= 95 ? "#f0fdf4" : yieldRate >= 80 ? "#fffbeb" : "#fef2f2",
            color: yieldRate >= 95 ? "#059669" : yieldRate >= 80 ? "#d97706" : "#ef4444",
          }}
        >
          수율 {yieldRate}%
        </span>
      </div>
      <div className="flex items-center justify-center gap-2.5 rounded-lg py-3.5" style={{ backgroundColor: "rgba(5,150,105,0.06)" }}>
        <span className="text-[16px] font-medium text-muted-foreground">최종양품</span>
        <span className="text-[36px] font-extrabold tabular-nums leading-none tracking-tight text-emerald-600">{goodQty.toLocaleString()}</span>
        <span className="text-[16px] font-medium text-muted-foreground">EA</span>
      </div>
      {(defectQty > 0 || concessionQty > 0) && (
        <div className="flex flex-wrap gap-2">
          {defectQty > 0 && (
            <span className="inline-flex items-center gap-1.5 rounded-lg bg-red-50 px-3 py-1.5 text-[14px] font-semibold text-red-600">
              <span className="font-medium opacity-70">불량</span> {defectQty.toLocaleString()}
            </span>
          )}
          {concessionQty > 0 && (
            <span className="inline-flex items-center gap-1.5 rounded-lg bg-violet-50 px-3 py-1.5 text-[14px] font-semibold text-violet-600">
              <span className="font-medium opacity-70">특채</span> {concessionQty.toLocaleString()}
            </span>
          )}
        </div>
      )}
    </div>
  );
}

// ── 메트릭 박스 ──
function MesMetricBox({ label, value, color, dimZero = false }: {
  label: string; value: number; color: string; dimZero?: boolean;
}) {
  const isDim = dimZero && value === 0;
  return (
    <div className={cn("flex flex-col items-center rounded px-1 py-0.5", isDim && "opacity-40")}
      style={{ backgroundColor: `${color}08` }}>
      <span className="text-[8px] text-muted-foreground">{label}</span>
      <span className="text-[11px] font-bold tabular-nums" style={{ color }}>{value.toLocaleString()}</span>
    </div>
  );
}
