'use client';

/**
 * 자재투입 모달 (로트 선택 기반)
 *
 * 로트를 체크박스로 선택하면 필요수량만큼 FIFO 순서로 자동 배분합니다.
 * 같은 품목의 여러 로트를 조합하여 필요수량을 충족시킬 수 있습니다.
 *
 * 기능:
 * - 기투입 LOT 표시 및 수정 (replace 모드)
 * - 선택완료 배지
 * - 필요수량 배정 완료 시에만 투입 가능
 * - FIFO 자동입력 버튼
 */

import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { Loader2, Check, Zap, Info, Search, X } from 'lucide-react';
import { ContentSkeleton } from '@/components/ui/skeleton';
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table';
import { toast } from 'sonner';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { getMaterialsForWorkOrder, registerMaterialInput, getMaterialsForItem, registerMaterialInputForItem, searchStockByCode, forceCreateReceiving, searchItems, type MaterialForInput, type MaterialForItemInput, type StockSearchResult, type ItemSearchResult } from './actions';
import type { WorkOrder } from '../ProductionDashboard/types';
import type { MaterialInput } from './types';
import { formatNumber } from '@/lib/utils/amount';

interface MaterialInputModalProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  order: WorkOrder | null;
  workOrderItemId?: number;       // 개소(작업지시품목) ID (첫 번째 item, 호환용)
  workOrderItemIds?: number[];    // 개소 내 모든 작업지시품목 IDs (절곡 등 복수 item)
  workOrderItemName?: string;     // 개소명 (모달 헤더 표시용)
  onComplete?: () => void;
  isCompletionFlow?: boolean;
  onSaveMaterials?: (orderId: string, materials: MaterialInput[]) => void;
  savedMaterials?: MaterialInput[];
}

interface MaterialGroup {
  itemId: number;
  groupKey: string;              // 그룹 식별 키 (itemId 또는 itemId_woItemId)
  materialName: string;
  materialCode: string;
  requiredQty: number;
  effectiveRequiredQty: number;  // 남은 필요수량 (이미 투입분 차감)
  alreadyInputted: number;       // 이미 투입된 수량
  unit: string;
  lots: MaterialForInput[];
  // dynamic_bom 추가 정보
  workOrderItemId?: number;
  lotPrefix?: string;
  partType?: string;
  category?: string;
}

const fmtQty = (v: number) => formatNumber(parseFloat(String(v)));

export function MaterialInputModal({
  open,
  onOpenChange,
  order,
  workOrderItemId,
  workOrderItemIds,
  workOrderItemName,
  onComplete,
  isCompletionFlow = false,
  onSaveMaterials,
}: MaterialInputModalProps) {
  const [materials, setMaterials] = useState<MaterialForInput[]>([]);
  const [selectedLotKeys, setSelectedLotKeys] = useState<Set<string>>(new Set());
  const [manualAllocations, setManualAllocations] = useState<Map<string, number>>(new Map());
  const [isLoading, setIsLoading] = useState(false);
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [showUnfulfilledOnly, setShowUnfulfilledOnly] = useState(false);
  const materialsLoadedRef = useRef(false);

  // 재공품 여부 판별 — 재공품이면 검색 시 원자재(RM)만 조회
  const isWipOrder = useMemo(() => {
    if (!order) return false;
    return order.projectName === '재고생산' || order.salesOrderNo?.startsWith('STK');
  }, [order]);

  // 재고 검색 상태
  const [searchOpenGroup, setSearchOpenGroup] = useState<string | null>(null);
  const [searchQuery, setSearchQuery] = useState('');
  const [searchResults, setSearchResults] = useState<StockSearchResult[]>([]);
  const [isSearching, setIsSearching] = useState(false);

  // 품목 검색 결과 (재고 없는 품목 포함)
  const [itemSearchResults, setItemSearchResults] = useState<ItemSearchResult[]>([]);

  const handleStockSearch = useCallback(async (query: string | undefined) => {
    if (!query?.trim()) { setSearchResults([]); setItemSearchResults([]); return; }
    setIsSearching(true);
    // 재공품: 원자재(RM)만 검색, 일반: 전체 검색
    const typeFilter = isWipOrder ? 'RM' : undefined;
    const [stockResult, itemResult] = await Promise.all([
      searchStockByCode(query.trim(), typeFilter),
      searchItems(query.trim(), typeFilter),
    ]);
    if (stockResult.success) setSearchResults(stockResult.data);
    if (itemResult.success) {
      // 재고 검색 결과에 이미 있는 품목은 제외
      const stockItemIds = new Set(stockResult.data?.map(s => s.itemId) || []);
      setItemSearchResults(itemResult.data.filter(i => !stockItemIds.has(i.itemId)));
    }
    setIsSearching(false);
  }, [isWipOrder]);

  const [isForceCreating, setIsForceCreating] = useState(false);

  // 목업 자재 데이터 (개발용)
  const MOCK_MATERIALS: MaterialForInput[] = Array.from({ length: 5 }, (_, i) => ({
    stockLotId: 100 + i,
    itemId: 200 + i,
    lotNo: `LOT-2026-${String(i + 1).padStart(3, '0')}`,
    materialCode: `MAT-${String(i + 1).padStart(3, '0')}`,
    materialName: `자재 ${i + 1}`,
    specification: '',
    unit: 'EA',
    requiredQty: 100,
    lotAvailableQty: 500 - i * 50,
    fifoRank: i + 1,
  }));

  // 로트 키 생성 (그룹별 독립 — 같은 LOT가 여러 그룹에 있어도 구분)
  const getLotKey = useCallback((material: MaterialForInput, groupKey: string) =>
    `${String(material.stockLotId ?? `item-${material.itemId}`)}__${groupKey}`, []);

  // 기투입 LOT 존재 여부
  const hasPreInputted = useMemo(() => {
    return materials.some(m => {
      const itemInput = m as unknown as MaterialForItemInput;
      return (itemInput.lotInputtedQty ?? 0) > 0;
    });
  }, [materials]);

  // 품목별 그룹핑 (BOM 엔트리별 고유키 사용 — 같은 item_id라도 category+partType 다르면 별도 그룹)
  const materialGroups: MaterialGroup[] = useMemo(() => {
    const groups = new Map<string, MaterialForInput[]>();
    for (const m of materials) {
      const itemInput = m as unknown as MaterialForItemInput;
      const groupKey = itemInput.bomGroupKey
        ?? (m.workOrderItemId ? `${m.itemId}_${m.workOrderItemId}` : String(m.itemId));
      const existing = groups.get(groupKey) || [];
      existing.push(m);
      groups.set(groupKey, existing);
    }
    // 작업일지와 동일한 카테고리 순서
    const categoryOrder: Record<string, number> = {
      guideRail: 0, bottomBar: 1, shutterBox: 2, smokeBarrier: 3,
    };

    return Array.from(groups.entries()).map(([groupKey, lots]) => {
      const first = lots[0];
      const itemInput = first as unknown as MaterialForItemInput;
      const alreadyInputted = itemInput.alreadyInputted ?? 0;
      const effectiveRequiredQty = Math.max(0, itemInput.remainingRequiredQty ?? first.requiredQty);
      return {
        itemId: first.itemId,
        groupKey,
        materialName: first.materialName,
        materialCode: first.materialCode,
        requiredQty: first.requiredQty,
        effectiveRequiredQty,
        alreadyInputted,
        unit: first.unit,
        lots: lots.sort((a, b) => a.fifoRank - b.fifoRank),
        workOrderItemId: first.workOrderItemId,
        lotPrefix: first.lotPrefix,
        partType: first.partType,
        category: first.category,
      };
    }).sort((a, b) => {
      const catA = categoryOrder[a.category ?? ''] ?? 99;
      const catB = categoryOrder[b.category ?? ''] ?? 99;
      return catA - catB;
    });
  }, [materials]);

  // 그룹별 목표 수량 (기투입 있으면 전체 필요수량, 없으면 남은 필요수량)
  const getGroupTargetQty = useCallback((group: MaterialGroup) => {
    return group.alreadyInputted > 0 ? group.requiredQty : group.effectiveRequiredQty;
  }, []);

  // 배정 수량 계산 (manual 우선 → 나머지 FIFO 자동배분, 물리LOT 교차 추적)
  const allocations = useMemo(() => {
    const result = new Map<string, number>();

    // 물리 LOT별 실제 가용량: lotAvailableQty + SUM(모든 그룹의 lotInputtedQty)
    const physicalAvail = new Map<number, number>();
    const lotBaseAvail = new Map<number, number>();
    for (const group of materialGroups) {
      for (const lot of group.lots) {
        if (!lot.stockLotId) continue;
        const itemInput = lot as unknown as MaterialForItemInput;
        const inputted = itemInput.lotInputtedQty ?? 0;
        if (!lotBaseAvail.has(lot.stockLotId)) {
          lotBaseAvail.set(lot.stockLotId, lot.lotAvailableQty);
          physicalAvail.set(lot.stockLotId, lot.lotAvailableQty + inputted);
        } else {
          physicalAvail.set(lot.stockLotId, (physicalAvail.get(lot.stockLotId) ?? 0) + inputted);
        }
      }
    }
    const physicalUsed = new Map<number, number>();

    for (const group of materialGroups) {
      const targetQty = getGroupTargetQty(group);
      let remaining = targetQty;

      // 1차: manual allocations 적용
      for (const lot of group.lots) {
        const lotKey = getLotKey(lot, group.groupKey);
        if (selectedLotKeys.has(lotKey) && lot.stockLotId && manualAllocations.has(lotKey)) {
          const val = manualAllocations.get(lotKey)!;
          result.set(lotKey, val);
          remaining -= val;
          physicalUsed.set(lot.stockLotId, (physicalUsed.get(lot.stockLotId) || 0) + val);
        }
      }

      // 2차: non-manual 선택 로트 FIFO 자동배분 (물리LOT 교차 추적)
      for (const lot of group.lots) {
        const lotKey = getLotKey(lot, group.groupKey);
        if (selectedLotKeys.has(lotKey) && lot.stockLotId && !manualAllocations.has(lotKey)) {
          const totalAvail = physicalAvail.get(lot.stockLotId) ?? lot.lotAvailableQty;
          const used = physicalUsed.get(lot.stockLotId) || 0;
          const effectiveAvail = Math.max(0, totalAvail - used);
          const alloc = remaining > 0 ? Math.min(effectiveAvail, remaining) : 0;
          result.set(lotKey, alloc);
          remaining -= alloc;
          if (alloc > 0) {
            physicalUsed.set(lot.stockLotId, used + alloc);
          }
        }
      }
    }
    return result;
  }, [materialGroups, selectedLotKeys, manualAllocations, getLotKey, getGroupTargetQty]);

  // 전체 배정 완료 여부
  const allGroupsFulfilled = useMemo(() => {
    if (materialGroups.length === 0) return false;
    return materialGroups.every((group) => {
      const targetQty = getGroupTargetQty(group);
      if (targetQty <= 0) return true;
      const allocated = group.lots.reduce(
        (sum, lot) => sum + (allocations.get(getLotKey(lot, group.groupKey)) || 0),
        0
      );
      return allocated >= targetQty;
    });
  }, [materialGroups, allocations, getLotKey, getGroupTargetQty]);

  // 배정된 항목 존재 여부
  const hasAnyAllocation = useMemo(() => {
    return Array.from(allocations.values()).some((v) => v > 0);
  }, [allocations]);

  // 로트 선택/해제
  const toggleLot = useCallback((lotKey: string) => {
    setSelectedLotKeys((prev) => {
      const next = new Set(prev);
      if (next.has(lotKey)) {
        next.delete(lotKey);
        setManualAllocations(prev => {
          const n = new Map(prev);
          n.delete(lotKey);
          return n;
        });
      } else {
        next.add(lotKey);
      }
      return next;
    });
  }, []);

  // 수량 수동 변경
  const handleAllocationChange = useCallback((lotKey: string, value: number, maxAvailable: number) => {
    const clamped = Math.max(0, Math.min(value, maxAvailable));
    setManualAllocations(prev => {
      const next = new Map(prev);
      next.set(lotKey, clamped);
      return next;
    });
  }, []);

  // FIFO 자동입력 (물리LOT 가용량 교차 추적 — 동일 stockLotId의 실제 잔량 공유)
  const handleAutoFill = useCallback(() => {
    const newSelected = new Set<string>();
    const newAllocations = new Map<string, number>();

    // 물리 LOT별 실제 가용량 계산: lotAvailableQty + SUM(모든 그룹의 lotInputtedQty)
    // replace 모드에서 기투입분이 복원되므로 모든 그룹의 기투입을 합산
    const physicalAvail = new Map<number, number>();
    const lotBaseAvail = new Map<number, number>();

    for (const group of materialGroups) {
      for (const lot of group.lots) {
        if (!lot.stockLotId) continue;
        const itemInput = lot as unknown as MaterialForItemInput;
        const inputted = itemInput.lotInputtedQty ?? 0;
        if (!lotBaseAvail.has(lot.stockLotId)) {
          lotBaseAvail.set(lot.stockLotId, lot.lotAvailableQty);
          physicalAvail.set(lot.stockLotId, lot.lotAvailableQty + inputted);
        } else {
          physicalAvail.set(lot.stockLotId, (physicalAvail.get(lot.stockLotId) ?? 0) + inputted);
        }
      }
    }

    // 물리 LOT 사용량 추적
    const physicalUsed = new Map<number, number>();

    // 2차: 그룹별 FIFO 배정
    for (const group of materialGroups) {
      const targetQty = getGroupTargetQty(group);
      if (targetQty <= 0) continue;

      let remaining = targetQty;
      for (const lot of group.lots) {
        if (!lot.stockLotId || remaining <= 0) continue;
        const lotKey = getLotKey(lot, group.groupKey);
        const totalAvail = physicalAvail.get(lot.stockLotId) ?? lot.lotAvailableQty;
        const used = physicalUsed.get(lot.stockLotId) || 0;
        const effectiveAvail = Math.max(0, totalAvail - used);
        const alloc = Math.min(effectiveAvail, remaining);
        if (alloc > 0) {
          newSelected.add(lotKey);
          newAllocations.set(lotKey, alloc);
          remaining -= alloc;
          physicalUsed.set(lot.stockLotId, used + alloc);
        }
      }
    }

    setSelectedLotKeys(newSelected);
    setManualAllocations(newAllocations);
  }, [materialGroups, getLotKey, getGroupTargetQty]);

  // API로 자재 목록 로드
  const loadMaterials = useCallback(async () => {
    if (!order) return;

    setIsLoading(true);
    materialsLoadedRef.current = false;
    try {
      // 목업 아이템인 경우 목업 자재 데이터 사용
      if (order.id.startsWith('mock-')) {
        setMaterials(MOCK_MATERIALS);
        setIsLoading(false);
        materialsLoadedRef.current = true;
        return;
      }

      // 개소 대표 아이템 1개만 조회 (dynamic_bom은 개소 내 모든 아이템에 동일하게 저장됨)
      const itemId = workOrderItemId
        ?? (workOrderItemIds && workOrderItemIds.length > 0 ? workOrderItemIds[0] : null);

      if (itemId) {
        const result = await getMaterialsForItem(order.id, itemId);
        if (result.success) {
          const tagged = result.data.map((m) => ({
            ...m,
            workOrderItemId: m.workOrderItemId || itemId,
          }));
          setMaterials(tagged);
          materialsLoadedRef.current = true;
        } else {
          toast.error(result.error || '자재 목록 조회에 실패했습니다.');
        }
      } else {
        // 전체 작업지시 기준 조회
        const result = await getMaterialsForWorkOrder(order.id);
        if (result.success) {
          setMaterials(result.data);
          materialsLoadedRef.current = true;
        } else {
          toast.error(result.error || '자재 목록 조회에 실패했습니다.');
        }
      }
    } catch (error) {
      if (isNextRedirectError(error)) throw error;
      console.error('[MaterialInputModal] loadMaterials error:', error);
      toast.error('자재 목록 로드 중 오류가 발생했습니다.');
    } finally {
      setIsLoading(false);
    }
  }, [order, workOrderItemId, workOrderItemIds]);

  // [개발전용] 입고 강제생성 핸들러
  const handleForceReceiving = useCallback(async (itemId: number, itemCode: string) => {
    setIsForceCreating(true);
    const result = await forceCreateReceiving(itemId, 100);
    if (result.success && result.data) {
      const d = result.data as Record<string, unknown>;
      const rm = d.rm_item_code || d.rm_item_name;
      const pt = d.pt_item_code || d.item_code;
      const label = rm ? `원자재 ${rm} → 재공품 ${pt}` : String(pt);
      toast.success(`${label} 입고 완료 (LOT: ${d.lot_no}, ${d.qty}EA)`);
      handleStockSearch(itemCode);
      loadMaterials();
    } else {
      toast.error(result.error || '입고 생성 실패');
    }
    setIsForceCreating(false);
  }, [handleStockSearch, loadMaterials]);

  // 모달이 열릴 때 데이터 로드 + 선택 초기화
  useEffect(() => {
    if (open && order) {
      loadMaterials();
      setSelectedLotKeys(new Set());
      setManualAllocations(new Map());
    }
  }, [open, order, loadMaterials]);

  // 자재 로드 후 기투입 LOT 자동 선택 (그룹별 독립 처리)
  useEffect(() => {
    if (!materialsLoadedRef.current || materials.length === 0 || materialGroups.length === 0) return;

    const preSelected = new Set<string>();
    const preAllocations = new Map<string, number>();

    for (const group of materialGroups) {
      for (const m of group.lots) {
        const itemInput = m as unknown as MaterialForItemInput;
        const lotInputted = itemInput.lotInputtedQty ?? 0;
        if (lotInputted > 0 && m.stockLotId) {
          const lotKey = getLotKey(m, group.groupKey);
          preSelected.add(lotKey);
          preAllocations.set(lotKey, lotInputted);
        }
      }
    }

    if (preSelected.size > 0) {
      setSelectedLotKeys(prev => new Set([...prev, ...preSelected]));
      setManualAllocations(prev => new Map([...prev, ...preAllocations]));
    }
    // 한 번만 실행하도록 ref 초기화
    materialsLoadedRef.current = false;
  }, [materials, materialGroups, getLotKey]);

  // 투입 등록
  const handleSubmit = async () => {
    if (!order) return;

    // 배분된 로트를 그룹별 개별 엔트리로 추출 (bom_group_key 포함)
    const inputs: { stock_lot_id: number; qty: number; bom_group_key: string }[] = [];
    for (const group of materialGroups) {
      for (const lot of group.lots) {
        const lotKey = getLotKey(lot, group.groupKey);
        const allocQty = allocations.get(lotKey) || 0;
        if (allocQty > 0 && lot.stockLotId) {
          inputs.push({
            stock_lot_id: lot.stockLotId,
            qty: allocQty,
            bom_group_key: group.groupKey,
          });
        }
      }
    }

    if (inputs.length === 0) {
      toast.error('투입할 로트를 선택해주세요.');
      return;
    }

    setIsSubmitting(true);
    try {
      // 대표 아이템 기준 자재 투입 등록
      let result: { success: boolean; error?: string };
      const targetItemId = workOrderItemId
        ?? (workOrderItemIds && workOrderItemIds.length > 0 ? workOrderItemIds[0] : null);

      if (targetItemId) {
        // 기투입 LOT 있으면 replace 모드 (기존 투입 삭제 후 재등록)
        result = await registerMaterialInputForItem(order.id, targetItemId, inputs, hasPreInputted);
      } else {
        result = await registerMaterialInput(order.id, inputs);
      }

      if (result.success) {
        toast.success('자재 투입이 등록되었습니다.');

        if (onSaveMaterials) {
          // 표시용: 같은 LOT는 합산 (자재투입목록 UI)
          const lotTotals = new Map<number, number>();
          for (const inp of inputs) {
            lotTotals.set(inp.stock_lot_id, (lotTotals.get(inp.stock_lot_id) || 0) + inp.qty);
          }
          const savedList: MaterialInput[] = [];
          const processedLotIds = new Set<number>();
          for (const group of materialGroups) {
            for (const lot of group.lots) {
              if (!lot.stockLotId || processedLotIds.has(lot.stockLotId)) continue;
              const totalQty = lotTotals.get(lot.stockLotId) || 0;
              if (totalQty > 0) {
                processedLotIds.add(lot.stockLotId);
                savedList.push({
                  id: String(lot.stockLotId),
                  lotNo: lot.lotNo || '',
                  materialName: lot.materialName,
                  quantity: lot.lotAvailableQty,
                  unit: lot.unit,
                  inputQuantity: totalQty,
                });
              }
            }
          }
          onSaveMaterials(order.id, savedList);
        }

        resetAndClose();

        if (isCompletionFlow && onComplete) {
          onComplete();
        }
      } else {
        toast.error(result.error || '자재 투입 등록에 실패했습니다.');
      }
    } catch (error) {
      if (isNextRedirectError(error)) throw error;
      console.error('[MaterialInputModal] handleSubmit error:', error);
      toast.error('자재 투입 등록 중 오류가 발생했습니다.');
    } finally {
      setIsSubmitting(false);
    }
  };

  const resetAndClose = () => {
    setSelectedLotKeys(new Set());
    setManualAllocations(new Map());
    onOpenChange(false);
  };

  if (!order) return null;

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="!max-w-3xl p-0 gap-0 max-h-[85vh] flex flex-col">
        {/* 헤더 */}
        <DialogHeader className="p-6 pb-4 shrink-0">
          <DialogTitle className="text-xl font-semibold">
            자재 투입{workOrderItemName ? ` - ${workOrderItemName}` : ''}
          </DialogTitle>
          <div className="flex items-center justify-between mt-1">
            <div className="flex items-center gap-2">
              <p className="text-sm text-gray-500">
                로트를 선택하면 필요수량만큼 자동 배분됩니다.
              </p>
              {!isLoading && materialGroups.length > 0 && (() => {
                const fulfilled = materialGroups.filter((g) => {
                  const target = getGroupTargetQty(g);
                  if (target <= 0 && g.alreadyInputted <= 0) return true;
                  const allocated = g.lots.reduce((sum, lot) => sum + (allocations.get(getLotKey(lot, g.groupKey)) || 0), 0);
                  return allocated >= target;
                }).length;
                const total = materialGroups.length;
                return (
                  <button
                    onClick={() => setShowUnfulfilledOnly(prev => !prev)}
                    className={cn(
                      "text-xs font-semibold px-2.5 py-1 rounded-full cursor-pointer transition-colors",
                      showUnfulfilledOnly
                        ? "bg-amber-200 text-amber-800 ring-2 ring-amber-400"
                        : fulfilled === total ? "bg-emerald-100 text-emerald-700 hover:bg-emerald-200" : "bg-amber-100 text-amber-700 hover:bg-amber-200"
                    )}
                  >
                    {fulfilled === total ? <Check className="h-3 w-3 inline mr-0.5" /> : null}
                    {showUnfulfilledOnly ? `미배정 ${total - fulfilled}건` : `${fulfilled} / ${total} 배정완료`}
                  </button>
                );
              })()}
            </div>
            {!isLoading && materials.length > 0 && (
              <button
                onClick={handleAutoFill}
                className="flex items-center gap-1 px-3 py-1.5 text-xs font-semibold bg-blue-100 text-blue-700 rounded-full hover:bg-blue-200 transition-colors shrink-0"
              >
                <Zap className="h-3 w-3" />
                자동입력
              </button>
            )}
          </div>
        </DialogHeader>

        <div className="px-6 pb-6 space-y-4 flex-1 min-h-0 flex flex-col">
          {/* 자재 목록 */}
          {isLoading ? (
            <ContentSkeleton type="table" rows={4} />
          ) : materials.length === 0 ? (
            <div className="border rounded-lg overflow-hidden">
              <div className="py-8 text-center text-sm text-gray-500">
                이 공정에 배정된 자재가 없습니다.
              </div>
              {/* 자재 없을 때도 재고 검색 + 강제입고 가능 */}
              <div className="px-4 py-3 bg-blue-50 border-t space-y-2">
                <div className="flex items-center gap-2">
                  <Search className="h-3.5 w-3.5 text-blue-500 shrink-0" />
                  <input
                    type="text"
                    value={searchQuery}
                    onChange={(e) => setSearchQuery(e.target.value)}
                    onKeyDown={(e) => e.key === 'Enter' && handleStockSearch(searchQuery)}
                    placeholder={isWipOrder ? "원자재 코드 또는 자재명으로 검색" : "품목코드 또는 자재명으로 재고 검색"}
                    className="flex-1 text-xs px-2 py-1.5 border rounded bg-white"
                  />
                  <Button size="sm" variant="outline" className="h-7 text-xs" onClick={() => handleStockSearch(searchQuery)}>
                    {isSearching ? <Loader2 className="h-3 w-3 animate-spin" /> : '검색'}
                  </Button>
                </div>
                {searchResults.length > 0 ? (
                  <div className="space-y-1">
                    <p className="text-[11px] text-blue-600 font-medium">{searchResults.length}건 검색됨:</p>
                    {searchResults.map((s, si) => (
                      <div key={`${s.itemId}-${si}`} className={cn(
                        "text-[11px] px-2 py-1.5 rounded border",
                        s.availableQty > 0 ? "bg-white border-emerald-200" : "bg-gray-50 border-gray-200"
                      )}>
                        <div className="flex justify-between items-center">
                          <span className="font-medium">{s.itemCode}</span>
                          <span className={s.availableQty > 0 ? "text-emerald-600 font-semibold" : "text-gray-400"}>
                            가용 {formatNumber(s.availableQty)} EA
                          </span>
                        </div>
                        <div className="text-gray-500">{s.itemName}</div>
                        {s.lots.length > 0 && s.lots.slice(0, 3).map((l, li) => (
                          <div key={li} className="text-[10px] text-gray-400 ml-2">
                            LOT {l.lotNo} | {formatNumber(l.availableQty)} EA | FIFO #{l.fifoOrder}
                          </div>
                        ))}
                      </div>
                    ))}
                  </div>
                ) : isSearching ? (
                  <p className="text-[11px] text-gray-400 flex items-center gap-1"><Loader2 className="h-3 w-3 animate-spin" />검색 중...</p>
                ) : searchQuery && searchResults.length === 0 && itemSearchResults.length === 0 ? (
                  <p className="text-[11px] text-red-500">검색 결과 없음</p>
                ) : null}
                {/* 재고 없는 품목 목록 (items 테이블에만 존재) */}
                {itemSearchResults.length > 0 && (
                  <div className="space-y-1">
                    <p className="text-[11px] text-orange-600 font-medium">재고 없는 품목 {itemSearchResults.length}건:</p>
                    {itemSearchResults.map((item, idx) => (
                      <div key={`item-${item.itemId}-${idx}`} className="text-[11px] px-2 py-1.5 rounded border bg-orange-50 border-orange-200 flex items-center justify-between">
                        <div>
                          <span className="font-medium">{item.itemCode}</span>
                          <span className="text-gray-500 ml-1">{item.itemName}</span>
                          <span className="text-[10px] text-gray-400 ml-1">({item.itemType})</span>
                        </div>
                        <Button
                          size="sm"
                          variant="outline"
                          className="h-6 text-[10px] px-2 border-orange-300 text-orange-600 hover:bg-orange-100"
                          disabled={isForceCreating}
                          onClick={() => handleForceReceiving(item.itemId, item.itemCode)}
                        >
                          {isForceCreating ? <Loader2 className="h-3 w-3 animate-spin" /> : <Zap className="h-3 w-3 mr-0.5" />}
                          강제입고
                        </Button>
                      </div>
                    ))}
                  </div>
                )}
              </div>
            </div>
          ) : (
            <div className="space-y-4 flex-1 overflow-y-auto min-h-0">
              {materialGroups.filter((group) => {
                if (!showUnfulfilledOnly) return true;
                const target = getGroupTargetQty(group);
                if (target <= 0 && group.alreadyInputted <= 0) return false;
                const allocated = group.lots.reduce((sum, lot) => sum + (allocations.get(getLotKey(lot, group.groupKey)) || 0), 0);
                return allocated < target;
              }).map((group, groupIdx) => {
                // 같은 카테고리 내 순번 계산 (①②③...)
                const categoryIndex = group.category
                  ? materialGroups.slice(0, groupIdx).filter(g => g.category === group.category).length
                  : -1;
                const circledNumbers = ['①','②','③','④','⑤','⑥','⑦','⑧','⑨','⑩'];
                const circledNum = categoryIndex >= 0 && categoryIndex < circledNumbers.length
                  ? circledNumbers[categoryIndex] : '';

                const targetQty = getGroupTargetQty(group);
                const groupAllocated = group.lots.reduce(
                  (sum, lot) => sum + (allocations.get(getLotKey(lot, group.groupKey)) || 0),
                  0
                );
                const isGroupComplete = targetQty <= 0 && group.alreadyInputted <= 0;
                const isFulfilled = isGroupComplete || groupAllocated >= targetQty;

                return (
                  <div key={group.groupKey} className="border rounded-lg overflow-hidden">
                    {/* 품목 그룹 헤더 */}
                    <div className="flex flex-wrap items-center justify-between gap-1.5 px-4 py-2.5 bg-gray-50 border-b">
                      <div className="flex items-center gap-2 flex-wrap min-w-0">
                        {group.category && (
                          <span className={`text-[10px] px-1.5 py-0.5 rounded font-medium ${
                            group.category === 'guideRail' ? 'bg-blue-100 text-blue-700' :
                            group.category === 'bottomBar' ? 'bg-green-100 text-green-700' :
                            group.category === 'shutterBox' ? 'bg-orange-100 text-orange-700' :
                            group.category === 'smokeBarrier' ? 'bg-red-100 text-red-700' :
                            'bg-gray-100 text-gray-600'
                          }`}>
                            {group.category === 'guideRail' ? '가이드레일' :
                             group.category === 'bottomBar' ? '하단마감재' :
                             group.category === 'shutterBox' ? '셔터박스' :
                             group.category === 'smokeBarrier' ? '연기차단재' :
                             group.category}
                          </span>
                        )}
                        {group.partType && (
                          <span className="text-[10px] px-1.5 py-0.5 rounded bg-gray-200 text-gray-600 font-medium">
                            {circledNum}{group.partType}
                          </span>
                        )}
                        <span className="text-sm font-semibold text-gray-900">
                          {group.materialName}
                        </span>
                        {group.materialCode && (
                          <span className="text-xs text-gray-400">
                            {group.materialCode}
                          </span>
                        )}
                        {/* 매칭 정보 말풍선 */}
                        <span className="relative group/info inline-flex">
                          <Info className={cn(
                            "h-3.5 w-3.5 cursor-help",
                            group.lots.some(l => l.stockLotId !== null) ? "text-blue-400" : "text-red-400"
                          )} />
                          <span className="absolute left-0 top-5 z-50 hidden group-hover/info:block w-64 p-2.5 rounded-lg shadow-lg border bg-white text-xs text-gray-700 leading-relaxed">
                            <span className="font-semibold text-gray-900 block mb-1">자재 매칭 정보</span>
                            <span className="block text-[11px] text-gray-500 mb-1.5">
                              품목코드: {group.materialCode || '-'}<br/>
                              규격: {group.lots[0]?.specification || '-'}<br/>
                              매칭기준: 품목코드 + 규격 일치하는 입고 LOT 검색 (FIFO)
                            </span>
                            {(() => {
                              const availableLots = group.lots.filter(l => l.stockLotId !== null);
                              const noStockLots = group.lots.filter(l => l.stockLotId === null);
                              if (availableLots.length > 0) {
                                return (
                                  <span className="block">
                                    <span className="text-emerald-600 font-medium">
                                      ✓ {availableLots.length}건 매칭 완료
                                    </span>
                                    {availableLots.map((l, i) => (
                                      <span key={i} className="block text-[11px] text-gray-500 ml-2">
                                        LOT {l.lotNo} | 가용 {formatNumber(l.lotAvailableQty)}{group.unit} | FIFO #{l.fifoRank}
                                      </span>
                                    ))}
                                  </span>
                                );
                              }
                              return (
                                <span className="block text-red-600 font-medium">
                                  ✗ 매칭 가능한 입고 LOT 없음
                                  <span className="block text-[11px] text-red-500 font-normal mt-0.5">
                                    → [{group.materialCode}] {group.lots[0]?.specification || ''} 자재를 입고해주세요
                                  </span>
                                </span>
                              );
                            })()}
                          </span>
                        </span>
                      </div>
                      <div className="flex items-center gap-2 flex-wrap">
                        <span className="text-xs text-gray-500">
                          {group.alreadyInputted > 0 ? (
                            <>
                              필요:{' '}
                              <span className="font-semibold text-gray-900">
                                {fmtQty(group.requiredQty)}
                              </span>{' '}
                              {group.unit}
                              <span className="ml-1 text-blue-500">
                                (기투입: {fmtQty(group.alreadyInputted)})
                              </span>
                            </>
                          ) : (
                            <>
                              필요:{' '}
                              <span className="font-semibold text-gray-900">
                                {fmtQty(group.requiredQty)}
                              </span>{' '}
                              {group.unit}
                            </>
                          )}
                        </span>
                        <span
                          className={`text-xs font-semibold px-2 py-0.5 rounded-full flex items-center gap-1 ${
                            isFulfilled
                              ? 'bg-emerald-100 text-emerald-700'
                              : groupAllocated > 0
                                ? 'bg-amber-100 text-amber-700'
                                : 'bg-gray-100 text-gray-500'
                          }`}
                        >
                          {isFulfilled ? (
                            <>
                              <Check className="h-3 w-3" />
                              배정 완료
                            </>
                          ) : (
                            `${fmtQty(groupAllocated)} / ${fmtQty(targetQty)}`
                          )}
                        </span>
                      </div>
                    </div>

                    {/* 로트 테이블 */}
                    <div className="overflow-x-auto">
                    <Table>
                      <TableHeader>
                        <TableRow>
                          <TableHead className="text-center w-24">선택</TableHead>
                          <TableHead className="text-center">로트번호</TableHead>
                          <TableHead className="text-center">가용수량</TableHead>
                          <TableHead className="text-center">단위</TableHead>
                          <TableHead className="text-center">배정수량</TableHead>
                        </TableRow>
                      </TableHeader>
                      <TableBody>
                        {group.lots.map((lot, idx) => {
                          const lotKey = getLotKey(lot, group.groupKey);
                          const hasStock = lot.stockLotId !== null;
                          const isSelected = selectedLotKeys.has(lotKey);
                          const allocated = allocations.get(lotKey) || 0;
                          const itemInput = lot as unknown as MaterialForItemInput;
                          const lotInputted = itemInput.lotInputtedQty ?? 0;
                          const isPreInputted = lotInputted > 0;
                          // 가용수량 = 현재 가용 + 기투입분 (replace 시 복원되므로)
                          const effectiveAvailable = lot.lotAvailableQty + lotInputted;
                          const canSelect = hasStock && (!isFulfilled || isSelected);

                          return (
                            <TableRow
                              key={`${lotKey}-${idx}`}
                              className={cn(
                                isSelected && allocated > 0 ? 'bg-blue-50/50' : '',
                                isPreInputted && isSelected ? 'bg-blue-50/70' : ''
                              )}
                            >
                              <TableCell className="text-center">
                                {hasStock ? (
                                  <button
                                    onClick={() => toggleLot(lotKey)}
                                    disabled={!canSelect}
                                    className={cn(
                                      'min-w-[64px] px-3 py-1.5 rounded-lg text-xs font-semibold transition-all',
                                      isSelected
                                        ? 'bg-blue-600 text-white shadow-sm'
                                        : canSelect
                                          ? 'bg-gray-100 text-gray-600 hover:bg-gray-200'
                                          : 'bg-gray-100 text-gray-400 cursor-not-allowed'
                                    )}
                                  >
                                    {isSelected ? '선택완료' : '선택'}
                                  </button>
                                ) : null}
                              </TableCell>
                              <TableCell className="text-center text-sm">
                                <div className="flex items-center justify-center gap-1">
                                  {lot.lotNo || (
                                    <span className="text-gray-400">
                                      재고 없음
                                    </span>
                                  )}
                                  {isPreInputted && (
                                    <span className="text-[10px] px-1.5 py-0.5 rounded bg-blue-100 text-blue-600 font-medium">
                                      기투입
                                    </span>
                                  )}
                                </div>
                              </TableCell>
                              <TableCell className="text-center text-sm">
                                {hasStock ? (
                                  isPreInputted ? (
                                    <span>
                                      {fmtQty(lot.lotAvailableQty)}
                                      <span className="text-blue-500 text-xs ml-1">(+{fmtQty(lotInputted)})</span>
                                    </span>
                                  ) : (
                                    fmtQty(lot.lotAvailableQty)
                                  )
                                ) : (
                                  <span className="text-red-500">0</span>
                                )}
                              </TableCell>
                              <TableCell className="text-center text-sm">
                                {lot.unit}
                              </TableCell>
                              <TableCell className="text-center text-sm font-medium">
                                {isSelected && hasStock ? (
                                  <input
                                    type="number"
                                    value={allocated || ''}
                                    onChange={(e) => {
                                      const val = parseFloat(e.target.value) || 0;
                                      handleAllocationChange(lotKey, val, effectiveAvailable);
                                    }}
                                    className="w-20 text-center text-blue-600 font-semibold border border-blue-200 rounded px-2 py-1 focus:outline-none focus:ring-1 focus:ring-blue-400 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
                                    min={0}
                                    max={effectiveAvailable}
                                  />
                                ) : allocated > 0 ? (
                                  <span className="text-blue-600">
                                    {fmtQty(allocated)}
                                  </span>
                                ) : (
                                  <span className="text-gray-300">-</span>
                                )}
                              </TableCell>
                            </TableRow>
                          );
                        })}
                      </TableBody>
                    </Table>
                    </div>

                    {/* [DEV] 배정 미완료 그룹에서만 강제입고 */}
                    {group.lots[0]?.itemId && group.lots.some(l => l.stockLotId !== null) && !isFulfilled && (
                      <div className="px-4 py-1.5 border-t">
                        <Button
                          size="sm"
                          variant="ghost"
                          className="h-7 text-[10px] text-orange-500 hover:text-orange-700 hover:bg-orange-50 px-2"
                          disabled={isForceCreating}
                          onClick={() => handleForceReceiving(group.lots[0].itemId, group.materialCode || group.materialName)}
                        >
                          {isForceCreating ? <Loader2 className="h-3 w-3 animate-spin mr-1" /> : <Zap className="h-3 w-3 mr-1" />}
                          [DEV] 재고 추가 ({group.materialCode || '품목'} × 100EA)
                        </Button>
                      </div>
                    )}

                    {/* 재고 검색 패널 */}
                    {!group.lots.some(l => l.stockLotId !== null) && searchOpenGroup !== group.groupKey && (
                      <button
                        onClick={() => {
                          setSearchOpenGroup(group.groupKey);
                          setSearchQuery(group.materialCode || group.materialName);
                          setSearchResults([]);
                          handleStockSearch(group.materialCode || group.materialName);
                        }}
                        className="w-full px-4 py-2 text-xs text-red-600 bg-red-50 border-t flex items-center gap-1.5 hover:bg-red-100 transition-colors"
                      >
                        <Search className="h-3.5 w-3.5" />
                        매칭 가능한 입고 재고 없음 — 클릭하여 재고 검색
                      </button>
                    )}
                    {searchOpenGroup === group.groupKey && (
                      <div className="px-4 py-3 bg-blue-50 border-t space-y-2">
                        <div className="flex items-center gap-2">
                          <Search className="h-3.5 w-3.5 text-blue-500 shrink-0" />
                          <input
                            type="text"
                            value={searchQuery}
                            onChange={(e) => setSearchQuery(e.target.value)}
                            onKeyDown={(e) => e.key === 'Enter' && handleStockSearch(searchQuery)}
                            placeholder={isWipOrder ? "원자재 코드 또는 자재명 검색" : "품목코드 또는 자재명 검색"}
                            className="flex-1 text-xs px-2 py-1.5 border rounded bg-white"
                          />
                          <Button size="sm" variant="outline" className="h-7 text-xs" onClick={() => handleStockSearch(searchQuery)}>
                            {isSearching ? <Loader2 className="h-3 w-3 animate-spin" /> : '검색'}
                          </Button>
                          <button onClick={() => setSearchOpenGroup(null)} className="text-gray-400 hover:text-gray-600">
                            <X className="h-4 w-4" />
                          </button>
                        </div>
                        {searchResults.length > 0 ? (
                          <div className="space-y-1">
                            <p className="text-[11px] text-blue-600 font-medium">{searchResults.length}건 검색됨:</p>
                            {searchResults.map((s, si) => (
                              <div key={`${s.itemId}-${si}`} className={cn(
                                "text-[11px] px-2 py-1.5 rounded border",
                                s.availableQty > 0 ? "bg-white border-emerald-200" : "bg-gray-50 border-gray-200"
                              )}>
                                <div className="flex justify-between items-center">
                                  <span className="font-medium">{s.itemCode}</span>
                                  <span className={s.availableQty > 0 ? "text-emerald-600 font-semibold" : "text-gray-400"}>
                                    가용 {formatNumber(s.availableQty)} EA
                                  </span>
                                </div>
                                <div className="text-gray-500">{s.itemName}</div>
                                {s.lots.length > 0 && (
                                  <div className="mt-1 space-y-0.5">
                                    {s.lots.slice(0, 3).map((l, li) => (
                                      <div key={li} className="text-[10px] text-gray-400 ml-2">
                                        LOT {l.lotNo} | {formatNumber(l.availableQty)} EA | FIFO #{l.fifoOrder}
                                      </div>
                                    ))}
                                  </div>
                                )}
                              </div>
                            ))}
                          </div>
                        ) : isSearching ? (
                          <p className="text-[11px] text-gray-400 flex items-center gap-1"><Loader2 className="h-3 w-3 animate-spin" />검색 중...</p>
                        ) : searchQuery && (
                          <p className="text-[11px] text-red-500">검색 결과 없음 — 해당 품목의 입고 처리가 필요합니다</p>
                        )}
                        {/* [개발전용] 입고 강제생성 버튼 */}
                        {group.lots[0]?.itemId && (
                          <Button
                            size="sm"
                            variant="outline"
                            className="w-full h-8 text-xs mt-1 border-orange-300 text-orange-600 hover:bg-orange-50"
                            disabled={isForceCreating}
                            onClick={() => handleForceReceiving(group.lots[0].itemId, group.materialCode || group.materialName)}
                          >
                            {isForceCreating ? <Loader2 className="h-3 w-3 animate-spin mr-1" /> : <Zap className="h-3 w-3 mr-1" />}
                            [DEV] 입고자료 강제생성 ({group.materialCode || '품목'} × 100EA)
                          </Button>
                        )}
                      </div>
                    )}
                  </div>
                );
              })}
            </div>
          )}

          {/* 버튼 영역 */}
          <div className="flex gap-3 shrink-0">
            <Button
              variant="outline"
              onClick={resetAndClose}
              disabled={isSubmitting}
              className="flex-1 py-6 text-base font-medium"
            >
              취소
            </Button>
            <Button
              onClick={handleSubmit}
              disabled={isSubmitting || !allGroupsFulfilled || !hasAnyAllocation}
              className="flex-1 py-6 text-base font-medium bg-gray-900 hover:bg-gray-800"
            >
              {isSubmitting ? (
                <>
                  <Loader2 className="w-4 h-4 mr-2 animate-spin" />
                  등록 중...
                </>
              ) : (
                '투입'
              )}
            </Button>
          </div>
        </div>
      </DialogContent>
    </Dialog>
  );
}