"use client";

import React, { useEffect, useState, useMemo, useCallback, useRef } from "react";
import {
  Loader2, Play, Pause, CheckCircle2, AlertCircle, Timer, Package,
  ChevronLeft, ChevronRight, Check, X, CircleDot, ClipboardList,
  Plus, Trash2, Save, FileCheck, Construction, Zap, RefreshCw,
} from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { Badge } from "@/components/ui/badge";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";
import { dataApi } from "@/lib/api/data";
import { apiClient } from "@/lib/api/client";
import { usePopEvent } from "@/hooks/pop/usePopEvent";
import { useAuth } from "@/hooks/useAuth";
import type { PopWorkDetailConfig, ResultSectionConfig, ResultSectionType, PlcDataConfig } from "../types";
import type { TimelineProcessStep } from "../types";
import { ExternalDbConnectionAPI } from "@/lib/api/externalDbConnection";

// ========================================
// 타입
// ========================================

type RowData = Record<string, unknown>;

interface WorkResultRow {
  id: string;
  work_order_process_id: string;
  source_work_item_id: string;
  source_detail_id: string;
  work_phase: string;
  item_title: string;
  item_sort_order: string;
  detail_type: string;
  detail_label: string | null;
  detail_content: string;
  detail_sort_order: string;
  spec_value: string | null;
  lower_limit: string | null;
  upper_limit: string | null;
  input_type: string | null;
  result_value: string | null;
  status: string;
  is_passed: string | null;
  is_required: string | null;
  recorded_by: string | null;
  recorded_at: string | null;
  started_at: string | null;
  group_started_at: string | null;
  group_paused_at: string | null;
  group_total_paused_time: string | null;
  group_completed_at: string | null;
  inspection_method: string | null;
  unit: string | null;
  inspection_code?: string | null;
  judgment_criteria?: string | null;
}

// ========================================
// 입력 UI 타입 해석
// ========================================
//
// 1순위: inspection_standard.judgment_criteria (CAT_JC_01~04)
//   - CAT_JC_01: 수치(범위)    → "number"   (숫자 키패드)
//   - CAT_JC_02: 텍스트입력     → "text"     (텍스트 입력)
//   - CAT_JC_03: O/X           → "checkbox" (큰 토글 버튼)
//   - CAT_JC_04: 선택형         → "select"   (선택 버튼)
//
// 2순위: detail_type 폴백 (inspection_code가 없거나 judgment_criteria가 비어있을 때)
type ResolvedInputType = "number" | "text" | "checkbox" | "select";

function resolveInputType(item: {
  judgment_criteria?: string | null;
  detail_type?: string | null;
  input_type?: string | null;
}): ResolvedInputType {
  // 1순위: judgment_criteria
  if (item.judgment_criteria) {
    switch (item.judgment_criteria) {
      case "CAT_JC_01":
        return "number";
      case "CAT_JC_02":
        return "text";
      case "CAT_JC_03":
        return "checkbox";
      case "CAT_JC_04":
        return "select";
    }
  }
  // 2순위: detail_type 폴백
  switch (item.detail_type) {
    case "checklist":
    case "check":
    case "procedure":
    case "equip_inspection":
      return "checkbox";
    case "inspection":
    case "equip_condition":
    case "production_result":
      return "number";
    case "input":
      return "text";
    case "lookup":
      return "text";
    default:
      // 마지막 보루: 기존 input_type 값 유지 (number/checkbox가 들어있을 수 있음)
      if (item.input_type === "number") return "number";
      if (item.input_type === "checkbox") return "checkbox";
      if (item.input_type === "select") return "select";
      return "text";
  }
}

/**
 * judgment_criteria가 있는 경우 item을 "inspect" 라우터가 올바르게
 * 분기하도록 detail_type/input_type을 정규화한다.
 *
 * CAT_JC_01(수치) → detail_type=inspect, input_type=numeric_range
 * CAT_JC_02(텍스트) → detail_type=inspect, input_type=text
 * CAT_JC_03(O/X)   → detail_type=inspect, input_type=ox
 * CAT_JC_04(선택형) → detail_type=inspect, input_type=select
 *
 * judgment_criteria가 없으면 원본을 그대로 반환(detail_type 폴백).
 */
function normalizeByJudgmentCriteria(item: WorkResultRow): WorkResultRow {
  if (!item.judgment_criteria) return item;
  let inputType: string | null = null;
  switch (item.judgment_criteria) {
    case "CAT_JC_01":
      inputType = "numeric_range";
      break;
    case "CAT_JC_02":
      inputType = "text";
      break;
    case "CAT_JC_03":
      inputType = "ox";
      break;
    case "CAT_JC_04":
      inputType = "select";
      break;
    default:
      return item;
  }
  return { ...item, detail_type: "inspect", input_type: inputType };
}

/**
 * detail_type 폴백 정규화.
 * DB에 실제로 들어있는 원본 detail_type 값(`checklist`, `equip_inspection`,
 * `inspection`, `production_result`, `lookup`, `material_input` 등)을
 * 기존 라우터가 알고 있는 canonical 값(`check`, `inspect`, `input`, `material`)으로
 * 매핑한다.
 *
 * 이 단계는 judgment_criteria 정규화가 일어나지 않은 경우에만 영향을 준다.
 */
function normalizeByDetailType(item: WorkResultRow): WorkResultRow {
  const dt = item.detail_type ?? "";
  // 이미 canonical 타입 or inspect_* 레거시이면 그대로 둔다
  if (
    dt === "check" ||
    dt === "input" ||
    dt === "procedure" ||
    dt === "material" ||
    dt === "result" ||
    dt === "info" ||
    dt === "" ||
    dt.startsWith("inspect")
  ) {
    return item;
  }
  switch (dt) {
    // 토글(확인/미확인) — checklist/장비점검/절차류
    case "checklist":
    case "equip_inspection":
      return { ...item, detail_type: "check" };
    // 숫자 키패드 — 검사/장비상태/생산실적
    case "inspection":
    case "equip_condition":
    case "production_result":
      return { ...item, detail_type: "inspect", input_type: item.input_type || "numeric_range" };
    // 텍스트 — 자유입력/조회
    case "lookup":
      return { ...item, detail_type: "input", input_type: item.input_type || "text" };
    // 자재 투입 — BOM 자재 목록
    case "material_input":
      return { ...item, detail_type: "material" };
    default:
      return item;
  }
}

interface GroupTimerState {
  startedAt: string | null;
  pausedAt: string | null;
  totalPausedTime: number;
  completedAt: string | null;
}

interface WorkGroup {
  phase: string;
  title: string;
  itemId: string;
  sortOrder: number;
  total: number;
  completed: number;
  stepStatus: "pending" | "active" | "completed";
  timer: GroupTimerState;
}

type WorkPhase = "PRE" | "IN" | "POST";
const PHASE_ORDER: Record<string, number> = { PRE: 1, IN: 2, POST: 3 };

interface ProcessTimerData {
  started_at: string | null;
  paused_at: string | null;
  total_paused_time: string | null;
  completed_at: string | null;
  completed_by: string | null;
  actual_work_time: string | null;
  status: string;
  good_qty: string | null;
  defect_qty: string | null;
  total_production_qty: string | null;
  defect_detail: string | null;
  result_note: string | null;
  result_status: string | null;
  input_qty: string | null;
}

interface DefectDetailEntry {
  defect_code: string;
  defect_name: string;
  qty: string;
  disposition: string;
}

interface DefectTypeOption {
  id: string;
  defect_code: string;
  defect_name: string;
  defect_type: string;
  severity: string;
}

const DEFAULT_INFO_FIELDS = [
  { label: "작업지시", column: "wo_no" },
  { label: "품목", column: "item_name" },
  { label: "공정", column: "__process_name" },
  { label: "지시수량", column: "qty" },
];

const DEFAULT_CFG: PopWorkDetailConfig = {
  showTimer: true,
  showQuantityInput: false,
  displayMode: "list",
  phaseLabels: { PRE: "작업 전", IN: "작업 중", POST: "작업 후" },
  infoBar: { enabled: true, fields: [] },
  stepControl: { requireStartBeforeInput: false, autoAdvance: true },
  navigation: { showPrevNext: true, showCompleteButton: true },
  resultSections: [
    { id: "total-qty", type: "total-qty", enabled: true, showCondition: { type: "always" } },
    { id: "good-defect", type: "good-defect", enabled: true, showCondition: { type: "always" } },
    { id: "defect-types", type: "defect-types", enabled: true, showCondition: { type: "always" } },
    { id: "note", type: "note", enabled: true, showCondition: { type: "always" } },
  ],
};

// ========================================
// ISA-101 디자인 토큰 (combined-final 기준)
// ========================================

const DESIGN = {
  button: { height: 60, minWidth: 130 },
  input: { height: 52 },
  stat: { valueSize: 40, labelSize: 16, weight: 700 },
  section: { titleSize: 16, gap: 16 },
  tab: { height: 52 },
  footer: { height: 64 },
  header: { height: 52 },
  kpi: { valueSize: 44, labelSize: 15, weight: 800 },
  nav: { height: 56 },
  infoBar: { labelSize: 13, valueSize: 16 },
  defectRow: { height: 48 },
  sidebar: { width: 220 },
  bg: { page: '#F5F5F5', card: '#FFFFFF', header: '#1a1a2e', infoBar: '#1a1a2e' },
} as const;

// ========================================
// Glossy 버튼 스타일 (v3-rev2 참고)
// ========================================

const glossyBase: React.CSSProperties = {
  position: 'relative',
  borderRadius: 16,
  fontWeight: 800,
  cursor: 'pointer',
  textShadow: '0 1px 3px rgba(0,0,0,0.3)',
  transition: 'all 0.15s ease',
  border: 'none',
  color: 'white',
};

const glossyOverlay: React.CSSProperties = {
  content: "''",
  position: 'absolute',
  top: 0,
  left: 0,
  right: 0,
  height: '50%',
  borderRadius: '16px 16px 0 0',
  background: 'linear-gradient(to bottom, rgba(255,255,255,0.4), rgba(255,255,255,0.05))',
  pointerEvents: 'none',
};

const GLOSSY_VARIANTS = {
  green: {
    background: 'linear-gradient(to bottom, #4ade80, #16a34a)',
    boxShadow: '0 4px 12px rgba(34,197,94,0.3), inset 0 1px 0 rgba(255,255,255,0.4)',
  },
  blue: {
    background: 'linear-gradient(to bottom, #60a5fa, #2563eb)',
    boxShadow: '0 4px 12px rgba(59,130,246,0.3), inset 0 1px 0 rgba(255,255,255,0.4)',
  },
  red: {
    background: 'linear-gradient(to bottom, #f87171, #dc2626)',
    boxShadow: '0 4px 12px rgba(239,68,68,0.3), inset 0 1px 0 rgba(255,255,255,0.4)',
  },
  teal: {
    background: 'linear-gradient(to bottom, #2dd4bf, #0d9488)',
    boxShadow: '0 4px 12px rgba(20,184,166,0.3), inset 0 1px 0 rgba(255,255,255,0.4)',
  },
  purple: {
    background: 'linear-gradient(to bottom, #a78bfa, #7c3aed)',
    boxShadow: '0 4px 12px rgba(139,92,246,0.3), inset 0 1px 0 rgba(255,255,255,0.4)',
  },
  yellow: {
    background: 'linear-gradient(to bottom, #fbbf24, #d97706)',
    boxShadow: '0 4px 12px rgba(245,158,11,0.3), inset 0 1px 0 rgba(255,255,255,0.4)',
  },
  gray: {
    background: 'linear-gradient(to bottom, #9ca3af, #6b7280)',
    boxShadow: '0 4px 12px rgba(107,114,128,0.3), inset 0 1px 0 rgba(255,255,255,0.4)',
  },
} as const;

type GlossyVariant = keyof typeof GLOSSY_VARIANTS;

function GlossyButton({
  variant,
  children,
  onClick,
  disabled,
  className,
  style,
}: {
  variant: GlossyVariant;
  children: React.ReactNode;
  onClick?: () => void;
  disabled?: boolean;
  className?: string;
  style?: React.CSSProperties;
}) {
  const variantStyle = GLOSSY_VARIANTS[variant];
  return (
    <button
      className={cn("flex items-center justify-center gap-2", className)}
      style={{
        ...glossyBase,
        ...variantStyle,
        minHeight: 60,
        minWidth: DESIGN.button.minWidth,
        fontSize: 18,
        ...style,
        ...(disabled ? { opacity: 0.4, cursor: 'not-allowed' } : {}),
      }}
      onClick={disabled ? undefined : onClick}
      disabled={disabled}
    >
      <span style={glossyOverlay} />
      {children}
    </button>
  );
}

const COLORS = {
  good: 'text-green-600',
  defect: 'text-red-600',
  complete: 'text-blue-600',
  warning: 'text-amber-600',
  info: 'text-violet-600',
  kpiInput: 'text-gray-900',
  kpiComplete: 'text-blue-600',
  kpiRemaining: 'text-amber-600',
  kpiDefect: 'text-red-600',
} as const;

// ========================================
// 숫자 키패드 모달 (체크리스트/자재투입 공용)
// ========================================

const NUMPAD_KEYS = [
  { label: "7", action: "7" },
  { label: "8", action: "8" },
  { label: "9", action: "9" },
  { label: "\u2190", action: "backspace" },
  { label: "4", action: "4" },
  { label: "5", action: "5" },
  { label: "6", action: "6" },
  { label: "C", action: "clear" },
  { label: "1", action: "1" },
  { label: "2", action: "2" },
  { label: "3", action: "3" },
  { label: ".", action: "dot" },
];

interface NumericKeypadModalProps {
  open: boolean;
  onClose: () => void;
  onConfirm: (value: string) => void;
  title?: string;
  unit?: string;
  initialValue?: string;
  lowerLimit?: number;
  upperLimit?: number;
}

function NumericKeypadModal({
  open,
  onClose,
  onConfirm,
  title,
  unit,
  initialValue,
  lowerLimit,
  upperLimit,
}: NumericKeypadModalProps) {
  const [value, setValue] = useState(initialValue || "0");
  const hasRange = lowerLimit !== undefined && upperLimit !== undefined && !isNaN(lowerLimit) && !isNaN(upperLimit);

  useEffect(() => {
    if (open) {
      setValue(initialValue || "0");
    }
  }, [open, initialValue]);

  const handleKey = useCallback((action: string) => {
    setValue((prev) => {
      switch (action) {
        case "backspace":
          return prev.length <= 1 ? "0" : prev.slice(0, -1);
        case "clear":
          return "0";
        case "dot":
          return prev.includes(".") ? prev : prev + ".";
        default: {
          if (prev === "0" && action !== ".") return action;
          return prev + action;
        }
      }
    });
  }, []);

  const handleConfirm = () => {
    const num = parseFloat(value);
    if (isNaN(num)) return;
    onConfirm(value);
    onClose();
  };

  if (!open) return null;

  const numValue = parseFloat(value);
  const isOutOfRange = hasRange && !isNaN(numValue) && (numValue < lowerLimit! || numValue > upperLimit!);

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center">
      <div className="absolute inset-0 bg-black/40" onClick={onClose} />
      <div className="relative bg-white w-[90%] max-w-[360px] rounded-2xl shadow-2xl z-10 overflow-hidden">
        {/* Header */}
        <div
          className="flex items-center justify-between px-4 py-3"
          style={{ background: "linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)" }}
        >
          <span className="text-sm font-semibold text-white truncate">
            {title || "숫자 입력"}
          </span>
          <button
            onClick={onClose}
            className="w-8 h-8 rounded-lg bg-white/20 flex items-center justify-center text-white hover:bg-white/30"
          >
            <X className="w-4 h-4" />
          </button>
        </div>

        <div className="p-4">
          {/* Range indicator */}
          {hasRange && (
            <div className={cn(
              "mb-3 text-center text-sm font-semibold px-3 py-2 rounded-lg",
              isOutOfRange
                ? "bg-red-50 text-red-600 border border-red-200"
                : "bg-blue-50 text-blue-700 border border-blue-200"
            )}>
              기준: {lowerLimit} ~ {upperLimit}{unit ? ` ${unit}` : ""}
            </div>
          )}

          {/* Display */}
          <div className="relative mb-3">
            <input
              type="text"
              readOnly
              value={value === "0" ? "0" : value}
              className={cn(
                "w-full px-4 py-3 text-right text-3xl font-bold border-2 rounded-xl bg-gray-50",
                isOutOfRange ? "border-red-300 text-red-600" : "border-gray-200 text-gray-900"
              )}
              style={{ fontVariantNumeric: "tabular-nums" }}
            />
            {unit && (
              <span className="absolute right-14 top-1/2 -translate-y-1/2 text-gray-400 text-lg">{unit}</span>
            )}
          </div>

          {/* Numpad grid */}
          <div className="grid grid-cols-4 gap-2.5">
            {NUMPAD_KEYS.map((key) => (
              <button
                key={key.action}
                onClick={() => handleKey(key.action)}
                className={`h-16 rounded-xl text-lg font-semibold active:scale-95 transition-all ${
                  key.action === "backspace" || key.action === "clear"
                    ? "bg-amber-100 text-amber-700 hover:bg-amber-200"
                    : key.action === "dot"
                    ? "bg-gray-200 text-gray-700 hover:bg-gray-300"
                    : "bg-gray-100 text-gray-900 hover:bg-gray-200"
                }`}
                style={{ minWidth: 64, minHeight: 64 }}
              >
                {key.label}
              </button>
            ))}
            {/* Bottom row: 0 (span 2) + Confirm (span 2) */}
            <button
              onClick={() => handleKey("0")}
              className="col-span-2 h-16 rounded-xl text-lg font-semibold bg-gray-100 text-gray-900 hover:bg-gray-200 active:scale-95 transition-all"
              style={{ minHeight: 64 }}
            >
              0
            </button>
            <button
              onClick={handleConfirm}
              disabled={isNaN(parseFloat(value))}
              className={cn(
                "col-span-2 h-16 rounded-xl text-lg font-bold text-white active:scale-95 transition-all",
                isNaN(parseFloat(value)) && "opacity-40 cursor-not-allowed"
              )}
              style={{
                minHeight: 64,
                background: isNaN(parseFloat(value))
                  ? "#9ca3af"
                  : isOutOfRange
                  ? "linear-gradient(135deg, #f87171 0%, #dc2626 100%)"
                  : "linear-gradient(135deg, #10b981 0%, #059669 100%)",
              }}
            >
              {isOutOfRange ? "확인 (범위 외)" : "확인"}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ========================================
// Props
// ========================================

interface PopWorkDetailComponentProps {
  config?: PopWorkDetailConfig;
  screenId?: string;
  componentId?: string;
  currentRowSpan?: number;
  currentColSpan?: number;
  parentRow?: RowData;
}

// ========================================
// 메인 컴포넌트
// ========================================

export function PopWorkDetailComponent({
  config,
  screenId,
  parentRow: parentRowProp,
}: PopWorkDetailComponentProps) {
  const { getSharedData, publish } = usePopEvent(screenId || "default");
  const { user } = useAuth();

  // 모달 내부 렌더링 감지: 부모에 [role="dialog"]가 있으면 모달 모드
  const containerRef = useRef<HTMLDivElement>(null);
  const [isInModal, setIsInModal] = useState(false);
  useEffect(() => {
    if (containerRef.current) {
      setIsInModal(!!containerRef.current.closest('[role="dialog"]'));
    }
  }, []);

  const cfg: PopWorkDetailConfig = {
    ...DEFAULT_CFG,
    ...config,
    displayMode: config?.displayMode ?? DEFAULT_CFG.displayMode,
    infoBar: { ...DEFAULT_CFG.infoBar, ...config?.infoBar },
    stepControl: { ...DEFAULT_CFG.stepControl, ...config?.stepControl },
    navigation: { ...DEFAULT_CFG.navigation, ...config?.navigation },
    phaseLabels: { ...DEFAULT_CFG.phaseLabels, ...config?.phaseLabels },
  };

  const parentRow = parentRowProp ?? getSharedData<RowData>("parentRow");
  const processFlow = parentRow?.__processFlow__ as TimelineProcessStep[] | undefined;
  const currentProcess = processFlow?.find((p) => p.isCurrent);
  const workOrderProcessId = parentRow?.__splitProcessId
    ? String(parentRow.__splitProcessId)
    : parentRow?.__process_id
      ? String(parentRow.__process_id)
      : currentProcess?.processId
        ? String(currentProcess.processId)
        : undefined;
  const processName = currentProcess?.processName ?? "공정 상세";

  // ========================================
  // 상태
  // ========================================

  const [allResults, setAllResults] = useState<WorkResultRow[]>([]);
  const [processData, setProcessData] = useState<ProcessTimerData | null>(null);
  const [loading, setLoading] = useState(true);
  const [selectedGroupId, setSelectedGroupId] = useState<string | null>(null);
  const [tick, setTick] = useState(Date.now());
  const [savingIds, setSavingIds] = useState<Set<string>>(new Set());
  const [activeStepIds, setActiveStepIds] = useState<Set<string>>(new Set());

  const [goodQty, setGoodQty] = useState("");
  const [defectQty, setDefectQty] = useState("");
  const [currentItemIdx, setCurrentItemIdx] = useState(0);
  const [showQuantityPanel, setShowQuantityPanel] = useState(false);

  // 탭 상태: 상단 탭 (작업 전 | 작업 중 | 작업 후 | 실적)
  const [activePhaseTab, setActivePhaseTab] = useState<string | null>(null);
  const [resultTabActive, setResultTabActive] = useState(false);
  const hasResultSections = !!(cfg.resultSections && cfg.resultSections.some((s) => s.enabled));

  // 불량 유형 목록 (부모에서 1회 로드, ResultPanel에 전달)
  const [cachedDefectTypes, setCachedDefectTypes] = useState<DefectTypeOption[]>([]);

  const contentRef = useRef<HTMLDivElement>(null);

  // ========================================
  // 데이터 로드
  // ========================================

  const fetchData = useCallback(async () => {
    if (!workOrderProcessId) {
      setLoading(false);
      return;
    }
    try {
      setLoading(true);
      const [resultRes, processRes] = await Promise.all([
        // POP 전용 API: inspection_standard.judgment_criteria 조인 포함
        apiClient
          .get(`/pop/production/checklist-items/${workOrderProcessId}`)
          .then((res) => ({ data: (res.data?.data ?? []) as WorkResultRow[] }))
          .catch(async () => {
            // 폴백: 전용 API가 없는 환경에서는 기존 일반 조회 사용
            const fb = await dataApi.getTableData("process_work_result", {
              size: 500,
              filters: { work_order_process_id: workOrderProcessId },
            });
            return { data: (fb.data ?? []) as unknown as WorkResultRow[] };
          }),
        dataApi.getTableData("work_order_process", {
          size: 1,
          filters: { id: workOrderProcessId },
        }),
      ]);
      setAllResults(resultRes.data);
      const proc = (processRes.data?.[0] ?? null) as ProcessTimerData | null;
      setProcessData(proc);
      if (proc) {
        setGoodQty(proc.good_qty ?? "");
        setDefectQty(proc.defect_qty ?? "");
      }
    } catch {
      toast.error("데이터를 불러오는데 실패했습니다.");
    } finally {
      setLoading(false);
    }
  }, [workOrderProcessId]);

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

  useEffect(() => {
    const loadDefectTypes = async () => {
      try {
        const res = await apiClient.get("/pop/production/defect-types");
        if (res.data?.success) {
          setCachedDefectTypes(res.data.data || []);
        }
      } catch { /* 실패 시 빈 배열 유지 */ }
    };
    loadDefectTypes();
  }, []);

  // ========================================
  // 좌측 사이드바 - 작업항목 그룹핑
  // ========================================

  const groups = useMemo<WorkGroup[]>(() => {
    const map = new Map<string, WorkGroup>();
    for (const row of allResults) {
      const key = row.source_work_item_id;
      if (!map.has(key)) {
        map.set(key, {
          phase: row.work_phase,
          title: row.item_title,
          itemId: key,
          sortOrder: parseInt(row.item_sort_order || "0", 10),
          total: 0,
          completed: 0,
          stepStatus: "pending",
          timer: {
            startedAt: row.group_started_at ?? null,
            pausedAt: row.group_paused_at ?? null,
            totalPausedTime: parseInt(row.group_total_paused_time || "0", 10),
            completedAt: row.group_completed_at ?? null,
          },
        });
      }
      const g = map.get(key)!;
      g.total++;
      if (row.status === "completed") g.completed++;
    }
    const arr = Array.from(map.values()).sort(
      (a, b) =>
        (PHASE_ORDER[a.phase] ?? 9) - (PHASE_ORDER[b.phase] ?? 9) ||
        a.sortOrder - b.sortOrder
    );
    for (const g of arr) {
      if (g.completed >= g.total && g.total > 0) {
        g.stepStatus = "completed";
      } else if (activeStepIds.has(g.itemId) || g.timer.startedAt) {
        g.stepStatus = "active";
      }
    }
    return arr;
  }, [allResults, activeStepIds]);

  const groupsByPhase = useMemo(() => {
    const result: Record<string, WorkGroup[]> = {};
    for (const g of groups) {
      if (!result[g.phase]) result[g.phase] = [];
      result[g.phase].push(g);
    }
    return result;
  }, [groups]);

  // 사용 가능한 phase 목록 (탭으로 표시)
  const availablePhases = useMemo(() => {
    const phases: WorkPhase[] = [];
    if (groupsByPhase["PRE"]?.length) phases.push("PRE");
    if (groupsByPhase["IN"]?.length) phases.push("IN");
    if (groupsByPhase["POST"]?.length) phases.push("POST");
    return phases;
  }, [groupsByPhase]);

  // 탭별 완료 카운트 (탭 라벨에 표시)
  const phaseProgress = useMemo(() => {
    const result: Record<string, { done: number; total: number }> = {};
    for (const phase of availablePhases) {
      const phaseGrps = groupsByPhase[phase] || [];
      const totalItems = phaseGrps.reduce((s, g) => s + g.total, 0);
      const doneItems = phaseGrps.reduce((s, g) => s + g.completed, 0);
      result[phase] = { done: doneItems, total: totalItems };
    }
    return result;
  }, [availablePhases, groupsByPhase]);

  useEffect(() => {
    if (groups.length > 0 && !selectedGroupId) {
      setSelectedGroupId(groups[0].itemId);
      if (!activePhaseTab) {
        setActivePhaseTab(groups[0].phase);
      }
    } else if (groups.length === 0 && hasResultSections && !resultTabActive) {
      setResultTabActive(true);
    }
  }, [groups, selectedGroupId, hasResultSections, resultTabActive, activePhaseTab]);

  // 현재 선택 인덱스
  const selectedIndex = useMemo(
    () => groups.findIndex((g) => g.itemId === selectedGroupId),
    [groups, selectedGroupId]
  );

  // ========================================
  // 네비게이션
  // ========================================

  const navigateStep = useCallback(
    (delta: number) => {
      const nextIdx = selectedIndex + delta;
      if (nextIdx >= 0 && nextIdx < groups.length) {
        setSelectedGroupId(groups[nextIdx].itemId);
        contentRef.current?.scrollTo({ top: 0, behavior: "smooth" });
      }
    },
    [selectedIndex, groups]
  );

  // ========================================
  // 우측 체크리스트
  // ========================================

  const currentItems = useMemo(
    () =>
      allResults
        .filter((r) => r.source_work_item_id === selectedGroupId)
        .sort(
          (a, b) =>
            parseInt(a.detail_sort_order || "0", 10) -
            parseInt(b.detail_sort_order || "0", 10)
        ),
    [allResults, selectedGroupId]
  );

  // 스텝 모드: 전체 항목을 flat 리스트로 정렬
  const flatItems = useMemo(() => {
    const sorted = [...allResults].sort(
      (a, b) =>
        (PHASE_ORDER[a.work_phase] ?? 9) - (PHASE_ORDER[b.work_phase] ?? 9) ||
        parseInt(a.item_sort_order || "0", 10) - parseInt(b.item_sort_order || "0", 10) ||
        parseInt(a.detail_sort_order || "0", 10) - parseInt(b.detail_sort_order || "0", 10)
    );
    return sorted;
  }, [allResults]);

  // 스텝 모드: 모든 체크리스트 항목 완료 여부
  const allItemsCompleted = useMemo(
    () => flatItems.length > 0 && flatItems.every((r) => r.status === "completed"),
    [flatItems]
  );

  // 그룹 선택 변경 시 스텝 인덱스 리셋
  useEffect(() => {
    setCurrentItemIdx(0);
  }, [selectedGroupId]);

  // 스텝 모드 자동 다음 이동
  const stepAutoAdvance = useCallback(() => {
    if (cfg.displayMode !== "step") return;
    const nextPendingIdx = currentItems.findIndex(
      (r, i) => i > currentItemIdx && r.status !== "completed"
    );
    if (nextPendingIdx >= 0) {
      setCurrentItemIdx(nextPendingIdx);
    } else if (currentItems.every((r) => r.status === "completed")) {
      // 현재 그룹 완료 → 다음 그룹
      const nextGroupIdx = groups.findIndex(
        (g, i) => i > selectedIndex && g.stepStatus !== "completed"
      );
      if (nextGroupIdx >= 0) {
        setSelectedGroupId(groups[nextGroupIdx].itemId);
        toast.success(`${groups[selectedIndex]?.title} 완료`);
      } else {
        setShowQuantityPanel(true);
      }
    }
  }, [cfg.displayMode, currentItems, currentItemIdx, groups, selectedIndex, selectedGroupId]);

  const saveResultValue = useCallback(
    async (
      rowId: string,
      resultValue: string,
      isPassed: string | null,
      newStatus: string
    ) => {
      setSavingIds((prev) => new Set(prev).add(rowId));
      try {
        const existingRow = allResults.find((r) => r.id === rowId);
        const isFirstTouch = existingRow && !existingRow.started_at;
        const now = new Date().toISOString();

        const mkTask = (col: string, val: string) => ({
          type: "data-update" as const,
          targetTable: "process_work_result",
          targetColumn: col,
          operationType: "assign" as const,
          valueSource: "fixed" as const,
          fixedValue: val,
          lookupMode: "manual" as const,
          manualItemField: "id",
          manualPkColumn: "id",
        });

        const tasks = [
          mkTask("result_value", resultValue),
          mkTask("status", newStatus),
          ...(isPassed !== null ? [mkTask("is_passed", isPassed)] : []),
          mkTask("recorded_by", user?.userId ?? ""),
          mkTask("recorded_at", now),
          ...(isFirstTouch ? [mkTask("started_at", now)] : []),
        ];

        await apiClient.post("/pop/execute-action", {
          tasks,
          data: { items: [{ id: rowId }], fieldValues: {} },
        });

        setAllResults((prev) =>
          prev.map((r) =>
            r.id === rowId
              ? {
                  ...r,
                  result_value: resultValue,
                  status: newStatus,
                  is_passed: isPassed,
                  recorded_by: user?.userId ?? null,
                  recorded_at: now,
                  started_at: r.started_at ?? now,
                }
              : r
          )
        );

        if (cfg.stepControl.autoAdvance && newStatus === "completed") {
          setTimeout(() => {
            if (cfg.displayMode === "step") {
              stepAutoAdvance();
            } else {
              checkAutoAdvance();
            }
          }, 300);
        }
      } catch {
        toast.error("저장에 실패했습니다.");
      } finally {
        setSavingIds((prev) => {
          const next = new Set(prev);
          next.delete(rowId);
          return next;
        });
      }
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [user?.userId, cfg.stepControl.autoAdvance, allResults]
  );

  const checkAutoAdvance = useCallback(() => {
    if (!selectedGroupId) return;
    const groupItems = allResults.filter(
      (r) => r.source_work_item_id === selectedGroupId
    );
    const requiredItems = groupItems.filter((r) => r.is_required === "Y");
    const allRequiredDone = requiredItems.length === 0 || requiredItems.every((r) => r.status === "completed");
    const allDone = groupItems.every((r) => r.status === "completed");

    // 필수 항목 미체크 시 다음 공정으로 안 넘어감
    if (!allRequiredDone) return;

    if (allDone) {
      const idx = groups.findIndex((g) => g.itemId === selectedGroupId);
      if (idx >= 0 && idx < groups.length - 1) {
        const nextGroup = groups[idx + 1];
        setSelectedGroupId(nextGroup.itemId);
        setActivePhaseTab(nextGroup.phase);
        contentRef.current?.scrollTo({ top: 0, behavior: "smooth" });
        toast.success(`${groups[idx].title} 완료 → ${nextGroup.title}`);
      }
    }
  }, [selectedGroupId, allResults, groups]);

  // ========================================
  // 단계 시작/활성화
  // ========================================

  const isStepLocked = useMemo(() => {
    if (!cfg.stepControl.requireStartBeforeInput) return false;
    if (!selectedGroupId) return true;
    return !activeStepIds.has(selectedGroupId);
  }, [cfg.stepControl.requireStartBeforeInput, selectedGroupId, activeStepIds]);

  // ========================================
  // 프로세스 타이머 (전체 공정용)
  // ========================================

  useEffect(() => {
    if (!cfg.showTimer) return;
    const hasActiveGroupTimer = groups.some(
      (g) => g.timer.startedAt && !g.timer.completedAt
    );
    if (!hasActiveGroupTimer && !processData?.started_at) return;
    const id = setInterval(() => setTick(Date.now()), 1000);
    return () => clearInterval(id);
  }, [cfg.showTimer, processData?.started_at, groups]);

  // 프로세스 레벨 타이머는 그룹별 타이머로 대체됨

  // ========================================
  // 그룹별 타이머
  // ========================================

  const selectedGroupTimer = useMemo(() => {
    const g = groups.find((g) => g.itemId === selectedGroupId);
    return g?.timer ?? { startedAt: null, pausedAt: null, totalPausedTime: 0, completedAt: null };
  }, [groups, selectedGroupId]);

  // 그룹 타이머: 순수 작업시간 (일시정지 제외)
  const groupWorkMs = useMemo(() => {
    if (!selectedGroupTimer.startedAt) return 0;
    const end = selectedGroupTimer.completedAt
      ? new Date(selectedGroupTimer.completedAt).getTime()
      : tick;
    const start = new Date(selectedGroupTimer.startedAt).getTime();
    const totalMs = end - start;
    const pausedMs = selectedGroupTimer.totalPausedTime * 1000;
    const currentPauseMs = selectedGroupTimer.pausedAt
      ? (selectedGroupTimer.completedAt ? 0 : tick - new Date(selectedGroupTimer.pausedAt).getTime())
      : 0;
    return Math.max(0, totalMs - pausedMs - currentPauseMs);
  }, [selectedGroupTimer, tick]);

  // 그룹 타이머: 경과 시간 (일시정지 무시, 시작~끝)
  const groupElapsedMs = useMemo(() => {
    if (!selectedGroupTimer.startedAt) return 0;
    const end = selectedGroupTimer.completedAt
      ? new Date(selectedGroupTimer.completedAt).getTime()
      : tick;
    const start = new Date(selectedGroupTimer.startedAt).getTime();
    return Math.max(0, end - start);
  }, [selectedGroupTimer, tick]);

  const groupTimerFormatted = useMemo(() => formatMsToTime(groupWorkMs), [groupWorkMs]);
  const groupElapsedFormatted = useMemo(() => formatMsToTime(groupElapsedMs), [groupElapsedMs]);

  const isGroupStarted = !!selectedGroupTimer.startedAt;
  const isGroupPaused = !!selectedGroupTimer.pausedAt;
  const isGroupCompleted = !!selectedGroupTimer.completedAt;

  const handleGroupTimerAction = useCallback(
    async (action: "start" | "pause" | "resume" | "complete") => {
      if (!workOrderProcessId || !selectedGroupId) return;
      try {
        await apiClient.post("/pop/production/group-timer", {
          work_order_process_id: workOrderProcessId,
          source_work_item_id: selectedGroupId,
          action,
        });
        await fetchData();
      } catch {
        toast.error("그룹 타이머 제어에 실패했습니다.");
      }
    },
    [workOrderProcessId, selectedGroupId, fetchData]
  );

  const handleTimerAction = useCallback(
    async (action: "start" | "pause" | "resume" | "complete") => {
      if (!workOrderProcessId) return;
      try {
        const body: Record<string, unknown> = {
          work_order_process_id: workOrderProcessId,
          action,
        };
        if (action === "complete") {
          body.good_qty = goodQty || "0";
          body.defect_qty = defectQty || "0";
        }
        await apiClient.post("/pop/production/timer", body);
        const res = await dataApi.getTableData("work_order_process", {
          size: 1,
          filters: { id: workOrderProcessId },
        });
        const proc = (res.data?.[0] ?? null) as ProcessTimerData | null;
        if (proc) {
          setProcessData(proc);
          if (action === "complete") {
            toast.success("공정이 완료되었습니다.");
            publish("process_completed", { workOrderProcessId, goodQty, defectQty });
          }
        }
      } catch {
        toast.error("타이머 제어에 실패했습니다.");
      }
    },
    [workOrderProcessId, goodQty, defectQty, publish]
  );

  // ========================================
  // 수량 등록 + 완료
  // ========================================

  const handleQuantityRegister = useCallback(async () => {
    if (!workOrderProcessId) return;
    try {
      const mkWopTask = (col: string, val: string) => ({
        type: "data-update" as const,
        targetTable: "work_order_process",
        targetColumn: col,
        operationType: "assign" as const,
        valueSource: "fixed" as const,
        fixedValue: val,
        lookupMode: "manual" as const,
        manualItemField: "id",
        manualPkColumn: "id",
      });
      await apiClient.post("/pop/execute-action", {
        tasks: [
          mkWopTask("good_qty", goodQty || "0"),
          mkWopTask("defect_qty", defectQty || "0"),
        ],
        data: { items: [{ id: workOrderProcessId }], fieldValues: {} },
      });
      toast.success("수량이 등록되었습니다.");
    } catch {
      toast.error("수량 등록에 실패했습니다.");
    }
  }, [workOrderProcessId, goodQty, defectQty]);

  const handleProcessComplete = useCallback(async () => {
    await handleTimerAction("complete");
  }, [handleTimerAction]);

  // 현재 탭의 그룹 목록
  const currentTabGroups = useMemo(
    () => (activePhaseTab && !resultTabActive ? groupsByPhase[activePhaseTab] ?? [] : []),
    [activePhaseTab, resultTabActive, groupsByPhase]
  );

  // 탭 전환 시 해당 phase의 첫 번째 그룹 자동 선택
  const handlePhaseTabChange = useCallback((phase: string) => {
    setActivePhaseTab(phase);
    setResultTabActive(false);
    const phaseGrps = groupsByPhase[phase];
    if (phaseGrps && phaseGrps.length > 0) {
      setSelectedGroupId(phaseGrps[0].itemId);
    }
    contentRef.current?.scrollTo({ top: 0, behavior: "smooth" });
  }, [groupsByPhase]);

  const handleResultTabClick = useCallback(() => {
    setResultTabActive(true);
    setActivePhaseTab(null);
    setSelectedGroupId(null);
    contentRef.current?.scrollTo({ top: 0, behavior: "smooth" });
  }, []);

  // ========================================
  // 안전 장치
  // ========================================

  if (!parentRow) {
    return (
      <div className="flex h-full items-center justify-center text-sm text-muted-foreground">
        <AlertCircle className="mr-2 h-4 w-4" />
        카드를 선택해주세요
      </div>
    );
  }

  if (!workOrderProcessId) {
    return (
      <div className="flex h-full items-center justify-center text-sm text-muted-foreground">
        <AlertCircle className="mr-2 h-4 w-4" />
        공정 정보를 찾을 수 없습니다
      </div>
    );
  }

  if (loading) {
    return (
      <div className="flex h-full items-center justify-center">
        <Loader2 className="h-6 w-6 animate-spin text-primary" />
      </div>
    );
  }

  const isProcessCompleted = processData?.status === "completed";

  if (allResults.length === 0 && !hasResultSections) {
    return (
      <div className="flex h-full items-center justify-center text-sm text-muted-foreground">
        <AlertCircle className="mr-2 h-4 w-4" />
        작업기준이 등록되지 않았습니다
      </div>
    );
  }
  const selectedGroup = groups.find((g) => g.itemId === selectedGroupId);

  // ========================================
  // 렌더링 (combined-final 레이아웃)
  // ========================================

  const woNo = parentRow?.wo_no ? String(parentRow.wo_no) : "";

  return (
    <div ref={containerRef} className="flex h-full flex-col rounded-2xl overflow-hidden" style={{ backgroundColor: DESIGN.bg.card }}>
      {/* ── 내부 헤더: 모달이 아닐 때만 표시 (모달은 Dialog 자체 헤더 사용) ── */}
      {!isInModal && (
        <div
          className="flex shrink-0 items-center justify-between border-b border-gray-100 px-6"
          style={{ height: `${DESIGN.header.height}px` }}
        >
          <div className="flex items-center gap-4">
            <h2 className="font-bold tracking-tight text-gray-900" style={{ fontSize: 20 }}>작업 상세</h2>
            {woNo && <span className="font-mono text-gray-400" style={{ fontSize: 14 }}>{woNo}</span>}
          </div>
          <button
            className="flex h-10 w-10 items-center justify-center rounded-xl text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600"
            onClick={() => publish("__pop_modal_close__", {})}
          >
            <X className="h-5 w-5" />
          </button>
        </div>
      )}

      {/* ── 정보바: 미니멀 다크 (고정) ── */}
      {cfg.infoBar.enabled && (
        <InfoBar
          fields={cfg.infoBar.fields.length > 0 ? cfg.infoBar.fields : DEFAULT_INFO_FIELDS}
          parentRow={parentRow}
          processName={processName}
        />
      )}

      {/* ── 본문: 사이드바 + 콘텐츠 ── */}
      <div className="flex flex-1 overflow-hidden">

        {/* ===== 사이드바 ===== */}
        <div
          className="flex shrink-0 flex-col overflow-y-auto border-r border-gray-100"
          style={{
            width: `${DESIGN.sidebar.width}px`,
            backgroundColor: 'rgba(249,250,251,0.5)',
            scrollbarWidth: 'thin',
          }}
        >
          <div className="py-4">
            {/* 페이즈별 그룹 */}
            {availablePhases.map((phase) => {
              const phaseGrps = groupsByPhase[phase] || [];
              const progress = phaseProgress[phase];
              const allDone = progress && progress.done >= progress.total && progress.total > 0;
              const anyActive = phaseGrps.some((g) => g.stepStatus === "active");

              return (
                <div key={phase} className="mb-5">
                  {/* 페이즈 헤더 */}
                  <div className="mb-2 flex items-center gap-2 px-4">
                    <div
                      className={cn(
                        "flex h-5 w-5 items-center justify-center rounded-full",
                        allDone ? "bg-green-500" : anyActive ? "bg-blue-500" : "bg-gray-300"
                      )}
                    >
                      {allDone ? (
                        <Check className="h-3 w-3 text-white" strokeWidth={3} />
                      ) : anyActive ? (
                        <svg className="h-3 w-3 text-white" fill="none" stroke="currentColor" strokeWidth={3} viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
                      ) : (
                        <Check className="h-3 w-3 text-white" strokeWidth={3} />
                      )}
                    </div>
                    <span
                      className={cn(
                        "font-semibold uppercase tracking-wider",
                        allDone || anyActive ? "text-gray-900" : "text-gray-400"
                      )}
                      style={{ fontSize: 13 }}
                    >
                      {cfg.phaseLabels[phase] ?? phase}
                    </span>
                    <span
                      className={cn(
                        "ml-auto font-medium",
                        allDone ? "text-green-600" : anyActive ? "text-blue-600" : "text-gray-400"
                      )}
                      style={{ fontSize: 13 }}
                    >
                      ({phaseGrps.length})
                    </span>
                  </div>

                  {/* 그룹 항목 */}
                  <div className="space-y-0.5">
                    {phaseGrps.map((g) => {
                      const isSelected = selectedGroupId === g.itemId && !resultTabActive;
                      return (
                        <button
                          key={g.itemId}
                          className={cn(
                            "flex w-full items-center gap-2.5 rounded-r-lg py-2 pl-4 pr-2 text-left transition-all duration-150 hover:bg-black/[0.04]",
                            isSelected && "border-l-[3px] border-l-blue-500 bg-blue-500/[0.06]",
                            !isSelected && "border-l-[3px] border-l-transparent"
                          )}
                          style={isSelected ? { paddingLeft: 13 } : undefined}
                          onClick={() => {
                            setSelectedGroupId(g.itemId);
                            setActivePhaseTab(g.phase);
                            setResultTabActive(false);
                            contentRef.current?.scrollTo({ top: 0, behavior: "smooth" });
                          }}
                        >
                          <SidebarStepIcon status={g.stepStatus} isSelected={isSelected} />
                          <span
                            className={cn(
                              "truncate flex-1",
                              g.stepStatus === "completed" ? "text-gray-500" :
                              isSelected ? "font-medium text-blue-700" :
                              g.stepStatus === "active" ? "text-gray-700" : "text-gray-400"
                            )}
                            style={{ fontSize: 14 }}
                          >
                            {g.title}
                          </span>
                          {g.total > 0 && (
                            <span
                              className={cn(
                                "ml-1 shrink-0 font-medium",
                                g.stepStatus === "completed" ? "text-green-500" :
                                isSelected ? "text-blue-500" :
                                g.stepStatus === "active" ? "text-gray-500" : "text-gray-300"
                              )}
                              style={{ fontSize: 11 }}
                            >
                              {g.completed}/{g.total}
                            </span>
                          )}
                        </button>
                      );
                    })}
                  </div>
                </div>
              );
            })}

            {/* 실적 그룹 */}
            {hasResultSections && (
              <div>
                <div className="mb-2 flex items-center gap-2 px-4">
                  <div className="flex h-5 w-5 items-center justify-center rounded-full bg-amber-500">
                    <ClipboardList className="h-3 w-3 text-white" strokeWidth={2.5} />
                  </div>
                  <span className="text-xs font-semibold uppercase tracking-wider text-gray-900">실적</span>
                  <span className="ml-auto text-xs font-medium text-amber-600">
                    {isProcessCompleted ? "확정" : "미확정"}
                  </span>
                </div>
                <div className="space-y-0.5">
                  <button
                    className={cn(
                      "flex w-full items-center gap-2.5 rounded-r-lg py-2 pl-4 pr-2 text-left transition-all duration-150 hover:bg-black/[0.04]",
                      resultTabActive && "border-l-[3px] border-l-blue-500 bg-blue-500/[0.06]",
                      !resultTabActive && "border-l-[3px] border-l-transparent"
                    )}
                    style={resultTabActive ? { paddingLeft: 13 } : undefined}
                    onClick={handleResultTabClick}
                  >
                    <AlertCircle className={cn("h-4 w-4 shrink-0", resultTabActive ? "text-blue-500" : "text-amber-500")} />
                    <span className={cn("text-sm", resultTabActive ? "font-medium text-blue-700" : "font-medium text-amber-700")}>실적 입력</span>
                  </button>
                </div>
              </div>
            )}
          </div>
        </div>

        {/* ===== 메인 콘텐츠 ===== */}
        <div className="flex flex-1 flex-col overflow-hidden" style={{ backgroundColor: DESIGN.bg.page }}>
          {/* 실적 입력 패널 (hidden으로 상태 유지) */}
          {hasResultSections && (
            <div className={cn("flex flex-1 flex-col overflow-hidden", !resultTabActive && "hidden")}>
              <ResultPanel
                workOrderProcessId={workOrderProcessId!}
                processData={processData}
                sections={cfg.resultSections ?? []}
                isProcessCompleted={isProcessCompleted}
                defectTypes={cachedDefectTypes}
                onSaved={(updated) => {
                  setProcessData((prev) => prev ? { ...prev, ...updated } : prev);
                  publish("process_completed", { workOrderProcessId, status: updated?.status });
                }}
              />
            </div>
          )}

          {/* 체크리스트 영역 */}
          <div className={cn("flex flex-1 flex-col overflow-hidden", resultTabActive && "hidden")}>
            {cfg.displayMode === "step" ? (
              /* ======== 스텝 모드 ======== */
              <>
                {showQuantityPanel || allItemsCompleted ? (
                  <div ref={contentRef} className="flex flex-1 flex-col items-center justify-center gap-5 overflow-y-auto p-6">
                    <CheckCircle2 className="h-12 w-12 text-green-600" />
                    <p className="text-base font-medium">모든 작업 항목이 완료되었습니다</p>
                    {cfg.showQuantityInput && !isProcessCompleted && !hasResultSections && (
                      <div className="w-full max-w-sm space-y-4 rounded-lg border p-5" style={{ backgroundColor: DESIGN.bg.card }}>
                        <p className="text-sm font-semibold uppercase tracking-wide text-gray-500">실적 수량 등록</p>
                        <div className="space-y-3">
                          <div className="flex items-center gap-3">
                            <span className="w-12 text-sm text-gray-500">양품</span>
                            <Input type="number" className="text-base" style={{ height: `${DESIGN.input.height}px` }} value={goodQty} onChange={(e) => setGoodQty(e.target.value)} placeholder="0" />
                          </div>
                          <div className="flex items-center gap-3">
                            <span className="w-12 text-sm text-gray-500">불량</span>
                            <Input type="number" className="text-base" style={{ height: `${DESIGN.input.height}px` }} value={defectQty} onChange={(e) => setDefectQty(e.target.value)} placeholder="0" />
                          </div>
                          {(parseInt(goodQty, 10) || 0) + (parseInt(defectQty, 10) || 0) > 0 && (
                            <p className="text-right text-sm text-gray-500">
                              합계: {(parseInt(goodQty, 10) || 0) + (parseInt(defectQty, 10) || 0)}
                            </p>
                          )}
                        </div>
                      </div>
                    )}
                    {isProcessCompleted && (
                      <Badge variant="outline" className="px-3 py-1 text-base text-green-600">
                        공정이 완료되었습니다
                      </Badge>
                    )}
                  </div>
                ) : (
                  <>
                    {/* 그룹 헤더 + 타이머 + 진행률 */}
                    {selectedGroup && (
                      <>
                        <GroupTimerHeader
                          group={selectedGroup}
                          cfg={cfg}
                          isProcessCompleted={isProcessCompleted}
                          isGroupStarted={isGroupStarted}
                          isGroupPaused={isGroupPaused}
                          isGroupCompleted={isGroupCompleted}
                          groupTimerFormatted={groupTimerFormatted}
                          groupElapsedFormatted={groupElapsedFormatted}
                          onTimerAction={handleGroupTimerAction}
                        />
                        <div className="px-4 pb-2" style={{ backgroundColor: DESIGN.bg.card }}>
                          <div className="flex items-center justify-between text-xs text-gray-500">
                            <span>{currentItemIdx + 1} / {currentItems.length}</span>
                          </div>
                          <div className="mt-1 h-2 w-full overflow-hidden rounded-full bg-gray-200">
                            <div
                              className="h-full bg-blue-500 transition-all duration-300"
                              style={{
                                width: `${currentItems.length > 0 && selectedGroup.total > 0 ? ((selectedGroup.completed / selectedGroup.total) * 100) : 0}%`,
                              }}
                            />
                          </div>
                        </div>
                      </>
                    )}

                    {/* 현재 항목 1개 표시 */}
                    <div ref={contentRef} className="flex flex-1 flex-col overflow-y-auto p-5">
                      {currentItems[currentItemIdx] && (
                        <div className="mx-auto w-full max-w-lg space-y-3">
                          {currentItems[currentItemIdx].started_at && (
                            <div className="flex items-center justify-end gap-1.5 text-xs text-gray-500">
                              <Timer className="h-3.5 w-3.5" />
                              {currentItems[currentItemIdx].recorded_at
                                ? formatDuration(currentItems[currentItemIdx].started_at!, currentItems[currentItemIdx].recorded_at!)
                                : "진행 중..."}
                            </div>
                          )}
                          <ChecklistItem
                            item={currentItems[currentItemIdx]}
                            saving={savingIds.has(currentItems[currentItemIdx].id)}
                            disabled={isProcessCompleted || isStepLocked}
                            onSave={saveResultValue}
                          />
                        </div>
                      )}
                    </div>

                    {/* 스텝 네비게이션 */}
                    <div className="flex items-center justify-between border-t border-gray-100 bg-white px-4 py-3">
                      <Button
                        size="sm"
                        variant="outline"
                        className="gap-1.5 px-5"
                        style={{ height: `${DESIGN.nav.height}px` }}
                        disabled={currentItemIdx <= 0 && selectedIndex <= 0}
                        onClick={() => {
                          if (currentItemIdx > 0) {
                            setCurrentItemIdx(currentItemIdx - 1);
                          } else if (selectedIndex > 0) {
                            const prevGroup = groups[selectedIndex - 1];
                            setSelectedGroupId(prevGroup.itemId);
                            setActivePhaseTab(prevGroup.phase);
                            const prevItems = allResults.filter(
                              (r) => r.source_work_item_id === prevGroup.itemId
                            );
                            setCurrentItemIdx(Math.max(0, prevItems.length - 1));
                          }
                        }}
                      >
                        <ChevronLeft className="h-4 w-4" />이전
                      </Button>

                      <span className="text-xs text-gray-500">
                        {selectedGroup?.title} ({currentItemIdx + 1}/{currentItems.length})
                      </span>

                      <Button
                        size="sm"
                        variant={currentItemIdx >= currentItems.length - 1 && selectedIndex >= groups.length - 1 ? "default" : "outline"}
                        className="gap-1.5 px-5"
                        style={{ height: `${DESIGN.nav.height}px` }}
                        onClick={() => {
                          if (currentItemIdx < currentItems.length - 1) {
                            setCurrentItemIdx(currentItemIdx + 1);
                          } else if (selectedIndex < groups.length - 1) {
                            const nextGroup = groups[selectedIndex + 1];
                            setActivePhaseTab(nextGroup.phase);
                            navigateStep(1);
                          } else {
                            setShowQuantityPanel(true);
                          }
                        }}
                      >
                        {currentItemIdx >= currentItems.length - 1 && selectedIndex >= groups.length - 1 ? (
                          <>수량 등록<ChevronRight className="h-4 w-4" /></>
                        ) : (
                          <>다음<ChevronRight className="h-4 w-4" /></>
                        )}
                      </Button>
                    </div>
                  </>
                )}
              </>
            ) : (
              /* ======== 리스트 모드 ======== */
              <>
                {/* 그룹 헤더 + 타이머 (콘텐츠 최상단 고정) */}
                {selectedGroup && (
                  <GroupTimerHeader
                    group={selectedGroup}
                    cfg={cfg}
                    isProcessCompleted={isProcessCompleted}
                    isGroupStarted={isGroupStarted}
                    isGroupPaused={isGroupPaused}
                    isGroupCompleted={isGroupCompleted}
                    groupTimerFormatted={groupTimerFormatted}
                    groupElapsedFormatted={groupElapsedFormatted}
                    onTimerAction={handleGroupTimerAction}
                  />
                )}
              <div ref={contentRef} className="flex-1 overflow-y-auto" style={{ scrollbarWidth: 'thin' }}>
                {/* KPI 카드 (콘텐츠와 함께 스크롤) */}
                <KpiCards
                  inputQty={parseInt(processData?.input_qty ?? "0", 10) || 0}
                  completedQty={parseInt(processData?.total_production_qty ?? "0", 10) || 0}
                  defectQty={parseInt(processData?.defect_qty ?? "0", 10) || 0}
                />

                {/* 체크리스트 콘텐츠 */}
                <div className="px-4 py-3" style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                  {selectedGroupId && (
                    <>
                      {currentItems.map((item) => (
                        <ChecklistRowItem
                          key={item.id}
                          item={item}
                          saving={savingIds.has(item.id)}
                          disabled={isProcessCompleted || isStepLocked}
                          onSave={saveResultValue}
                        />
                      ))}

                      {/* 각 그룹 하단: 작업완료 버튼 */}
                      {!isProcessCompleted && selectedGroup && (
                        <div className="mt-3 mb-2">
                          <GroupCompleteButton
                            group={selectedGroup}
                            currentItems={currentItems}
                            isGroupStarted={isGroupStarted}
                            isGroupCompleted={isGroupCompleted}
                            onComplete={async () => {
                              await handleGroupTimerAction("complete");
                              // 완료 후 다음 그룹으로 자동 이동
                              const idx = groups.findIndex((g) => g.itemId === selectedGroupId);
                              if (idx >= 0 && idx < groups.length - 1) {
                                const nextGroup = groups[idx + 1];
                                setSelectedGroupId(nextGroup.itemId);
                                setActivePhaseTab(nextGroup.phase);
                                contentRef.current?.scrollTo({ top: 0, behavior: "smooth" });
                              }
                            }}
                            onNavigateNext={() => {
                              // 필수 미완료 시 넘어가지 않음
                              const requiredItems = currentItems.filter((r) => r.is_required === "Y");
                              const allRequiredDone = requiredItems.length === 0 || requiredItems.every((r) => r.status === "completed");
                              if (!allRequiredDone) {
                                toast.error("필수 항목을 모두 완료해주세요.");
                                return;
                              }
                              // 다음 그룹으로 이동
                              const idx = groups.findIndex((g) => g.itemId === selectedGroupId);
                              if (idx >= 0 && idx < groups.length - 1) {
                                const nextGroup = groups[idx + 1];
                                setSelectedGroupId(nextGroup.itemId);
                                setActivePhaseTab(nextGroup.phase);
                                contentRef.current?.scrollTo({ top: 0, behavior: "smooth" });
                              }
                            }}
                          />
                        </div>
                      )}
                    </>
                  )}
                </div>
              </div>
              </>
            )}
          </div>
        </div>
      </div>

      {/* 풋터: 공정 완료 상태 표시만 (3버튼 제거 → 각 그룹 하단에 작업완료 배치) */}
      {isProcessCompleted && (
        <div
          className="flex shrink-0 items-center justify-center border-t border-gray-100 bg-white"
          style={{ height: `${DESIGN.footer.height}px` }}
        >
          <Badge variant="outline" className="px-4 py-1.5 text-base text-green-600">
            <CheckCircle2 className="mr-2 h-5 w-5" />
            공정이 완료되었습니다
          </Badge>
        </div>
      )}
    </div>
  );
}

// ========================================
// 실적 입력 패널 (분할 실적 누적 방식)
// ========================================

const DISPOSITION_OPTIONS = [
  { value: "scrap", label: "폐기" },
  { value: "rework", label: "재작업" },
  { value: "accept", label: "특채" },
];

interface BatchHistoryItem {
  seq: number;
  batch_qty: number;
  batch_good: number;
  batch_defect: number;
  accumulated_total: number;
  changed_at: string;
  changed_by: string | null;
}

const IMPLEMENTED_SECTIONS = new Set<ResultSectionType>(["total-qty", "good-defect", "defect-types", "note", "plc-data", "material-input"]);

const SECTION_LABELS: Record<ResultSectionType, string> = {
  "total-qty": "생산수량",
  "good-defect": "양품/불량",
  "defect-types": "불량 유형 상세",
  "note": "비고",
  "box-packing": "박스 포장",
  "label-print": "라벨 출력",
  "photo": "사진",
  "document": "문서",
  "material-input": "자재 투입",
  "barcode-scan": "바코드 스캔",
  "plc-data": "PLC 데이터",
};

interface ResultPanelProps {
  workOrderProcessId: string;
  processData: ProcessTimerData | null;
  sections: ResultSectionConfig[];
  isProcessCompleted: boolean;
  defectTypes: DefectTypeOption[];
  onSaved: (updated: Partial<ProcessTimerData>) => void;
}

function ResultPanel({
  workOrderProcessId,
  processData,
  sections,
  isProcessCompleted,
  defectTypes,
  onSaved,
}: ResultPanelProps) {
  // 이번 차수 입력값 (누적치가 아닌 이번에 생산한 수량)
  const [batchQty, setBatchQty] = useState("");
  const [batchDefect, setBatchDefect] = useState("");
  const [resultNote, setResultNote] = useState("");
  const [defectEntries, setDefectEntries] = useState<DefectDetailEntry[]>([]);
  const [saving, setSaving] = useState(false);
  const [confirming, setConfirming] = useState(false);
  const [history, setHistory] = useState<BatchHistoryItem[]>([]);
  const [historyLoading, setHistoryLoading] = useState(false);
  const [availableInfo, setAvailableInfo] = useState<{
    prevGoodQty: number;
    availableQty: number;
    instructionQty: number;
  } | null>(null);

  const isConfirmed = processData?.result_status === "confirmed";

  const inputQty = parseInt(processData?.input_qty ?? "0", 10) || 0;
  const accumulatedTotal = parseInt(processData?.total_production_qty ?? "0", 10) || 0;
  const accumulatedGood = parseInt(processData?.good_qty ?? "0", 10) || 0;
  const accumulatedDefect = parseInt(processData?.defect_qty ?? "0", 10) || 0;
  const remainingQty = Math.max(0, inputQty - accumulatedTotal);

  const batchGood = useMemo(() => {
    const production = parseInt(batchQty, 10) || 0;
    const defect = parseInt(batchDefect, 10) || 0;
    return Math.max(0, production - defect);
  }, [batchQty, batchDefect]);

  const totalDefectFromEntries = useMemo(
    () => defectEntries.reduce((sum, e) => sum + (parseInt(e.qty, 10) || 0), 0),
    [defectEntries]
  );

  useEffect(() => {
    if (totalDefectFromEntries > 0) {
      setBatchDefect(String(totalDefectFromEntries));
    }
  }, [totalDefectFromEntries]);

  const enabledSections = sections.filter((s) => s.enabled);

  // 접수가능량 로드
  const loadAvailableQty = useCallback(async () => {
    try {
      const res = await apiClient.get("/pop/production/available-qty", {
        params: { work_order_process_id: workOrderProcessId },
      });
      if (res.data?.success) {
        setAvailableInfo(res.data.data);
      }
    } catch { /* ignore */ }
  }, [workOrderProcessId]);

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

  // 이력 로드
  const loadHistory = useCallback(async () => {
    setHistoryLoading(true);
    try {
      const res = await apiClient.get("/pop/production/result-history", {
        params: { work_order_process_id: workOrderProcessId },
      });
      if (res.data?.success) {
        setHistory(res.data.data || []);
      }
    } catch { /* 실패 시 빈 배열 유지 */ }
    finally { setHistoryLoading(false); }
  }, [workOrderProcessId]);

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

  const addDefectEntry = () => {
    setDefectEntries((prev) => [
      ...prev,
      { defect_code: "", defect_name: "", qty: "", disposition: "scrap" },
    ]);
  };

  const removeDefectEntry = (idx: number) => {
    setDefectEntries((prev) => prev.filter((_, i) => i !== idx));
  };

  const updateDefectEntry = (idx: number, field: keyof DefectDetailEntry, value: string) => {
    setDefectEntries((prev) =>
      prev.map((e, i) => {
        if (i !== idx) return e;
        const updated = { ...e, [field]: value };
        if (field === "defect_code") {
          const found = defectTypes.find((dt) => dt.defect_code === value);
          if (found) updated.defect_name = found.defect_name;
        }
        return updated;
      })
    );
  };

  const resetForm = () => {
    setBatchQty("");
    setBatchDefect("");
    setResultNote("");
    setDefectEntries([]);
  };

  const handleSubmitBatch = async () => {
    if (!batchQty || parseInt(batchQty, 10) <= 0) {
      toast.error("생산수량을 입력해주세요.");
      return;
    }
    const batchNum = parseInt(batchQty, 10);
    if (inputQty > 0 && (accumulatedTotal + batchNum) > inputQty) {
      toast.error(`생산수량(${batchNum})이 잔여량(${remainingQty})을 초과합니다.`);
      return;
    }
    setSaving(true);
    try {
      const res = await apiClient.post("/pop/production/save-result", {
        work_order_process_id: workOrderProcessId,
        production_qty: batchQty,
        good_qty: String(batchGood),
        defect_qty: batchDefect || "0",
        defect_detail: defectEntries.length > 0 ? defectEntries : null,
        result_note: resultNote || null,
      });
      if (res.data?.success) {
        const savedData = res.data.data;
        if (savedData?.status === "completed") {
          toast.success("모든 수량이 완료되어 자동 확정되었습니다.");
        } else {
          toast.success(`${batchQty}개 실적이 등록되었습니다.`);
        }
        onSaved(savedData);
        resetForm();
        loadHistory();
        loadAvailableQty();
      } else {
        toast.error(res.data?.message || "실적 등록에 실패했습니다.");
      }
    } catch {
      toast.error("실적 등록 중 오류가 발생했습니다.");
    } finally {
      setSaving(false);
    }
  };

  const handleConfirm = async () => {
    if (accumulatedTotal <= 0) {
      toast.error("등록된 실적이 없습니다.");
      return;
    }
    setConfirming(true);
    try {
      const res = await apiClient.post("/pop/production/confirm-result", {
        work_order_process_id: workOrderProcessId,
      });
      if (res.data?.success) {
        toast.success("실적이 확정되었습니다.");
        onSaved({ ...res.data.data, result_status: "confirmed" });
      } else {
        toast.error(res.data?.message || "실적 확정에 실패했습니다.");
      }
    } catch {
      toast.error("실적 확정 중 오류가 발생했습니다.");
    } finally {
      setConfirming(false);
    }
  };

  return (
    <div className="flex flex-1 flex-col overflow-y-auto" style={{ scrollbarWidth: 'thin', backgroundColor: DESIGN.bg.page }}>
      {/* KPI 카드 (실적 탭에서도 표시) */}
      <KpiCards
        inputQty={inputQty}
        completedQty={accumulatedTotal}
        defectQty={accumulatedDefect}
      />

      <div className="space-y-8 px-8 py-6">
        {/* 확정 상태 배너 */}
        {isConfirmed && (
          <div className="flex items-center gap-2 rounded-xl border border-green-200 bg-green-50 px-4 py-2.5">
            <FileCheck className="h-4 w-4 text-green-600" />
            <span className="text-sm font-medium text-green-700">실적이 확정되었습니다</span>
          </div>
        )}

        {/* 이번 차수 실적 입력 */}
        {!isConfirmed && (
          <div>
            <div className="mb-4 text-xs font-semibold uppercase tracking-widest text-gray-400">이번 차수 실적 입력</div>

            {/* 생산수량 */}
            {enabledSections.some((s) => s.type === "total-qty") && (
              <div className="mb-5 flex items-end gap-6">
                <div>
                  <label className="mb-1.5 block text-sm font-medium text-gray-600">생산수량</label>
                  <div className="flex items-center gap-2">
                    <input
                      type="number"
                      className="h-14 w-36 rounded-xl border-2 border-gray-200 px-4 text-center text-2xl font-bold text-gray-900 transition-colors focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-100"
                      value={batchQty}
                      onChange={(e) => setBatchQty(e.target.value)}
                      placeholder="0"
                    />
                    <span className="text-sm text-gray-400">EA</span>
                  </div>
                </div>
                {enabledSections.some((s) => s.type === "good-defect") && (
                  <>
                    <div>
                      <label className="mb-1.5 block text-sm font-medium text-gray-600">
                        양품 <span className="ml-1 rounded bg-blue-50 px-1.5 py-0.5 text-xs text-blue-500">자동</span>
                      </label>
                      <input
                        type="number"
                        className="h-14 w-28 cursor-default rounded-xl border border-gray-200 bg-gray-50 px-3 text-center text-2xl font-bold text-green-600"
                        value={batchGood > 0 ? String(batchGood) : ""}
                        readOnly
                      />
                    </div>
                    <div>
                      <label className="mb-1.5 block text-sm font-medium text-gray-600">불량</label>
                      <input
                        type="number"
                        className="h-14 w-28 rounded-xl border-2 border-red-200 px-3 text-center text-2xl font-bold text-red-600 transition-colors focus:border-red-400 focus:outline-none focus:ring-2 focus:ring-red-100"
                        value={batchDefect}
                        onChange={(e) => setBatchDefect(e.target.value)}
                        placeholder="0"
                      />
                    </div>
                    {(parseInt(batchQty, 10) || 0) > 0 && (
                      <div className="pb-3">
                        <p className="text-xs text-gray-400">양품 {batchGood} = 생산 {batchQty} - 불량 {batchDefect || 0}</p>
                      </div>
                    )}
                  </>
                )}
              </div>
            )}
          </div>
        )}

        {/* 불량 유형 상세 */}
        {!isConfirmed && enabledSections.some((s) => s.type === "defect-types") && (
          <div>
            <div className="mb-4 flex items-center justify-between">
              <div className="text-xs font-semibold uppercase tracking-widest text-gray-400">불량 유형 상세</div>
              <div className="flex items-center gap-3">
                {totalDefectFromEntries > 0 && (
                  <span className="text-sm font-semibold text-red-500">합계: {totalDefectFromEntries}개</span>
                )}
                <button
                  className="flex h-9 items-center gap-1.5 rounded-lg border-2 border-dashed border-gray-300 px-4 text-xs font-medium text-gray-500 transition-colors hover:border-blue-400 hover:text-blue-600"
                  onClick={addDefectEntry}
                >
                  <Plus className="h-3.5 w-3.5" />
                  유형 추가
                </button>
              </div>
            </div>
            {defectEntries.length === 0 ? (
              <p className="text-sm text-gray-400">등록된 불량 유형이 없습니다.</p>
            ) : (
              <div className="space-y-3">
                {defectEntries.map((entry, idx) => {
                  const dt = defectTypes.find((d) => d.defect_code === entry.defect_code);
                  return (
                    <div key={idx} className="rounded-xl border border-gray-200 bg-white p-4">
                      <div className="flex items-center gap-3">
                        <div className="flex-1">
                          <select
                            className="h-11 w-full rounded-lg border border-gray-200 bg-white px-3 text-sm font-medium focus:border-blue-400 focus:outline-none"
                            value={entry.defect_code}
                            onChange={(e) => updateDefectEntry(idx, "defect_code", e.target.value)}
                          >
                            <option value="">불량유형 선택</option>
                            {defectTypes.map((d) => (
                              <option key={d.defect_code} value={d.defect_code}>
                                {d.defect_name} ({d.defect_code})
                              </option>
                            ))}
                          </select>
                        </div>
                        <div className="w-24">
                          <input
                            type="number"
                            className="h-11 w-full rounded-lg border-2 border-red-200 bg-white px-3 text-center text-lg font-bold text-red-600 focus:outline-none focus:ring-2 focus:ring-red-100"
                            value={entry.qty}
                            onChange={(e) => updateDefectEntry(idx, "qty", e.target.value)}
                            placeholder="수량"
                          />
                        </div>
                        <div className="w-28">
                          <select
                            className="h-11 w-full rounded-lg border border-gray-200 bg-white px-3 text-sm font-medium"
                            value={entry.disposition}
                            onChange={(e) => updateDefectEntry(idx, "disposition", e.target.value)}
                          >
                            {DISPOSITION_OPTIONS.map((opt) => (
                              <option key={opt.value} value={opt.value}>{opt.label}</option>
                            ))}
                          </select>
                        </div>
                        <button
                          className="flex h-11 w-11 items-center justify-center rounded-lg transition-colors hover:bg-red-50"
                          onClick={() => removeDefectEntry(idx)}
                        >
                          <Trash2 className="h-4 w-4 text-gray-400 hover:text-red-500" />
                        </button>
                      </div>
                      {dt && (
                        <div className="mt-2 flex items-center gap-2 text-xs">
                          <span className={cn(
                            "rounded px-2 py-0.5 font-medium",
                            dt.severity === "critical" || dt.severity === "심각" ? "bg-red-100 text-red-600" : "bg-amber-100 text-amber-600"
                          )}>
                            {dt.defect_type}
                          </span>
                          <span className="text-gray-400">심각도: {dt.severity}</span>
                        </div>
                      )}
                    </div>
                  );
                })}

                {/* 불량 합계 */}
                {defectEntries.length > 0 && (
                  <div className="flex items-center justify-between rounded-lg bg-gray-50 px-4 py-2.5">
                    <span className="text-xs font-medium text-gray-500">불량 유형 합계</span>
                    <div className="flex items-center gap-4 text-xs text-gray-500">
                      {DISPOSITION_OPTIONS.map((opt) => {
                        const cnt = defectEntries.reduce(
                          (s, e) => s + (e.disposition === opt.value ? (parseInt(e.qty, 10) || 0) : 0), 0
                        );
                        return <span key={opt.value}>{opt.label}: {cnt}</span>;
                      })}
                      <span className="text-sm font-bold text-red-600">총 {totalDefectFromEntries}개</span>
                    </div>
                  </div>
                )}
              </div>
            )}
          </div>
        )}

        {/* 비고 */}
        {!isConfirmed && enabledSections.some((s) => s.type === "note") && (
          <div>
            <div className="mb-3 text-xs font-semibold uppercase tracking-widest text-gray-400">비고</div>
            <textarea
              className="h-16 w-full resize-none rounded-xl border border-gray-200 px-4 py-3 text-sm transition-colors focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-100"
              value={resultNote}
              onChange={(e) => setResultNote(e.target.value)}
              placeholder="작업 내용, 특이사항 등"
            />
          </div>
        )}

        {/* PLC 데이터 섹션 */}
        {enabledSections
          .filter((s) => s.type === "plc-data")
          .map((s) => (
            <PlcDataSection
              key={s.id}
              config={s.plcConfig}
              workOrderProcessId={workOrderProcessId}
            />
          ))}

        {/* 자재 투입 섹션 */}
        {enabledSections.some((s) => s.type === "material-input") && (
          <MaterialInputSection
            workOrderProcessId={workOrderProcessId}
            isConfirmed={isConfirmed}
          />
        )}

        {/* 미구현 섹션 플레이스홀더 (순서 보존) */}
        {!isConfirmed && enabledSections
          .filter((s) => !IMPLEMENTED_SECTIONS.has(s.type))
          .map((s) => (
            <div key={s.id} className="flex items-center gap-3 rounded-xl border border-dashed border-gray-200 p-4">
              <Construction className="h-5 w-5 shrink-0 text-gray-400" />
              <div>
                <p className="text-sm font-medium text-gray-700">{SECTION_LABELS[s.type] ?? s.type}</p>
                <p className="text-xs text-gray-400">준비 중인 기능입니다</p>
              </div>
            </div>
          ))}

        {/* 등록 버튼 */}
        {!isConfirmed && (
          <div className="flex items-center gap-3">
            <button
              className={cn(
                "flex h-12 items-center gap-2 rounded-xl bg-gray-900 px-8 text-sm font-semibold text-white transition-colors hover:bg-gray-800",
                (saving || !batchQty) && "cursor-not-allowed opacity-50"
              )}
              onClick={handleSubmitBatch}
              disabled={saving || !batchQty}
            >
              {saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Plus className="h-4 w-4" />}
              실적 등록 {batchQty ? `(${batchQty}개)` : ""}
            </button>
          </div>
        )}

        {/* 등록 이력 (테이블 형태 - combined-final) */}
        <div>
          <div className="mb-4 text-xs font-semibold uppercase tracking-widest text-gray-400">등록 이력</div>
          {historyLoading ? (
            <div className="flex items-center gap-2 py-4 text-sm text-gray-400">
              <Loader2 className="h-4 w-4 animate-spin" />불러오는 중...
            </div>
          ) : history.length === 0 ? (
            <p className="py-4 text-sm text-gray-400">등록된 실적이 없습니다.</p>
          ) : (
            <table className="w-full text-sm">
              <thead>
                <tr className="text-xs uppercase tracking-wider text-gray-400">
                  <th className="px-3 py-2 text-left font-medium">차수</th>
                  <th className="px-3 py-2 text-right font-medium">생산수량</th>
                  <th className="px-3 py-2 text-right font-medium">양품</th>
                  <th className="px-3 py-2 text-right font-medium">불량</th>
                  <th className="px-3 py-2 text-right font-medium">누적</th>
                  <th className="px-3 py-2 text-right font-medium">시각</th>
                </tr>
              </thead>
              <tbody>
                {[...history].reverse().map((h, i) => (
                  <tr
                    key={h.seq}
                    className="border-t border-gray-100"
                    style={i % 2 === 1 ? { backgroundColor: '#FAFBFC' } : undefined}
                  >
                    <td className="px-3 py-3">
                      <span className={cn(
                        "rounded px-2 py-0.5 text-xs font-bold",
                        i === 0 ? "bg-blue-100 text-blue-700" : "bg-gray-100 text-gray-600"
                      )}>
                        #{h.seq}
                      </span>
                    </td>
                    <td className="px-3 py-3 text-right font-semibold text-gray-900">+{h.batch_qty}</td>
                    <td className="px-3 py-3 text-right font-medium text-green-600">+{h.batch_good}</td>
                    <td className="px-3 py-3 text-right font-medium text-red-600">+{h.batch_defect}</td>
                    <td className="px-3 py-3 text-right text-gray-500">{h.accumulated_total}</td>
                    <td className="px-3 py-3 text-right text-gray-400">
                      {new Date(h.changed_at).toLocaleTimeString("ko-KR", { hour: "2-digit", minute: "2-digit" })}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
        </div>

        {/* 여백 */}
        <div className="h-2" />
      </div>

      {/* 실적 확정 버튼 제거됨 - 자동 완료로 대체 (2026-03-17 결정) */}
    </div>
  );
}

// ========================================
// PLC 데이터 섹션 (런타임 표시)
// ========================================

interface PlcDataSectionProps {
  config?: PlcDataConfig;
  workOrderProcessId: string;
}

function PlcDataSection({ config, workOrderProcessId }: PlcDataSectionProps) {
  const [value, setValue] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
  const [manualMode, setManualMode] = useState(false);
  const [manualValue, setManualValue] = useState("");
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);

  const isConfigured =
    config &&
    config.connectionId &&
    config.tableName &&
    config.valueColumn &&
    config.deviceColumn;

  const fetchPlcValue = useCallback(async () => {
    if (!isConfigured || !config) return;

    setLoading(true);
    setError(null);

    try {
      // 쿼리: 디바이스/태그 필터 기반 최신 값 조회
      let query = `SELECT "${config.valueColumn}"`;
      if (config.timestampColumn) {
        query += `, "${config.timestampColumn}"`;
      }
      query += ` FROM "${config.tableName}" WHERE 1=1`;

      if (config.deviceFilter) {
        query += ` AND "${config.deviceColumn}" = '${config.deviceFilter}'`;
      }
      if (config.tagFilter) {
        // tag_name 컬럼 추정 - deviceColumn 외 첫 텍스트 컬럼
        // 실제로는 설정에서 명시적 tagColumn이 있으면 좋겠지만,
        // 현재는 tag_name이 데이터에 있다고 가정
        query += ` AND tag_name = '${config.tagFilter}'`;
      }

      if (config.timestampColumn) {
        query += ` ORDER BY "${config.timestampColumn}" DESC`;
      }
      query += ` LIMIT 1`;

      const res = await ExternalDbConnectionAPI.executeQuery(Number(config.connectionId), query);

      if (res.success && res.data && res.data.length > 0) {
        const row = res.data[0] as Record<string, unknown>;
        const rawValue = row[config.valueColumn];
        setValue(rawValue != null ? String(rawValue) : null);
        setLastUpdated(new Date());
      } else {
        setValue(null);
        setError("데이터 없음");
      }
    } catch (err) {
      setError("조회 실패");
      console.error("PLC 데이터 조회 오류:", err);
    } finally {
      setLoading(false);
    }
  }, [config, isConfigured]);

  // 초기 로드 + 폴링
  useEffect(() => {
    if (!isConfigured || manualMode) return;

    fetchPlcValue();

    const interval = (config?.refreshInterval ?? 30) * 1000;
    intervalRef.current = setInterval(fetchPlcValue, interval);

    return () => {
      if (intervalRef.current) clearInterval(intervalRef.current);
    };
  }, [fetchPlcValue, isConfigured, manualMode, config?.refreshInterval]);

  // 매핑 저장 (값 변경 시)
  useEffect(() => {
    if (!config?.mapping?.enabled || !value || !workOrderProcessId) return;
    if (!config.mapping.targetTable || !config.mapping.targetColumn) return;

    // 매핑 저장은 백엔드 API를 통해서 수행 (데이터 쓰기 규칙)
    const saveMapping = async () => {
      try {
        await apiClient.post("/pop/production/plc-mapping", {
          work_order_process_id: workOrderProcessId,
          target_table: config.mapping!.targetTable,
          target_column: config.mapping!.targetColumn,
          value,
          mode: config.mapping!.mode,
        });
      } catch {
        // 매핑 실패는 무시 (PLC 표시에는 영향 없음)
      }
    };
    saveMapping();
  }, [value, config?.mapping, workOrderProcessId]);

  const displayLabel = config?.label || "PLC 데이터";
  const displayUnit = config?.unit || "";

  // 미설정 상태
  if (!isConfigured) {
    return (
      <div className="rounded-xl border-2 border-dashed border-blue-200 bg-blue-50/50 p-5">
        <div className="flex items-center gap-2 text-blue-600">
          <Zap className="h-5 w-5" />
          <span className="text-sm font-semibold">PLC 데이터 - 연결 필요</span>
        </div>
        <p className="mt-2 text-xs text-blue-500">
          설정 패널에서 외부 DB 연결 및 테이블/컬럼을 설정해주세요.
        </p>
      </div>
    );
  }

  // 수동 모드
  if (manualMode) {
    return (
      <div className="rounded-xl border-2 border-gray-200 bg-white p-5">
        <div className="mb-4 flex items-center justify-between">
          <div className="flex items-center gap-2">
            <h3 className="font-semibold text-gray-800">{displayLabel}</h3>
            <span className="rounded-full bg-gray-100 px-2 py-0.5 text-[10px] font-medium text-gray-500">
              수동 입력
            </span>
          </div>
          {displayUnit && (
            <span className="text-sm font-medium text-gray-500">{displayUnit}</span>
          )}
        </div>
        <div className="flex items-center gap-2">
          <input
            type="number"
            className="h-14 flex-1 rounded-xl border-2 border-gray-200 px-4 text-center text-3xl font-bold text-gray-900 transition-colors focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-100"
            value={manualValue}
            onChange={(e) => setManualValue(e.target.value)}
            placeholder="0"
          />
        </div>
        <div className="mt-3 flex justify-end">
          <button
            className="rounded-lg bg-blue-50 px-3 py-1.5 text-xs font-medium text-blue-600 transition-colors hover:bg-blue-100"
            onClick={() => setManualMode(false)}
          >
            PLC 자동 전환
          </button>
        </div>
      </div>
    );
  }

  // 자동(PLC) 모드
  const formattedValue = value != null ? Number(value).toLocaleString() : "--";

  return (
    <div className="rounded-xl border-2 border-blue-200 bg-white p-5">
      <div className="mb-4 flex items-center justify-between">
        <div className="flex items-center gap-2">
          {error ? (
            <div className="h-2 w-2 rounded-full bg-red-500" />
          ) : (
            <div className="h-2 w-2 animate-pulse rounded-full bg-green-500" />
          )}
          <h3 className="font-semibold text-gray-800">{displayLabel}</h3>
          <span className="rounded-full bg-blue-100 px-2 py-0.5 text-[10px] font-medium text-blue-600">
            PLC 자동
          </span>
        </div>
        {displayUnit && (
          <span className="text-sm font-medium text-gray-500">{displayUnit}</span>
        )}
      </div>

      {/* 큰 숫자 표시 */}
      {config.displayMode === "gauge" ? (
        <div className="mb-3 rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-6">
          <div className="text-center">
            <div className="text-5xl font-bold tracking-wider text-blue-700" style={{ fontVariantNumeric: "tabular-nums" }}>
              {loading && !value ? (
                <Loader2 className="mx-auto h-10 w-10 animate-spin text-blue-400" />
              ) : (
                formattedValue
              )}
            </div>
            {/* 간단한 게이지 바 */}
            <div className="mx-auto mt-4 h-3 w-full max-w-xs overflow-hidden rounded-full bg-gray-200">
              <div
                className="h-full rounded-full bg-gradient-to-r from-blue-400 to-blue-600 transition-all duration-500"
                style={{ width: value ? `${Math.min(100, (Number(value) / 1000) * 100)}%` : "0%" }}
              />
            </div>
          </div>
        </div>
      ) : (
        <div className="mb-3 rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-6">
          <div className="text-center text-5xl font-bold tracking-wider text-blue-700" style={{ fontVariantNumeric: "tabular-nums" }}>
            {loading && !value ? (
              <Loader2 className="mx-auto h-10 w-10 animate-spin text-blue-400" />
            ) : (
              formattedValue
            )}
          </div>
        </div>
      )}

      {/* 실시간 상태 */}
      <div className="flex items-center justify-between text-xs">
        <div className={cn("flex items-center gap-1.5", error ? "text-red-500" : "text-green-600")}>
          {error ? (
            <>
              <AlertCircle className="h-3.5 w-3.5" />
              {error}
            </>
          ) : (
            <>
              <RefreshCw className="h-3.5 w-3.5 animate-spin" style={{ animationDuration: "3s" }} />
              실시간 수신 중 ({config.refreshInterval}초 간격)
            </>
          )}
        </div>
        <span className="text-gray-400">
          {config.deviceFilter && `${config.deviceFilter}`}
          {config.tagFilter && ` / ${config.tagFilter}`}
        </span>
      </div>

      {/* 수동 입력 폴백 */}
      <div className="mt-3 border-t border-gray-100 pt-3">
        <div className="flex items-center gap-2">
          <input
            type="number"
            className="h-9 flex-1 rounded-lg border px-3 text-sm text-gray-400"
            placeholder="수동입력 (fallback)"
            value={manualValue}
            onChange={(e) => setManualValue(e.target.value)}
            disabled
          />
          <button
            className="h-9 rounded-lg bg-gray-100 px-3 text-xs text-gray-500 transition-colors hover:bg-gray-200"
            onClick={() => setManualMode(true)}
          >
            수동 전환
          </button>
        </div>
      </div>
    </div>
  );
}

// ========================================
// 유틸리티
// ========================================

function formatSeconds(totalSec: number): string {
  const h = String(Math.floor(totalSec / 3600)).padStart(2, "0");
  const m = String(Math.floor((totalSec % 3600) / 60)).padStart(2, "0");
  const s = String(totalSec % 60).padStart(2, "0");
  return `${h}:${m}:${s}`;
}

function formatMsToTime(ms: number): string {
  return formatSeconds(Math.floor(ms / 1000));
}

function formatDuration(startISO: string, endISO: string): string {
  const diffMs = new Date(endISO).getTime() - new Date(startISO).getTime();
  if (diffMs <= 0) return "0초";
  const totalSec = Math.floor(diffMs / 1000);
  if (totalSec < 60) return `${totalSec}초`;
  const min = Math.floor(totalSec / 60);
  const sec = totalSec % 60;
  return sec > 0 ? `${min}분 ${sec}초` : `${min}분`;
}

// ========================================
// 작업지시 정보 바
// ========================================

interface InfoBarProps {
  fields: Array<{ label: string; column: string }>;
  parentRow: RowData;
  processName: string;
}

function InfoBar({ fields, parentRow, processName }: InfoBarProps) {
  return (
    <div
      className="flex shrink-0 items-center gap-8 px-6"
      style={{
        backgroundColor: DESIGN.bg.infoBar,
        paddingTop: 10,
        paddingBottom: 10,
      }}
    >
      {fields.map((f) => {
        const val = f.column === "__process_name"
          ? processName
          : parentRow[f.column];
        return (
          <div key={f.column} className="flex items-center gap-2">
            <span style={{ color: 'rgba(255,255,255,0.4)', fontSize: `${DESIGN.infoBar.labelSize}px` }}>{f.label}</span>
            <span className="font-medium text-white" style={{ fontSize: `${DESIGN.infoBar.valueSize}px` }}>{val != null ? String(val) : "-"}</span>
          </div>
        );
      })}
    </div>
  );
}

// ========================================
// 자재 투입 섹션
// ========================================

interface BomMaterial {
  id: string;
  child_item_id: string;
  child_item_code: string;
  child_item_name: string;
  bom_qty: number;
  unit: string;
  process_type: string;
  loss_rate: number;
  required_qty: number;
  input_qty: number;
}

interface MaterialInputRecord {
  id: string;
  item_code: string;
  item_name: string;
  input_qty: string;
  unit: string;
  remark: string | null;
  recorded_by: string | null;
  recorded_at: string | null;
}

// === 자재 수량 입력 (터치 키패드) ===
interface MaterialQtyInputProps {
  materialId: string;
  materialName?: string;
  unit: string;
  requiredQty: number;
  value: string;
  isConfirmed: boolean;
  onChange: (val: string) => void;
}

function MaterialQtyInput({
  materialId: _materialId,
  materialName,
  unit,
  requiredQty,
  value,
  isConfirmed,
  onChange,
}: MaterialQtyInputProps) {
  const [keypadOpen, setKeypadOpen] = useState(false);

  if (isConfirmed) {
    return (
      <div className="flex items-center justify-center gap-1.5">
        <span className="text-base font-bold text-gray-700">{value || "-"}</span>
        <span className="text-xs text-gray-400">{unit}</span>
      </div>
    );
  }

  // ±20% 범위
  const lower = requiredQty * 0.8;
  const upper = requiredQty * 1.2;

  return (
    <div className="flex items-center justify-center gap-1.5">
      <button
        type="button"
        className="h-14 w-28 rounded-xl border-2 border-gray-200 px-3 text-center text-lg font-bold text-gray-900 transition-colors hover:border-blue-400 active:scale-95"
        onClick={() => setKeypadOpen(true)}
      >
        {value || <span className="text-gray-300">0</span>}
      </button>
      <span className="text-xs text-gray-400">{unit}</span>
      <NumericKeypadModal
        open={keypadOpen}
        onClose={() => setKeypadOpen(false)}
        onConfirm={(val) => onChange(val)}
        title={materialName || "자재 투입"}
        unit={unit}
        initialValue={value || ""}
        lowerLimit={lower}
        upperLimit={upper}
      />
    </div>
  );
}

interface MaterialInputSectionProps {
  workOrderProcessId: string;
  isConfirmed: boolean;
}

function MaterialInputSection({ workOrderProcessId, isConfirmed }: MaterialInputSectionProps) {
  const [materials, setMaterials] = useState<BomMaterial[]>([]);
  const [inputValues, setInputValues] = useState<Record<string, string>>({});
  const [existingInputs, setExistingInputs] = useState<MaterialInputRecord[]>([]);
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);

  // BOM 자재 목록 + 기존 투입 기록 로드
  const loadData = useCallback(async () => {
    setLoading(true);
    try {
      const [bomRes, inputsRes] = await Promise.all([
        apiClient.get(`/pop/production/bom-materials/${workOrderProcessId}`),
        apiClient.get(`/pop/production/material-inputs/${workOrderProcessId}`),
      ]);

      const bomMaterials: BomMaterial[] = bomRes.data?.data?.materials ?? [];
      setMaterials(bomMaterials);

      const inputs: MaterialInputRecord[] = inputsRes.data?.data ?? [];
      setExistingInputs(inputs);

      // 기존 투입값을 inputValues에 반영 (같은 자재의 투입 합산)
      const existingMap: Record<string, number> = {};
      for (const inp of inputs) {
        const key = inp.item_code || inp.item_name;
        existingMap[key] = (existingMap[key] || 0) + (parseFloat(inp.input_qty) || 0);
      }

      const initialValues: Record<string, string> = {};
      for (const mat of bomMaterials) {
        const existing = existingMap[mat.child_item_code] || existingMap[mat.child_item_name];
        initialValues[mat.id] = existing ? String(existing) : "";
      }
      setInputValues(initialValues);
    } catch {
      // 에러 시 빈 상태 유지
    } finally {
      setLoading(false);
    }
  }, [workOrderProcessId]);

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

  const updateInputValue = (materialId: string, value: string) => {
    setInputValues((prev) => ({ ...prev, [materialId]: value }));
  };

  // 기준 범위 판정: lower_limit~upper_limit이 있으면 사용, 없으면 +/-20%
  const getStatus = (mat: BomMaterial, inputVal: string): { label: string; color: string } => {
    const val = parseFloat(inputVal);
    if (!inputVal || isNaN(val)) return { label: "", color: "" };

    const required = mat.required_qty;
    if (required <= 0) return { label: "기준 내", color: "text-green-600" };

    const lowerBound = required * 0.8;
    const upperBound = required * 1.2;

    if (val >= lowerBound && val <= upperBound) {
      return { label: "기준 내", color: "text-green-600" };
    } else if (val < lowerBound) {
      return { label: "기준 미달", color: "text-amber-600" };
    } else {
      return { label: "기준 초과", color: "text-amber-600" };
    }
  };

  const handleSave = async () => {
    const inputs = materials
      .filter((mat) => {
        const val = parseFloat(inputValues[mat.id] || "");
        return !isNaN(val) && val > 0;
      })
      .map((mat) => ({
        child_item_id: mat.child_item_id,
        child_item_code: mat.child_item_code,
        child_item_name: mat.child_item_name,
        input_qty: parseFloat(inputValues[mat.id]),
        unit: mat.unit,
        bom_detail_id: mat.id,
        required_qty: mat.required_qty,
      }));

    if (inputs.length === 0) {
      toast.error("투입 수량을 입력해주세요.");
      return;
    }

    setSaving(true);
    try {
      const res = await apiClient.post("/pop/production/material-input", {
        work_order_process_id: workOrderProcessId,
        inputs,
      });
      if (res.data?.success) {
        toast.success(res.data.message || "자재 투입이 저장되었습니다.");
        loadData(); // 새로고침
      } else {
        toast.error(res.data?.message || "자재 투입 저장에 실패했습니다.");
      }
    } catch {
      toast.error("자재 투입 저장 중 오류가 발생했습니다.");
    } finally {
      setSaving(false);
    }
  };

  if (loading) {
    return (
      <div>
        <div className="mb-4 text-xs font-semibold uppercase tracking-widest text-gray-400">자재 투입</div>
        <div className="flex items-center gap-2 py-4 text-sm text-gray-400">
          <Loader2 className="h-4 w-4 animate-spin" />불러오는 중...
        </div>
      </div>
    );
  }

  if (materials.length === 0) {
    return (
      <div>
        <div className="mb-4 text-xs font-semibold uppercase tracking-widest text-gray-400">자재 투입</div>
        <div className="flex items-center gap-3 rounded-xl border border-dashed border-gray-200 p-4">
          <Package className="h-5 w-5 shrink-0 text-gray-400" />
          <p className="text-sm text-gray-400">BOM 자재 정보가 없습니다.</p>
        </div>
      </div>
    );
  }

  return (
    <div>
      <div className="mb-4 text-xs font-semibold uppercase tracking-widest text-gray-400">자재 투입</div>

      {/* 자재 목록 테이블 */}
      <div className="overflow-hidden rounded-xl border border-gray-200 bg-white">
        <table className="w-full text-sm">
          <thead>
            <tr className="border-b border-gray-100 bg-gray-50 text-xs uppercase tracking-wider text-gray-400">
              <th className="px-4 py-3 text-left font-medium">품목명</th>
              <th className="px-4 py-3 text-right font-medium">기준량</th>
              <th className="px-4 py-3 text-center font-medium">실제투입</th>
              <th className="px-4 py-3 text-center font-medium">상태</th>
            </tr>
          </thead>
          <tbody>
            {materials.map((mat) => {
              const status = getStatus(mat, inputValues[mat.id] || "");
              return (
                <tr key={mat.id} className="border-t border-gray-50">
                  <td className="px-4 py-3">
                    <div className="font-medium text-gray-900">{mat.child_item_name || mat.child_item_code}</div>
                    {mat.child_item_code && mat.child_item_name && (
                      <div className="text-xs text-gray-400">{mat.child_item_code}</div>
                    )}
                  </td>
                  <td className="px-4 py-3 text-right">
                    <span className="font-semibold text-gray-700">{mat.required_qty}</span>
                    <span className="ml-1 text-xs text-gray-400">{mat.unit || "EA"}</span>
                  </td>
                  <td className="px-4 py-3">
                    <MaterialQtyInput
                      materialId={mat.id}
                      materialName={mat.child_item_name || mat.child_item_code}
                      unit={mat.unit || "EA"}
                      requiredQty={mat.required_qty}
                      value={inputValues[mat.id] || ""}
                      isConfirmed={isConfirmed}
                      onChange={(val) => updateInputValue(mat.id, val)}
                    />
                  </td>
                  <td className="px-4 py-3 text-center">
                    {status.label && (
                      <span className={cn("text-xs font-semibold", status.color)}>
                        {status.color === "text-green-600" ? (
                          <span className="inline-flex items-center gap-1">
                            <CheckCircle2 className="h-3.5 w-3.5" />
                            {status.label}
                          </span>
                        ) : (
                          <span className="inline-flex items-center gap-1">
                            <AlertCircle className="h-3.5 w-3.5" />
                            {status.label}
                          </span>
                        )}
                      </span>
                    )}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      {/* 저장 버튼 */}
      {!isConfirmed && (
        <div className="mt-4 flex items-center gap-3">
          <button
            className={cn(
              "flex h-14 items-center gap-2 rounded-xl bg-gray-900 px-8 text-sm font-semibold text-white transition-colors hover:bg-gray-800",
              saving && "cursor-not-allowed opacity-50"
            )}
            onClick={handleSave}
            disabled={saving}
          >
            {saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
            투입 기록 저장
          </button>
        </div>
      )}

      {/* 기존 투입 이력 */}
      {existingInputs.length > 0 && (
        <div className="mt-6">
          <div className="mb-3 text-xs font-semibold uppercase tracking-widest text-gray-400">투입 이력</div>
          <div className="overflow-hidden rounded-xl border border-gray-200 bg-white">
            <table className="w-full text-sm">
              <thead>
                <tr className="border-b border-gray-100 bg-gray-50 text-xs uppercase tracking-wider text-gray-400">
                  <th className="px-4 py-2 text-left font-medium">품목</th>
                  <th className="px-4 py-2 text-right font-medium">투입량</th>
                  <th className="px-4 py-2 text-right font-medium">시각</th>
                </tr>
              </thead>
              <tbody>
                {existingInputs.map((inp) => (
                  <tr key={inp.id} className="border-t border-gray-50">
                    <td className="px-4 py-2.5 font-medium text-gray-900">
                      {inp.item_name || inp.item_code}
                    </td>
                    <td className="px-4 py-2.5 text-right font-semibold text-gray-700">
                      {inp.input_qty} <span className="text-xs text-gray-400">{inp.unit}</span>
                    </td>
                    <td className="px-4 py-2.5 text-right text-gray-400">
                      {inp.recorded_at
                        ? new Date(inp.recorded_at).toLocaleString("ko-KR", {
                            month: "2-digit",
                            day: "2-digit",
                            hour: "2-digit",
                            minute: "2-digit",
                          })
                        : "-"}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      )}
    </div>
  );
}

// ========================================
// KPI 카드 (항상 표시)
// ========================================

interface KpiCardsProps {
  inputQty: number;
  completedQty: number;
  defectQty: number;
}

function KpiCards({ inputQty, completedQty, defectQty }: KpiCardsProps) {
  const remaining = Math.max(0, inputQty - completedQty);
  const cards = [
    { label: "접수량", value: inputQty, color: "text-gray-900", labelColor: "text-gray-400" },
    { label: "완료", value: completedQty, color: "text-blue-600", labelColor: "text-blue-400" },
    { label: "잔여", value: remaining, color: "text-amber-600", labelColor: "text-amber-400" },
    { label: "불량", value: defectQty, color: "text-red-600", labelColor: "text-red-400" },
  ];

  return (
    <div className="flex gap-5 border-b border-gray-100 bg-white px-8 py-5">
      {cards.map((c, i) => (
        <React.Fragment key={c.label}>
          {i > 0 && <div className="w-px bg-gray-100" />}
          <div className="flex flex-1 flex-col items-center py-3">
            <span
              className={cn(c.color)}
              style={{
                fontSize: `${DESIGN.kpi.valueSize}px`,
                lineHeight: 1,
                fontWeight: DESIGN.kpi.weight,
                fontVariantNumeric: 'tabular-nums',
                letterSpacing: '-0.02em',
              }}
            >
              {c.value}
            </span>
            <span
              className={cn("mt-1 font-medium", c.labelColor)}
              style={{ fontSize: `${DESIGN.kpi.labelSize}px`, letterSpacing: '0.01em' }}
            >
              {c.label}
            </span>
          </div>
        </React.Fragment>
      ))}
    </div>
  );
}

// ========================================
// 그룹별 타이머 헤더
// ========================================

interface GroupTimerHeaderProps {
  group: WorkGroup;
  cfg: PopWorkDetailConfig;
  isProcessCompleted: boolean;
  isGroupStarted: boolean;
  isGroupPaused: boolean;
  isGroupCompleted: boolean;
  groupTimerFormatted: string;
  groupElapsedFormatted: string;
  onTimerAction: (action: "start" | "pause" | "resume" | "complete") => void;
}

function GroupTimerHeader({
  group,
  cfg,
  isProcessCompleted,
  isGroupStarted,
  isGroupPaused,
  isGroupCompleted,
  groupTimerFormatted,
  groupElapsedFormatted,
  onTimerAction,
}: GroupTimerHeaderProps) {
  return (
    <div className="shrink-0 border-b border-gray-100 bg-white" style={{ zIndex: 10 }}>
      {/* 그룹 제목 + 진행 카운트 */}
      <div className="flex items-center justify-between px-6 pb-1.5 pt-3">
        <div className="flex items-center gap-2.5">
          <span className="font-bold text-gray-900 uppercase tracking-wide" style={{ fontSize: 18 }}>{group.title}</span>
          <span className="rounded bg-gray-100 px-2.5 py-1 font-semibold text-gray-600" style={{ fontSize: 15 }}>
            {group.completed}/{group.total}
          </span>
        </div>
        {isGroupCompleted && (
          <span className="rounded bg-green-100 px-3 py-1 text-sm font-semibold text-green-700">완료</span>
        )}
      </div>

      {/* 그룹 타이머 - 고정 + 큰 글자 + 시작/일시정지/재개 전환 */}
      {cfg.showTimer && (
        <div className="flex items-center justify-between px-6 py-2.5" style={{ backgroundColor: '#f0f2f5' }}>
          <div className="flex items-center gap-6">
            <div className="flex items-center gap-2">
              <Timer className="h-5 w-5 text-blue-500" />
              <span className="font-mono font-bold tabular-nums text-blue-700" style={{ fontSize: 38 }}>{groupTimerFormatted}</span>
            </div>
            <div className="flex items-center gap-1.5">
              <span className="font-mono tabular-nums text-gray-400" style={{ fontSize: 16 }}>경과 {groupElapsedFormatted}</span>
            </div>
          </div>
          {!isProcessCompleted && !isGroupCompleted && (
            <div className="flex items-center gap-2">
              {/* 시작 → 일시정지 → 재개 전환 토글 */}
              {!isGroupStarted && (
                <GlossyButton variant="teal" onClick={() => onTimerAction("start")} style={{ minHeight: 48, minWidth: 120, fontSize: 16 }}>
                  <Play className="h-5 w-5" />시작
                </GlossyButton>
              )}
              {isGroupStarted && !isGroupPaused && (
                <GlossyButton variant="yellow" onClick={() => onTimerAction("pause")} style={{ minHeight: 48, minWidth: 120, fontSize: 16 }}>
                  <Pause className="h-5 w-5" />일시정지
                </GlossyButton>
              )}
              {isGroupStarted && isGroupPaused && (
                <GlossyButton variant="teal" onClick={() => onTimerAction("resume")} style={{ minHeight: 48, minWidth: 120, fontSize: 16 }}>
                  <Play className="h-5 w-5" />재개
                </GlossyButton>
              )}
              {isGroupStarted && (
                <GlossyButton variant="green" onClick={() => onTimerAction("complete")} style={{ minHeight: 48, minWidth: 120, fontSize: 16 }}>
                  <Check className="h-5 w-5" />완료
                </GlossyButton>
              )}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ========================================
// 단계 상태 아이콘
// ========================================

function StepStatusIcon({ status }: { status: "pending" | "active" | "completed" }) {
  switch (status) {
    case "completed":
      return <CheckCircle2 className="h-4.5 w-4.5 shrink-0 text-green-600" />;
    case "active":
      return <CircleDot className="h-4.5 w-4.5 shrink-0 text-primary" />;
    default:
      return <div className="h-4.5 w-4.5 shrink-0 rounded-full border border-muted-foreground/40" />;
  }
}

// ========================================
// 사이드바 아이템 상태 아이콘 (combined-final)
// ========================================

function SidebarStepIcon({ status, isSelected }: { status: "pending" | "active" | "completed"; isSelected: boolean }) {
  if (status === "completed") {
    return (
      <svg className="h-4 w-4 shrink-0 text-green-500" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
        <path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
      </svg>
    );
  }
  if (isSelected || status === "active") {
    return (
      <svg className="h-4 w-4 shrink-0 text-blue-500" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
        <circle cx="12" cy="12" r="10" />
        <path d="M12 8v4l3 3" />
      </svg>
    );
  }
  return <div className="h-4 w-4 shrink-0 rounded-full border-2 border-gray-300" />;
}

// ========================================
// 체크리스트 행 래퍼 (행 전체 터치 영역 + 상태 표시)
// ========================================

function ChecklistRowItem({ item, saving, disabled, onSave }: {
  item: WorkResultRow;
  saving: boolean;
  disabled: boolean;
  onSave: (rowId: string, resultValue: string, isPassed: string | null, newStatus: string) => void;
}) {
  const isCompleted = item.status === "completed";
  const isRequired = item.is_required === "Y";

  // 좌정보/우입력 분할 레이아웃 (여백 최소화)
  const leftBg = isCompleted
    ? '#d4edda'
    : isRequired
      ? '#fde8e8'
      : '#f0f2f5';
  const leftBorderColor = isCompleted
    ? '#b8d8be'
    : isRequired
      ? '#f5c6c6'
      : '#dde0e5';

  // 범위/기준값 표시 텍스트
  const rangeText = buildRangeText(item);

  return (
    <div
      className={cn(
        "overflow-hidden rounded-xl shadow-sm transition-all",
        isCompleted && "ring-1 ring-green-200",
      )}
      style={{ backgroundColor: DESIGN.bg.card }}
    >
      <div className="flex items-center" style={{ minHeight: 68 }}>
        {/* 왼쪽: 정보 (항목명 + 기준값/범위) */}
        <div
          className="flex shrink-0 flex-col justify-center px-4 py-2"
          style={{
            width: 200,
            background: leftBg,
            borderRight: `2px solid ${leftBorderColor}`,
            minHeight: 68,
          }}
        >
          <div className="flex items-center gap-1">
            <span
              className={cn("font-semibold", isCompleted ? "text-green-800" : "text-gray-900")}
              style={{ fontSize: 15 }}
            >
              {item.detail_label || item.detail_content}
            </span>
            {isRequired && !isCompleted && <span className="text-red-500 font-bold" style={{ fontSize: 15 }}>*</span>}
          </div>
          {rangeText && (
            <div className={cn("mt-0.5", isCompleted ? "text-green-600" : "text-gray-500")} style={{ fontSize: 13 }}>
              {rangeText}
            </div>
          )}
        </div>

        {/* 오른쪽: 입력 (측정값, 체크박스, OK/NG) */}
        <div className="flex flex-1 items-center justify-end gap-4 px-4 py-2">
          <ChecklistItemInput item={item} saving={saving} disabled={disabled} onSave={onSave} />
        </div>
      </div>
    </div>
  );
}

/** 기준값/범위 텍스트 생성 */
function buildRangeText(item: WorkResultRow): string {
  const parts: string[] = [];
  const lower = item.lower_limit;
  const upper = item.upper_limit;
  const unit = item.unit || '';

  if (lower && upper) {
    parts.push(`${lower}~${upper}${unit ? ' ' + unit : ''}`);
  } else if (item.spec_value) {
    parts.push(`기준: ${item.spec_value}`);
  }

  if (item.is_required === "Y") parts.push("필수");

  const dt = item.detail_type ?? "";
  if (dt === "check") parts.push("체크");
  else if (dt.startsWith("inspect")) {
    const inputType = item.input_type ?? "";
    if (inputType === "ox") parts.push("O/X 판정");
    else if (inputType === "select") parts.push("선택형");
    else if (inputType === "text") parts.push("텍스트");
  }

  return parts.join(" | ");
}

// ========================================
// 그룹 하단 작업완료 버튼
// ========================================

function GroupCompleteButton({
  group,
  currentItems,
  isGroupStarted,
  isGroupCompleted,
  onComplete,
  onNavigateNext,
}: {
  group: WorkGroup;
  currentItems: WorkResultRow[];
  isGroupStarted: boolean;
  isGroupCompleted: boolean;
  onComplete: () => void;
  onNavigateNext: () => void;
}) {
  const [confirmOpen, setConfirmOpen] = useState(false);
  const requiredItems = currentItems.filter((r) => r.is_required === "Y");
  const allRequiredDone = requiredItems.length === 0 || requiredItems.every((r) => r.status === "completed");
  const allDone = currentItems.every((r) => r.status === "completed");
  const isDisabled = !isGroupStarted || (!allRequiredDone);

  // 이미 완료된 그룹: 완료 표시만
  if (isGroupCompleted) {
    return (
      <div
        className="flex items-center justify-center gap-2 rounded-2xl border-2 border-green-400 bg-green-50"
        style={{ width: '100%', minHeight: 64, fontSize: 18, color: '#16a34a', borderRadius: 16 }}
      >
        <Check className="h-6 w-6" />
        작업완료됨
      </div>
    );
  }

  if (confirmOpen) {
    return (
      <div className="flex items-center gap-3">
        <GlossyButton
          variant="gray"
          onClick={() => setConfirmOpen(false)}
          style={{ flex: 1, minHeight: 64, fontSize: 18, borderRadius: 16 }}
        >
          취소
        </GlossyButton>
        <GlossyButton
          variant="green"
          onClick={() => {
            setConfirmOpen(false);
            onComplete();
          }}
          style={{ flex: 2, minHeight: 64, fontSize: 18, borderRadius: 16 }}
        >
          <Check className="h-6 w-6" />
          정말 작업완료
        </GlossyButton>
      </div>
    );
  }

  return (
    <GlossyButton
      variant={allDone ? "green" : "purple"}
      disabled={isDisabled}
      onClick={() => {
        if (allDone) {
          setConfirmOpen(true);
        } else {
          // 필수만 완료된 경우 → 다음 공정으로
          onNavigateNext();
        }
      }}
      style={{ width: '100%', minHeight: 64, fontSize: 18, borderRadius: 16 }}
    >
      <svg className="h-6 w-6" fill="none" stroke="currentColor" strokeWidth={2.5} viewBox="0 0 24 24">
        <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
      </svg>
      {isDisabled
        ? `작업완료 (필수 항목 미완료)`
        : allDone
          ? `작업완료`
          : `다음 단계로`
      }
    </GlossyButton>
  );
}

// ========================================
// 체크리스트 개별 항목 (라우터)
// ========================================

interface ChecklistItemProps {
  item: WorkResultRow;
  saving: boolean;
  disabled: boolean;
  onSave: (rowId: string, resultValue: string, isPassed: string | null, newStatus: string) => void;
}

function ChecklistItem({ item: rawItem, saving, disabled, onSave }: ChecklistItemProps) {
  const isDisabled = disabled || saving;
  // 1순위: judgment_criteria 기반 정규화 (inspection_standard 조인 결과)
  // 2순위: detail_type 폴백 정규화 (원본 DB 값 → canonical 라우터 값)
  const item = normalizeByDetailType(normalizeByJudgmentCriteria(rawItem));
  const dt = item.detail_type ?? "";

  // "inspect_numeric" 등 레거시 형식 → "inspect"로 정규화, 접미사를 input_type 폴백으로 사용
  if (dt.startsWith("inspect")) {
    const normalized = { ...item, detail_type: "inspect" } as WorkResultRow;
    if (!normalized.input_type && dt.includes("_")) {
      const suffix = dt.split("_").slice(1).join("_");
      const typeMap: Record<string, string> = { numeric: "numeric_range", ox: "ox", text: "text", select: "select" };
      normalized.input_type = typeMap[suffix] ?? suffix;
    }
    return <InspectRouter item={normalized} disabled={isDisabled} saving={saving} onSave={onSave} />;
  }

  switch (dt) {
    case "check":
      return <CheckItem item={item} disabled={isDisabled} saving={saving} onSave={onSave} />;
    case "input":
      return <InputItem item={item} disabled={isDisabled} saving={saving} onSave={onSave} />;
    case "procedure":
      return <ProcedureItem item={item} disabled={isDisabled} saving={saving} onSave={onSave} />;
    case "material":
      return <MaterialItem item={item} disabled={isDisabled} saving={saving} onSave={onSave} />;
    case "result":
      return <ResultInputItem item={item} disabled={isDisabled} saving={saving} onSave={onSave} />;
    case "info":
      return (
        <div className="rounded-lg border bg-muted/30 px-4 py-3 text-sm">
          {item.detail_label || item.detail_content}
        </div>
      );
    default:
      return (
        <div className="rounded-lg border p-3 text-sm text-muted-foreground">
          알 수 없는 유형: {item.detail_type}
        </div>
      );
  }
}

// ========================================
// 체크리스트 입력부 (우측 영역 전용 - 분할 레이아웃)
// ========================================

function ChecklistItemInput({ item: rawItem, saving, disabled, onSave }: ChecklistItemProps) {
  const isDisabled = disabled || saving;
  // 1순위: judgment_criteria, 2순위: detail_type 폴백
  const item = normalizeByDetailType(normalizeByJudgmentCriteria(rawItem));
  const dt = item.detail_type ?? "";

  if (dt.startsWith("inspect")) {
    const normalized = { ...item, detail_type: "inspect" } as WorkResultRow;
    if (!normalized.input_type && dt.includes("_")) {
      const suffix = dt.split("_").slice(1).join("_");
      const typeMap: Record<string, string> = { numeric: "numeric_range", ox: "ox", text: "text", select: "select" };
      normalized.input_type = typeMap[suffix] ?? suffix;
    }
    return <InspectInputRouter item={normalized} disabled={isDisabled} saving={saving} onSave={onSave} />;
  }

  switch (dt) {
    case "check":
      return <CheckInputOnly item={item} disabled={isDisabled} saving={saving} onSave={onSave} />;
    case "input":
      return <InputOnlyItem item={item} disabled={isDisabled} saving={saving} onSave={onSave} />;
    case "procedure":
      return <ProcedureInputOnly item={item} disabled={isDisabled} saving={saving} onSave={onSave} />;
    case "material":
      return <MaterialInputOnly item={item} disabled={isDisabled} saving={saving} onSave={onSave} />;
    case "result":
      return <ResultInputOnly item={item} disabled={isDisabled} saving={saving} onSave={onSave} />;
    case "info":
      return <span className="text-gray-500" style={{ fontSize: 15 }}>{item.detail_label || item.detail_content}</span>;
    default:
      return <span className="text-gray-400" style={{ fontSize: 14 }}>알 수 없는 유형</span>;
  }
}

// === 입력 전용 inspect 라우터 ===
function InspectInputRouter(props: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const inputType = props.item.input_type ?? "numeric_range";
  switch (inputType) {
    case "ox":
      return <InspectOXInput {...props} />;
    case "select":
      return <InspectSelectInput {...props} />;
    case "text":
      return <InspectTextInput {...props} />;
    default:
      return <InspectNumericInput {...props} />;
  }
}

// === 수치 입력 (우측 전용) — 터치 키패드 모달 ===
function InspectNumericInput({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const [keypadOpen, setKeypadOpen] = useState(false);
  const lower = parseFloat(item.lower_limit ?? "");
  const upper = parseFloat(item.upper_limit ?? "");
  const hasRange = !isNaN(lower) && !isNaN(upper);

  const handleConfirm = (val: string) => {
    const numVal = parseFloat(val);
    let passed: string | null = null;
    if (hasRange) passed = numVal >= lower && numVal <= upper ? "Y" : "N";
    onSave(item.id, val, passed, "completed");
  };

  const displayValue = item.result_value ?? "";

  return (
    <>
      <button
        className={cn(
          "rounded-xl border-2 text-center font-bold transition-colors",
          disabled ? "bg-gray-50 border-gray-100" : "bg-white border-gray-200 hover:border-blue-400 active:scale-95",
        )}
        style={{
          width: 140, height: 56, fontSize: 22,
          color: item.is_passed === "Y" ? '#16a34a' : item.is_passed === "N" ? '#dc2626' : '#1f2937',
        }}
        onClick={() => !disabled && setKeypadOpen(true)}
        disabled={disabled}
      >
        {displayValue || <span className="text-gray-300">입력</span>}
      </button>
      {item.unit && <span className="text-gray-400" style={{ fontSize: 16 }}>{item.unit}</span>}
      {item.is_passed === "Y" && !saving && <span className="rounded-lg bg-green-100 px-3 py-1.5 font-bold text-green-700" style={{ fontSize: 15 }}>PASS</span>}
      {item.is_passed === "N" && !saving && <span className="rounded-lg bg-red-100 px-3 py-1.5 font-bold text-red-700" style={{ fontSize: 15 }}>FAIL</span>}
      {item.status === "completed" && item.is_passed === null && !saving && <span className="font-bold text-green-600" style={{ fontSize: 15 }}>완료</span>}
      {saving && <Loader2 className="h-5 w-5 animate-spin text-gray-400" />}
      <NumericKeypadModal
        open={keypadOpen}
        onClose={() => setKeypadOpen(false)}
        onConfirm={handleConfirm}
        title={item.detail_label || item.detail_content}
        unit={item.unit ?? undefined}
        initialValue={displayValue}
        lowerLimit={hasRange ? lower : undefined}
        upperLimit={hasRange ? upper : undefined}
      />
    </>
  );
}

// === O/X (우측 전용) ===
function InspectOXInput({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const criteriaOK = item.spec_value ?? "OK";
  const handleSelect = (value: string) => {
    if (disabled) return;
    const passed = value === criteriaOK ? "Y" : "N";
    onSave(item.id, value, passed, "completed");
  };

  return (
    <>
      <GlossyButton
        variant="green"
        onClick={() => handleSelect("OK")}
        disabled={disabled}
        style={{
          minHeight: 56, minWidth: 130, fontSize: 20,
          ...(item.result_value === "OK" ? { boxShadow: '0 0 0 3px #22c55e, 0 4px 12px rgba(34,197,94,0.3), inset 0 1px 0 rgba(255,255,255,0.4)' } : {}),
        }}
      >
        O
      </GlossyButton>
      <GlossyButton
        variant="red"
        onClick={() => handleSelect("NG")}
        disabled={disabled}
        style={{
          minHeight: 56, minWidth: 130, fontSize: 20,
          ...(item.result_value === "NG" ? { boxShadow: '0 0 0 3px #ef4444, 0 4px 12px rgba(239,68,68,0.3), inset 0 1px 0 rgba(255,255,255,0.4)' } : {}),
        }}
      >
        X
      </GlossyButton>
      {saving && <Loader2 className="h-5 w-5 animate-spin text-gray-400" />}
    </>
  );
}

// === 선택형 (우측 전용) ===
function InspectSelectInput({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const currentValue = item.result_value ?? "";
  let options: string[] = [];
  let passValues: string[] = [];
  try {
    const parsed = JSON.parse(item.spec_value ?? "{}");
    options = Array.isArray(parsed.options) ? parsed.options : [];
    passValues = Array.isArray(parsed.passValues) ? parsed.passValues : [];
  } catch {
    options = (item.spec_value ?? "").split(",").filter(Boolean);
  }
  const handleSelect = (value: string) => {
    if (disabled) return;
    const passed = passValues.includes(value) ? "Y" : "N";
    onSave(item.id, value, passed, "completed");
  };

  return (
    <>
      {options.map((opt) => (
        <GlossyButton
          key={opt}
          variant={currentValue === opt ? "blue" : "gray"}
          onClick={() => handleSelect(opt)}
          disabled={disabled}
          style={{
            minHeight: 56, minWidth: 100, fontSize: 16,
            ...(currentValue === opt ? { boxShadow: '0 0 0 3px #3b82f6, 0 4px 12px rgba(59,130,246,0.3), inset 0 1px 0 rgba(255,255,255,0.4)' } : {}),
          }}
        >
          {opt}
        </GlossyButton>
      ))}
      {saving && <Loader2 className="h-5 w-5 animate-spin text-gray-400" />}
    </>
  );
}

// === 텍스트 + 수동판정 (우측 전용) ===
function InspectTextInput({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const [inputVal, setInputVal] = useState(item.result_value ?? "");
  const [judged, setJudged] = useState<string | null>(item.is_passed);
  const handleJudge = (passed: string) => {
    if (disabled) return;
    setJudged(passed);
    onSave(item.id, inputVal || "-", passed, "completed");
  };

  return (
    <>
      <input
        className="flex-1 rounded-xl border-2 border-gray-200 px-3 transition-colors focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-100"
        style={{ height: 56, fontSize: 16 }}
        value={inputVal}
        onChange={(e) => setInputVal(e.target.value)}
        disabled={disabled}
        placeholder="내용 입력"
      />
      <GlossyButton
        variant={judged === "Y" ? "green" : "gray"}
        onClick={() => handleJudge("Y")}
        disabled={disabled}
        style={{ minHeight: 56, minWidth: 90, fontSize: 16 }}
      >
        합격
      </GlossyButton>
      <GlossyButton
        variant={judged === "N" ? "red" : "gray"}
        onClick={() => handleJudge("N")}
        disabled={disabled}
        style={{ minHeight: 56, minWidth: 90, fontSize: 16 }}
      >
        불합격
      </GlossyButton>
      {saving && <Loader2 className="h-5 w-5 animate-spin text-gray-400" />}
    </>
  );
}

// === 체크박스 (우측 전용) — 큰 터치 토글 ===
function CheckInputOnly({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const checked = item.result_value === "Y";
  return (
    <>
      <GlossyButton
        variant={checked ? "green" : "gray"}
        onClick={() => {
          if (disabled) return;
          onSave(item.id, "Y", "Y", "completed");
        }}
        disabled={disabled}
        style={{
          minHeight: 56, minWidth: 110, fontSize: 18,
          ...(checked ? { boxShadow: '0 0 0 3px #22c55e, 0 4px 12px rgba(34,197,94,0.3), inset 0 1px 0 rgba(255,255,255,0.4)' } : {}),
        }}
      >
        <Check className="h-5 w-5" /> 확인
      </GlossyButton>
      <GlossyButton
        variant={!checked && item.result_value === "N" ? "red" : "gray"}
        onClick={() => {
          if (disabled) return;
          onSave(item.id, "N", "N", "pending");
        }}
        disabled={disabled}
        style={{
          minHeight: 56, minWidth: 110, fontSize: 18,
          ...(!checked && item.result_value === "N" ? { boxShadow: '0 0 0 3px #ef4444, 0 4px 12px rgba(239,68,68,0.3), inset 0 1px 0 rgba(255,255,255,0.4)' } : {}),
        }}
      >
        <X className="h-5 w-5" /> 미확인
      </GlossyButton>
      {saving && <Loader2 className="h-5 w-5 animate-spin text-gray-400" />}
    </>
  );
}

// === 자유입력 (우측 전용) — number는 키패드, text는 큰 입력칸 ===
function InputOnlyItem({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const isNumber = item.input_type === "number";
  const [inputVal, setInputVal] = useState(item.result_value ?? "");
  const [keypadOpen, setKeypadOpen] = useState(false);

  const handleBlur = () => {
    if (!inputVal || disabled) return;
    onSave(item.id, inputVal, null, "completed");
  };

  if (isNumber) {
    return (
      <>
        <button
          className={cn(
            "flex-1 rounded-xl border-2 text-center font-semibold transition-colors",
            disabled ? "bg-gray-50 border-gray-100" : "bg-white border-gray-200 hover:border-blue-400 active:scale-95",
          )}
          style={{ height: 56, fontSize: 18, color: '#1f2937' }}
          onClick={() => !disabled && setKeypadOpen(true)}
          disabled={disabled}
        >
          {inputVal || <span className="text-gray-300">값 입력</span>}
        </button>
        {item.status === "completed" && !saving && <span className="font-bold text-green-600" style={{ fontSize: 15 }}>완료</span>}
        {saving && <Loader2 className="h-5 w-5 animate-spin text-gray-400" />}
        <NumericKeypadModal
          open={keypadOpen}
          onClose={() => setKeypadOpen(false)}
          onConfirm={(val) => {
            setInputVal(val);
            onSave(item.id, val, null, "completed");
          }}
          title={item.detail_label || item.detail_content}
          unit={item.unit ?? undefined}
          initialValue={inputVal}
        />
      </>
    );
  }

  return (
    <>
      <input
        type="text"
        className="flex-1 rounded-xl border-2 border-gray-200 px-3 text-center font-semibold transition-colors focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-100"
        style={{ height: 56, fontSize: 16 }}
        value={inputVal}
        onChange={(e) => setInputVal(e.target.value)}
        onBlur={handleBlur}
        disabled={disabled}
        placeholder="값 입력"
      />
      {saving && <Loader2 className="h-5 w-5 animate-spin text-gray-400" />}
    </>
  );
}

// === 절차 확인 (우측 전용) — 큰 터치 토글 ===
function ProcedureInputOnly({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const checked = item.result_value === "Y";
  return (
    <>
      <GlossyButton
        variant={checked ? "green" : "gray"}
        onClick={() => {
          if (disabled) return;
          onSave(item.id, checked ? "N" : "Y", null, checked ? "pending" : "completed");
        }}
        disabled={disabled}
        style={{
          minHeight: 56, minWidth: 130, fontSize: 18,
          ...(checked ? { boxShadow: '0 0 0 3px #22c55e, 0 4px 12px rgba(34,197,94,0.3), inset 0 1px 0 rgba(255,255,255,0.4)' } : {}),
        }}
      >
        <Check className="h-5 w-5" /> {checked ? "확인됨" : "확인"}
      </GlossyButton>
      {saving && <Loader2 className="h-5 w-5 animate-spin text-gray-400" />}
    </>
  );
}

// === 자재/LOT (우측 전용) ===
function MaterialInputOnly({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const [inputVal, setInputVal] = useState(item.result_value ?? "");
  const handleSubmit = () => {
    if (!inputVal || disabled) return;
    onSave(item.id, inputVal, null, "completed");
  };
  return (
    <>
      <input
        className="flex-1 rounded-xl border-2 border-gray-200 px-3 transition-colors focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-100"
        style={{ height: 48, fontSize: 15 }}
        value={inputVal}
        onChange={(e) => setInputVal(e.target.value)}
        onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
        disabled={disabled}
        placeholder="LOT/바코드"
      />
      <GlossyButton variant="blue" onClick={handleSubmit} disabled={disabled || !inputVal} style={{ minHeight: 48, minWidth: 80, fontSize: 15 }}>
        확인
      </GlossyButton>
      {saving && <Loader2 className="h-5 w-5 animate-spin text-gray-400" />}
      {item.status === "completed" && !saving && <span className="font-bold text-green-600" style={{ fontSize: 15 }}>투입</span>}
    </>
  );
}

// === 실적 입력 (우측 전용) ===
function ResultInputOnly({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  let savedData: { good?: string; defect?: string; defectType?: string; note?: string } = {};
  try { savedData = JSON.parse(item.result_value ?? "{}"); } catch { savedData = {}; }
  const [good, setGood] = useState(savedData.good ?? "");
  const [defect, setDefect] = useState(savedData.defect ?? "");
  const handleSave = () => {
    if (disabled) return;
    const val = JSON.stringify({ good, defect });
    onSave(item.id, val, null, "completed");
  };
  return (
    <>
      <div className="flex items-center gap-1.5">
        <span className="text-gray-500" style={{ fontSize: 14 }}>양품</span>
        <input type="number" className="w-20 rounded-lg border-2 border-gray-200 px-2 text-center font-bold" style={{ height: 44, fontSize: 16 }} value={good} onChange={(e) => setGood(e.target.value)} disabled={disabled} placeholder="0" />
      </div>
      <div className="flex items-center gap-1.5">
        <span className="text-gray-500" style={{ fontSize: 14 }}>불량</span>
        <input type="number" className="w-20 rounded-lg border-2 border-red-200 px-2 text-center font-bold text-red-600" style={{ height: 44, fontSize: 16 }} value={defect} onChange={(e) => setDefect(e.target.value)} disabled={disabled} placeholder="0" />
      </div>
      <GlossyButton variant="blue" onClick={handleSave} disabled={disabled} style={{ minHeight: 44, minWidth: 80, fontSize: 15 }}>
        등록
      </GlossyButton>
      {saving && <Loader2 className="h-5 w-5 animate-spin text-gray-400" />}
    </>
  );
}

// ========================================
// check: 체크박스
// ========================================

function CheckItem({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const checked = item.result_value === "Y";
  return (
    <div className={cn("rounded-lg border px-4 py-3", item.status === "completed" && "border-green-200 bg-green-50")}>
      <div className="mb-2 flex items-center gap-2">
        <span className="flex-1 text-sm font-medium">{item.detail_label || item.detail_content}</span>
        {item.is_required === "Y" && <span className="text-xs font-semibold text-destructive">필수</span>}
        {saving && <Loader2 className="h-4 w-4 animate-spin" />}
      </div>
      <div className="flex items-center gap-3">
        <GlossyButton
          variant={checked ? "green" : "gray"}
          onClick={() => {
            if (disabled) return;
            onSave(item.id, "Y", "Y", "completed");
          }}
          disabled={disabled}
          style={{
            minHeight: 56, minWidth: 130, fontSize: 18,
            ...(checked ? { boxShadow: '0 0 0 3px #22c55e, 0 4px 12px rgba(34,197,94,0.3), inset 0 1px 0 rgba(255,255,255,0.4)' } : {}),
          }}
        >
          <Check className="h-5 w-5" /> 확인
        </GlossyButton>
        <GlossyButton
          variant={!checked && item.result_value === "N" ? "red" : "gray"}
          onClick={() => {
            if (disabled) return;
            onSave(item.id, "N", "N", "pending");
          }}
          disabled={disabled}
          style={{
            minHeight: 56, minWidth: 130, fontSize: 18,
            ...(!checked && item.result_value === "N" ? { boxShadow: '0 0 0 3px #ef4444, 0 4px 12px rgba(239,68,68,0.3), inset 0 1px 0 rgba(255,255,255,0.4)' } : {}),
          }}
        >
          <X className="h-5 w-5" /> 미확인
        </GlossyButton>
      </div>
    </div>
  );
}

// ========================================
// inspect: 라우터 (input_type으로 분기)
// ========================================

function InspectRouter(props: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const inputType = props.item.input_type ?? "numeric_range";
  switch (inputType) {
    case "ox":
      return <InspectOX {...props} />;
    case "select":
      return <InspectSelect {...props} />;
    case "text":
      return <InspectText {...props} />;
    default:
      return <InspectNumeric {...props} />;
  }
}

// ===== inspect: 수치(범위) =====

function InspectNumeric({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const [keypadOpen, setKeypadOpen] = useState(false);
  const lower = parseFloat(item.lower_limit ?? "");
  const upper = parseFloat(item.upper_limit ?? "");
  const hasRange = !isNaN(lower) && !isNaN(upper);

  const handleConfirm = (val: string) => {
    const numVal = parseFloat(val);
    let passed: string | null = null;
    if (hasRange) {
      passed = numVal >= lower && numVal <= upper ? "Y" : "N";
    }
    onSave(item.id, val, passed, "completed");
  };

  const displayValue = item.result_value ?? "";

  return (
    <div className={cn("rounded-lg border px-4 py-3", item.is_passed === "Y" && "border-green-200 bg-green-50", item.is_passed === "N" && "border-red-200 bg-red-50")}>
      <div className="mb-2 flex items-center justify-between">
        <div className="flex items-center gap-2">
          <span className="text-sm font-medium">{item.detail_label || item.detail_content}</span>
          {item.is_required === "Y" && <span className="text-xs font-semibold text-destructive">필수</span>}
        </div>
        {hasRange && (
          <span className="text-xs text-muted-foreground">
            {item.lower_limit} ~ {item.upper_limit} {item.unit || ""}
            {item.spec_value ? ` (표준: ${item.spec_value})` : ""}
          </span>
        )}
      </div>
      {item.inspection_method && (
        <div className="mb-2 text-xs text-muted-foreground">{item.inspection_method}</div>
      )}
      <div className="flex items-center gap-2.5">
        <button
          className={cn(
            "w-36 rounded-xl border-2 text-center font-bold transition-colors",
            disabled ? "bg-gray-50 border-gray-100" : "bg-white border-gray-200 hover:border-blue-400 active:scale-95",
          )}
          style={{ height: 56, fontSize: 20, color: '#1f2937' }}
          onClick={() => !disabled && setKeypadOpen(true)}
          disabled={disabled}
        >
          {displayValue || <span className="text-gray-300">측정값</span>}
        </button>
        {item.unit && <span className="text-xs text-muted-foreground">{item.unit}</span>}
        {saving && <Loader2 className="h-4 w-4 animate-spin" />}
        {item.is_passed === "Y" && !saving && <Badge variant="outline" className="text-xs text-green-600">합격</Badge>}
        {item.is_passed === "N" && !saving && <Badge variant="outline" className="text-xs text-red-600">불합격</Badge>}
      </div>
      <NumericKeypadModal
        open={keypadOpen}
        onClose={() => setKeypadOpen(false)}
        onConfirm={handleConfirm}
        title={item.detail_label || item.detail_content}
        unit={item.unit ?? undefined}
        initialValue={displayValue}
        lowerLimit={hasRange ? lower : undefined}
        upperLimit={hasRange ? upper : undefined}
      />
    </div>
  );
}

// ===== inspect: O/X =====

function InspectOX({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const currentValue = item.result_value ?? "";
  const criteriaOK = item.spec_value ?? "OK";

  const handleSelect = (value: string) => {
    if (disabled) return;
    const passed = value === criteriaOK ? "Y" : "N";
    onSave(item.id, value, passed, "completed");
  };

  return (
    <div className={cn("rounded-lg border px-4 py-3", item.is_passed === "Y" && "border-green-200 bg-green-50", item.is_passed === "N" && "border-red-200 bg-red-50")}>
      <div className="mb-2 flex items-center gap-2">
        <span className="text-sm font-medium">{item.detail_label || item.detail_content}</span>
        {item.is_required === "Y" && <span className="text-xs font-semibold text-destructive">필수</span>}
      </div>
      <div className="flex items-center gap-2.5">
        <Button
          size="sm"
          variant={currentValue === "OK" ? "default" : "outline"}
          className={cn("gap-1.5 px-6 font-medium", currentValue === "OK" && "bg-green-600 hover:bg-green-700")}
          style={{ height: `${DESIGN.button.height}px`, fontSize: 20 }}
          disabled={disabled}
          onClick={() => handleSelect("OK")}
        >
          <Check className="h-4 w-4" />OK
        </Button>
        <Button
          size="sm"
          variant={currentValue === "NG" ? "default" : "outline"}
          className={cn("gap-1.5 px-6 font-medium", currentValue === "NG" && "bg-red-600 hover:bg-red-700")}
          style={{ height: `${DESIGN.button.height}px`, fontSize: 20 }}
          disabled={disabled}
          onClick={() => handleSelect("NG")}
        >
          <X className="h-4 w-4" />NG
        </Button>
        {saving && <Loader2 className="h-4 w-4 animate-spin" />}
        {item.is_passed === "Y" && !saving && <Badge variant="outline" className="text-xs text-green-600">합격</Badge>}
        {item.is_passed === "N" && !saving && <Badge variant="outline" className="text-xs text-red-600">불합격</Badge>}
      </div>
    </div>
  );
}

// ===== inspect: 선택형 =====

function InspectSelect({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const currentValue = item.result_value ?? "";
  let options: string[] = [];
  let passValues: string[] = [];

  try {
    const parsed = JSON.parse(item.spec_value ?? "{}");
    options = Array.isArray(parsed.options) ? parsed.options : [];
    passValues = Array.isArray(parsed.passValues) ? parsed.passValues : [];
  } catch {
    options = (item.spec_value ?? "").split(",").filter(Boolean);
  }

  const handleSelect = (value: string) => {
    if (disabled) return;
    const passed = passValues.includes(value) ? "Y" : "N";
    onSave(item.id, value, passed, "completed");
  };

  return (
    <div className={cn("rounded-lg border px-4 py-3", item.is_passed === "Y" && "border-green-200 bg-green-50", item.is_passed === "N" && "border-red-200 bg-red-50")}>
      <div className="mb-2 flex items-center gap-2">
        <span className="text-sm font-medium">{item.detail_label || item.detail_content}</span>
        {item.is_required === "Y" && <span className="text-xs font-semibold text-destructive">필수</span>}
      </div>
      <div className="flex flex-wrap items-center gap-2">
        {options.map((opt) => (
          <Button
            key={opt}
            size="sm"
            variant={currentValue === opt ? "default" : "outline"}
            className="px-4"
            style={{ height: `${DESIGN.button.height}px`, fontSize: 20 }}
            disabled={disabled}
            onClick={() => handleSelect(opt)}
          >
            {opt}
          </Button>
        ))}
        {saving && <Loader2 className="h-4 w-4 animate-spin" />}
        {item.is_passed === "Y" && !saving && <Badge variant="outline" className="text-xs text-green-600">합격</Badge>}
        {item.is_passed === "N" && !saving && <Badge variant="outline" className="text-xs text-red-600">불합격</Badge>}
      </div>
    </div>
  );
}

// ===== inspect: 텍스트 입력 + 수동 판정 =====

function InspectText({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const [inputVal, setInputVal] = useState(item.result_value ?? "");
  const [judged, setJudged] = useState<string | null>(item.is_passed);

  const handleJudge = (passed: string) => {
    if (disabled) return;
    setJudged(passed);
    onSave(item.id, inputVal || "-", passed, "completed");
  };

  const handleBlur = () => {
    if (!inputVal || disabled) return;
    if (judged) {
      onSave(item.id, inputVal, judged, "completed");
    }
  };

  return (
    <div className={cn("rounded-lg border px-4 py-3", judged === "Y" && "border-green-200 bg-green-50", judged === "N" && "border-red-200 bg-red-50")}>
      <div className="mb-2 flex items-center gap-2">
        <span className="text-sm font-medium">{item.detail_label || item.detail_content}</span>
        {item.is_required === "Y" && <span className="text-xs font-semibold text-destructive">필수</span>}
      </div>
      <div className="flex items-center gap-2.5">
        <Input className="flex-1" style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} value={inputVal} onChange={(e) => setInputVal(e.target.value)} onBlur={handleBlur} disabled={disabled} placeholder="내용 입력" />
        <Button
          size="sm"
          variant={judged === "Y" ? "default" : "outline"}
          className={cn("px-4", judged === "Y" && "bg-green-600 hover:bg-green-700")}
          style={{ height: `${DESIGN.button.height}px`, fontSize: 20 }}
          disabled={disabled}
          onClick={() => handleJudge("Y")}
        >
          합격
        </Button>
        <Button
          size="sm"
          variant={judged === "N" ? "default" : "outline"}
          className={cn("px-4", judged === "N" && "bg-red-600 hover:bg-red-700")}
          style={{ height: `${DESIGN.button.height}px`, fontSize: 20 }}
          disabled={disabled}
          onClick={() => handleJudge("N")}
        >
          불합격
        </Button>
        {saving && <Loader2 className="h-4 w-4 animate-spin" />}
      </div>
    </div>
  );
}

// ========================================
// input: 자유 입력
// ========================================

function InputItem({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const isNumber = item.input_type === "number";
  const [inputVal, setInputVal] = useState(item.result_value ?? "");
  const [keypadOpen, setKeypadOpen] = useState(false);

  const handleBlur = () => {
    if (!inputVal || disabled) return;
    onSave(item.id, inputVal, null, "completed");
  };

  return (
    <div className={cn("rounded-lg border px-4 py-3", item.status === "completed" && "border-green-200 bg-green-50")}>
      <div className="mb-2 text-sm font-medium">{item.detail_label || item.detail_content}</div>
      <div className="flex items-center gap-2.5">
        {isNumber ? (
          <>
            <button
              className={cn(
                "flex-1 rounded-xl border-2 text-center font-bold transition-colors",
                disabled ? "bg-gray-50 border-gray-100" : "bg-white border-gray-200 hover:border-blue-400 active:scale-95",
              )}
              style={{ height: 56, fontSize: 20, color: '#1f2937' }}
              onClick={() => !disabled && setKeypadOpen(true)}
              disabled={disabled}
            >
              {inputVal || <span className="text-gray-300">값 입력</span>}
            </button>
            <NumericKeypadModal
              open={keypadOpen}
              onClose={() => setKeypadOpen(false)}
              onConfirm={(val) => {
                setInputVal(val);
                onSave(item.id, val, null, "completed");
              }}
              title={item.detail_label || item.detail_content}
              unit={item.unit ?? undefined}
              initialValue={inputVal}
            />
          </>
        ) : (
          <Input type="text" className="flex-1" style={{ height: 56, fontSize: `${DESIGN.section.titleSize}px` }} value={inputVal} onChange={(e) => setInputVal(e.target.value)} onBlur={handleBlur} disabled={disabled} placeholder="값 입력" />
        )}
        {saving && <Loader2 className="h-4 w-4 animate-spin" />}
      </div>
    </div>
  );
}

// ========================================
// procedure: 절차 확인
// ========================================

function ProcedureItem({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const checked = item.result_value === "Y";
  return (
    <div className={cn("rounded-lg border px-4 py-3", item.status === "completed" && "border-green-200 bg-green-50")}>
      <div className="mb-2 flex items-center gap-2.5">
        <div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-bold text-primary-foreground">
          {item.detail_sort_order || "?"}
        </div>
        <span className="flex-1 text-sm">{item.detail_content || item.detail_label}</span>
        {item.is_required === "Y" && <span className="text-xs font-semibold text-destructive">필수</span>}
      </div>
      <div className="flex items-center gap-2.5 pl-9">
        <GlossyButton
          variant={checked ? "green" : "gray"}
          onClick={() => {
            if (disabled) return;
            onSave(item.id, checked ? "N" : "Y", null, checked ? "pending" : "completed");
          }}
          disabled={disabled}
          style={{
            minHeight: 56, minWidth: 130, fontSize: 18,
            ...(checked ? { boxShadow: '0 0 0 3px #22c55e, 0 4px 12px rgba(34,197,94,0.3), inset 0 1px 0 rgba(255,255,255,0.4)' } : {}),
          }}
        >
          <Check className="h-5 w-5" /> {checked ? "확인됨" : "확인"}
        </GlossyButton>
        {saving && <Loader2 className="h-4 w-4 animate-spin" />}
      </div>
    </div>
  );
}

// ========================================
// material: 자재/LOT 입력 (바코드 스캔 지원)
// ========================================

function MaterialItem({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  const [inputVal, setInputVal] = useState(item.result_value ?? "");
  const inputRef = useRef<HTMLInputElement>(null);

  const handleSubmit = () => {
    if (!inputVal || disabled) return;
    onSave(item.id, inputVal, null, "completed");
  };

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === "Enter") {
      e.preventDefault();
      handleSubmit();
    }
  };

  return (
    <div className={cn("rounded-lg border px-4 py-3", item.status === "completed" && "border-green-200 bg-green-50")}>
      <div className="mb-2 flex items-center gap-2">
        <Package className="h-4 w-4 text-muted-foreground" />
        <span className="text-sm font-medium">{item.detail_label || item.detail_content}</span>
        {item.is_required === "Y" && <span className="text-xs font-semibold text-destructive">필수</span>}
      </div>
      <div className="flex items-center gap-2.5">
        <Input
          ref={inputRef}
          className="flex-1"
          style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }}
          value={inputVal}
          onChange={(e) => setInputVal(e.target.value)}
          onKeyDown={handleKeyDown}
          disabled={disabled}
          placeholder="LOT/바코드 스캔 또는 입력"
        />
        <Button size="sm" variant="outline" className="px-4" style={{ height: `${DESIGN.button.height}px`, fontSize: 20 }} onClick={handleSubmit} disabled={disabled || !inputVal}>
          확인
        </Button>
        {saving && <Loader2 className="h-4 w-4 animate-spin" />}
        {item.status === "completed" && !saving && <Badge variant="outline" className="text-xs text-green-600">투입</Badge>}
      </div>
    </div>
  );
}

// ========================================
// result: 실적 입력 (양품/불량/불량유형)
// ========================================

function ResultInputItem({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
  let savedData: { good?: string; defect?: string; defectType?: string; note?: string } = {};
  try {
    savedData = JSON.parse(item.result_value ?? "{}");
  } catch {
    savedData = {};
  }

  const [good, setGood] = useState(savedData.good ?? "");
  const [defect, setDefect] = useState(savedData.defect ?? "");
  const [defectType, setDefectType] = useState(savedData.defectType ?? "");
  const [note, setNote] = useState(savedData.note ?? "");

  const handleSave = () => {
    if (disabled) return;
    const val = JSON.stringify({ good, defect, defectType, note });
    onSave(item.id, val, null, "completed");
  };

  const total = (parseInt(good, 10) || 0) + (parseInt(defect, 10) || 0);

  return (
    <div className={cn("rounded-lg border px-4 py-3 space-y-3", item.status === "completed" && "border-green-200 bg-green-50")}>
      <div className="text-sm font-medium">{item.detail_label || item.detail_content || "실적 입력"}</div>

      <div className="flex items-center gap-4">
        <div className="flex items-center gap-2">
          <span className="text-xs text-muted-foreground">양품</span>
          <Input type="number" className="w-24" style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} value={good} onChange={(e) => setGood(e.target.value)} disabled={disabled} placeholder="0" />
        </div>
        <div className="flex items-center gap-2">
          <span className="text-xs text-muted-foreground">불량</span>
          <Input type="number" className="w-24" style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} value={defect} onChange={(e) => setDefect(e.target.value)} disabled={disabled} placeholder="0" />
        </div>
        <span className="text-xs text-muted-foreground">합계: {total}</span>
      </div>

      {parseInt(defect, 10) > 0 && (
        <div className="flex items-center gap-2">
          <span className="text-xs text-muted-foreground">불량유형</span>
          <Input className="flex-1" style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} value={defectType} onChange={(e) => setDefectType(e.target.value)} disabled={disabled} placeholder="스크래치, 치수불량 등" />
        </div>
      )}

      <div className="flex items-center gap-2">
        <span className="text-xs text-muted-foreground">비고</span>
        <Input className="flex-1" style={{ height: `${DESIGN.input.height}px`, fontSize: `${DESIGN.section.titleSize}px` }} value={note} onChange={(e) => setNote(e.target.value)} disabled={disabled} placeholder="비고 (선택)" />
      </div>

      <div className="flex items-center gap-2.5">
        <Button size="sm" variant="outline" className="px-4" style={{ height: `${DESIGN.button.height}px`, fontSize: 20 }} onClick={handleSave} disabled={disabled}>
          실적 등록
        </Button>
        {saving && <Loader2 className="h-4 w-4 animate-spin" />}
        {item.status === "completed" && !saving && <Badge variant="outline" className="text-xs text-green-600">등록됨</Badge>}
      </div>
    </div>
  );
}
