"use client";

/**
 * 크롤링 관리 — 외부 웹사이트 데이터 수집 설정/실행/로그 관리
 */

import React, { useState, useEffect, useCallback } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Badge } from "@/components/ui/badge";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogFooter,
} from "@/components/ui/dialog";
import {
  Plus,
  Search,
  Play,
  Pencil,
  Trash2,
  RefreshCw,
  Globe,
  Eye,
  Clock,
  CheckCircle,
  XCircle,
  Loader2,
  Check,
  ChevronsUpDown,
} from "lucide-react";
import { toast } from "sonner";
import { apiClient } from "@/lib/api/client";
import { tableTypeApi } from "@/lib/api/screen";
import { useConfirmDialog } from "@/components/common/ConfirmDialog";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { cn } from "@/lib/utils";

interface CrawlConfig {
  id: string;
  company_code: string;
  name: string;
  url: string;
  method: string;
  headers: Record<string, string>;
  request_body?: string;
  selector_type: string;
  row_selector: string;
  column_mappings: Array<{
    selector: string;
    column: string;
    type: string;
    attribute?: string;
  }>;
  target_table: string;
  upsert_key?: string;
  cron_schedule?: string;
  is_active: string;
  last_executed_at?: string;
  last_status?: string;
  last_error?: string;
}

interface CrawlLog {
  id: string;
  status: string;
  rows_collected: number;
  rows_saved: number;
  error_message?: string;
  started_at: string;
  finished_at?: string;
}

const EMPTY_CONFIG: Partial<CrawlConfig> = {
  name: "",
  url: "",
  method: "GET",
  headers: {},
  selector_type: "css",
  row_selector: "",
  column_mappings: [],
  target_table: "",
  upsert_key: "",
  cron_schedule: "",
  is_active: "Y",
};

export default function CrawlingManagementPage() {
  const [configs, setConfigs] = useState<CrawlConfig[]>([]);
  const [selectedId, setSelectedId] = useState<string | null>(null);
  const [searchText, setSearchText] = useState("");
  const [loading, setLoading] = useState(false);

  // 모달
  const [modalOpen, setModalOpen] = useState(false);
  const [modalMode, setModalMode] = useState<"add" | "edit">("add");
  const [form, setForm] = useState<Partial<CrawlConfig>>(EMPTY_CONFIG);
  const [saving, setSaving] = useState(false);

  // 테이블/컬럼 목록
  const [allTables, setAllTables] = useState<Array<{ tableName: string; displayName: string }>>([]);
  const [targetColumns, setTargetColumns] = useState<Array<{ columnName: string; columnLabel?: string }>>([]);
  const [tablePopoverOpen, setTablePopoverOpen] = useState(false);

  // URL 분석
  const [analyzing, setAnalyzing] = useState(false);
  const [analyzedTables, setAnalyzedTables] = useState<Array<{
    index: number; selector: string; caption: string; headers: string[]; rowCount: number; sampleRows: string[][];
  }>>([]);
  const [selectedAnalyzedIdx, setSelectedAnalyzedIdx] = useState<number | null>(null);

  // 컬럼 매핑 시각적 폼
  const [mappingRows, setMappingRows] = useState<Array<{ selector: string; column: string; type: string }>>([]);

  // 미리보기
  const [previewOpen, setPreviewOpen] = useState(false);
  const [previewData, setPreviewData] = useState<any>(null);
  const [previewing, setPreviewing] = useState(false);

  // 실행 로그
  const [logs, setLogs] = useState<CrawlLog[]>([]);
  const [logsLoading, setLogsLoading] = useState(false);

  // 실행 중
  const [executing, setExecuting] = useState<string | null>(null);

  const { confirm, ConfirmDialogComponent } = useConfirmDialog();

  // ─── 데이터 로드 ───

  const loadConfigs = useCallback(async () => {
    setLoading(true);
    try {
      const res = await apiClient.get("/crawl/configs");
      setConfigs(res.data.data || []);
    } catch {
      toast.error("크롤링 설정 로드 실패");
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    loadConfigs();
    tableTypeApi.getTables().then((t) => setAllTables(t || [])).catch(() => {});
  }, [loadConfigs]);

  // target_table 변경 시 컬럼 목록 로드
  useEffect(() => {
    if (!form.target_table) { setTargetColumns([]); return; }
    tableTypeApi.getColumns(form.target_table).then((cols) => {
      setTargetColumns(cols.map((c: any) => ({ columnName: c.columnName || c.column_name, columnLabel: c.displayName || c.columnLabel || c.column_label })));
    }).catch(() => setTargetColumns([]));
  }, [form.target_table]);

  const loadLogs = useCallback(async (configId: string) => {
    setLogsLoading(true);
    try {
      const res = await apiClient.get(`/crawl/configs/${configId}/logs?limit=20`);
      setLogs(res.data.data || []);
    } catch {
      setLogs([]);
    } finally {
      setLogsLoading(false);
    }
  }, []);

  useEffect(() => {
    if (selectedId) loadLogs(selectedId);
    else setLogs([]);
  }, [selectedId, loadLogs]);

  // ─── 필터링 ───

  const filteredConfigs = configs.filter(
    (c) =>
      !searchText ||
      c.name.toLowerCase().includes(searchText.toLowerCase()) ||
      c.url.toLowerCase().includes(searchText.toLowerCase())
  );

  const selectedConfig = configs.find((c) => c.id === selectedId);

  // ─── CRUD ───

  const openAddModal = () => {
    setModalMode("add");
    setForm({ ...EMPTY_CONFIG });
    setMappingRows([]);
    setAnalyzedTables([]);
    setSelectedAnalyzedIdx(null);
    setModalOpen(true);
  };

  const openEditModal = (config: CrawlConfig) => {
    setModalMode("edit");
    setForm({ ...config });
    const mappings = typeof config.column_mappings === "string"
      ? JSON.parse(config.column_mappings) : config.column_mappings || [];
    setMappingRows(mappings.map((m: any) => ({ selector: m.selector || "", column: m.column || "", type: m.type || "text" })));
    setAnalyzedTables([]);
    setSelectedAnalyzedIdx(null);
    setModalOpen(true);
  };

  // URL 분석
  const handleAnalyze = async () => {
    if (!form.url) { toast.error("URL을 입력하세요."); return; }
    setAnalyzing(true);
    try {
      const res = await apiClient.post("/crawl/analyze", { url: form.url });
      const data = res.data.data;
      setAnalyzedTables(data.tables || []);
      if (data.tables?.length > 0) {
        toast.success(`${data.tables.length}개 테이블 감지됨`);
      } else {
        toast.info("페이지에서 테이블을 찾지 못했습니다.");
      }
    } catch (err: any) {
      toast.error(err.response?.data?.message || "URL 분석 실패");
    } finally {
      setAnalyzing(false);
    }
  };

  // 분석된 테이블 선택 시 자동 매핑 생성
  const handleSelectAnalyzedTable = (idx: number) => {
    const table = analyzedTables[idx];
    if (!table) return;
    setSelectedAnalyzedIdx(idx);
    setForm((p) => ({ ...p, row_selector: `${table.selector} tbody tr` }));
    // 헤더 기반으로 컬럼 매핑 자동 생성
    const newMappings = table.headers.map((h, i) => ({
      selector: `td:nth-child(${i + 1})`,
      column: h.replace(/\s+/g, "_").replace(/[^a-zA-Z0-9_가-힣]/g, "").toLowerCase() || `col_${i + 1}`,
      type: "text",
    }));
    setMappingRows(newMappings);
  };

  const handleSave = async () => {
    if (!form.name || !form.url || !form.target_table) {
      toast.error("이름, URL, 대상 테이블은 필수입니다.");
      return;
    }

    setSaving(true);
    try {
      const payload = {
        ...form,
        column_mappings: mappingRows.filter((m) => m.selector && m.column),
        headers: form.headers || {},
      };

      if (modalMode === "add") {
        await apiClient.post("/crawl/configs", payload);
        toast.success("크롤링 설정이 생성되었습니다.");
      } else {
        await apiClient.put(`/crawl/configs/${form.id}`, payload);
        toast.success("크롤링 설정이 수정되었습니다.");
      }

      setModalOpen(false);
      loadConfigs();
    } catch (err: any) {
      toast.error(err.response?.data?.message || "저장 실패");
    } finally {
      setSaving(false);
    }
  };

  const handleDelete = async (id: string) => {
    const ok = await confirm("정말 삭제하시겠습니까?", { variant: "destructive", confirmText: "삭제" });
    if (!ok) return;
    try {
      await apiClient.delete(`/crawl/configs/${id}`);
      toast.success("삭제되었습니다.");
      if (selectedId === id) setSelectedId(null);
      loadConfigs();
    } catch {
      toast.error("삭제 실패");
    }
  };

  // ─── 실행 & 미리보기 ───

  const handleExecute = async (id: string) => {
    setExecuting(id);
    try {
      const res = await apiClient.post(`/crawl/execute/${id}`);
      const data = res.data.data;
      toast.success(`수집 ${data.collected}건, 저장 ${data.saved}건`);
      loadConfigs();
      if (selectedId === id) loadLogs(id);
    } catch (err: any) {
      toast.error(err.response?.data?.message || "실행 실패");
    } finally {
      setExecuting(null);
    }
  };

  const handlePreview = async () => {
    setPreviewing(true);
    try {
      const res = await apiClient.post("/crawl/preview", {
        url: form.url,
        row_selector: form.row_selector,
        column_mappings: mappingRows.filter((m) => m.selector && m.column),
        method: form.method,
        headers: form.headers || {},
        request_body: form.request_body,
      });
      setPreviewData(res.data.data);
      setPreviewOpen(true);
    } catch (err: any) {
      toast.error(err.response?.data?.message || "미리보기 실패");
    } finally {
      setPreviewing(false);
    }
  };

  // ─── 렌더링 ───

  return (
    <div className="flex h-full gap-4 p-4">
      {/* 좌측: 설정 목록 */}
      <div className="flex w-[340px] shrink-0 flex-col rounded-lg border bg-card">
        <div className="flex items-center justify-between border-b p-3">
          <h2 className="text-sm font-semibold">크롤링 설정</h2>
          <div className="flex gap-1">
            <Button variant="outline" size="sm" onClick={loadConfigs} disabled={loading}>
              <RefreshCw className={`h-3.5 w-3.5 ${loading ? "animate-spin" : ""}`} />
            </Button>
            <Button size="sm" onClick={openAddModal}>
              <Plus className="mr-1 h-3.5 w-3.5" /> 추가
            </Button>
          </div>
        </div>

        <div className="border-b p-2">
          <div className="relative">
            <Search className="absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
            <Input
              value={searchText}
              onChange={(e) => setSearchText(e.target.value)}
              placeholder="검색..."
              className="h-8 pl-8 text-xs"
            />
          </div>
        </div>

        <div className="flex-1 overflow-auto">
          {filteredConfigs.length === 0 ? (
            <div className="p-4 text-center text-xs text-muted-foreground">
              {loading ? "로딩 중..." : "설정이 없습니다."}
            </div>
          ) : (
            filteredConfigs.map((config) => (
              <div
                key={config.id}
                className={`cursor-pointer border-b p-3 transition-colors hover:bg-muted/50 ${
                  selectedId === config.id ? "bg-muted" : ""
                }`}
                onClick={() => setSelectedId(config.id)}
              >
                <div className="flex items-center justify-between">
                  <div className="flex items-center gap-2">
                    <Globe className="h-4 w-4 text-primary" />
                    <span className="text-sm font-medium">{config.name}</span>
                  </div>
                  <Badge variant={config.is_active === "Y" ? "default" : "secondary"} className="text-[10px]">
                    {config.is_active === "Y" ? "활성" : "비활성"}
                  </Badge>
                </div>
                <div className="mt-1 truncate text-[11px] text-muted-foreground">{config.url}</div>
                <div className="mt-1 flex items-center gap-2 text-[10px] text-muted-foreground">
                  {config.cron_schedule && (
                    <span className="flex items-center gap-0.5">
                      <Clock className="h-3 w-3" /> {config.cron_schedule}
                    </span>
                  )}
                  {config.last_status && (
                    <span className="flex items-center gap-0.5">
                      {config.last_status === "success" ? (
                        <CheckCircle className="h-3 w-3 text-green-500" />
                      ) : (
                        <XCircle className="h-3 w-3 text-red-500" />
                      )}
                      {config.last_status}
                    </span>
                  )}
                </div>
              </div>
            ))
          )}
        </div>
      </div>

      {/* 우측: 상세 + 로그 */}
      <div className="flex flex-1 flex-col gap-4">
        {selectedConfig ? (
          <>
            {/* 상세 정보 */}
            <div className="rounded-lg border bg-card p-4">
              <div className="mb-3 flex items-center justify-between">
                <h3 className="text-sm font-semibold">{selectedConfig.name}</h3>
                <div className="flex gap-1">
                  <Button
                    variant="outline"
                    size="sm"
                    onClick={() => handleExecute(selectedConfig.id)}
                    disabled={executing === selectedConfig.id}
                  >
                    {executing === selectedConfig.id ? (
                      <Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
                    ) : (
                      <Play className="mr-1 h-3.5 w-3.5" />
                    )}
                    실행
                  </Button>
                  <Button variant="outline" size="sm" onClick={() => openEditModal(selectedConfig)}>
                    <Pencil className="mr-1 h-3.5 w-3.5" /> 수정
                  </Button>
                  <Button variant="ghost" size="sm" onClick={() => handleDelete(selectedConfig.id)}>
                    <Trash2 className="h-3.5 w-3.5 text-destructive" />
                  </Button>
                </div>
              </div>

              <div className="grid grid-cols-2 gap-3 text-xs">
                <div>
                  <span className="text-muted-foreground">URL</span>
                  <div className="mt-0.5 truncate font-mono">{selectedConfig.url}</div>
                </div>
                <div>
                  <span className="text-muted-foreground">대상 테이블</span>
                  <div className="mt-0.5 font-mono">{selectedConfig.target_table}</div>
                </div>
                <div>
                  <span className="text-muted-foreground">행 선택자</span>
                  <div className="mt-0.5 font-mono">{selectedConfig.row_selector || "-"}</div>
                </div>
                <div>
                  <span className="text-muted-foreground">스케줄</span>
                  <div className="mt-0.5">{selectedConfig.cron_schedule || "수동 실행"}</div>
                </div>
                <div>
                  <span className="text-muted-foreground">UPSERT 키</span>
                  <div className="mt-0.5">{selectedConfig.upsert_key || "-"}</div>
                </div>
                <div>
                  <span className="text-muted-foreground">컬럼 매핑</span>
                  <div className="mt-0.5">{(selectedConfig.column_mappings || []).length}개</div>
                </div>
              </div>

              {selectedConfig.last_error && (
                <div className="mt-3 rounded bg-destructive/10 p-2 text-xs text-destructive">
                  {selectedConfig.last_error}
                </div>
              )}
            </div>

            {/* 실행 로그 */}
            <div className="flex-1 rounded-lg border bg-card">
              <div className="flex items-center justify-between border-b p-3">
                <h3 className="text-sm font-semibold">실행 로그</h3>
                <Button variant="ghost" size="sm" onClick={() => loadLogs(selectedConfig.id)} disabled={logsLoading}>
                  <RefreshCw className={`h-3.5 w-3.5 ${logsLoading ? "animate-spin" : ""}`} />
                </Button>
              </div>
              <div className="max-h-[400px] overflow-auto">
                {logs.length === 0 ? (
                  <div className="p-4 text-center text-xs text-muted-foreground">실행 로그가 없습니다.</div>
                ) : (
                  <table className="w-full text-xs">
                    <thead className="bg-muted/50">
                      <tr>
                        <th className="px-3 py-2 text-left">상태</th>
                        <th className="px-3 py-2 text-left">시작</th>
                        <th className="px-3 py-2 text-right">수집</th>
                        <th className="px-3 py-2 text-right">저장</th>
                        <th className="px-3 py-2 text-left">에러</th>
                      </tr>
                    </thead>
                    <tbody>
                      {logs.map((log) => (
                        <tr key={log.id} className="border-b hover:bg-muted/30">
                          <td className="px-3 py-2">
                            <Badge
                              variant={
                                log.status === "success" ? "default" : log.status === "running" ? "secondary" : "destructive"
                              }
                              className="text-[10px]"
                            >
                              {log.status}
                            </Badge>
                          </td>
                          <td className="px-3 py-2">{new Date(log.started_at).toLocaleString("ko-KR")}</td>
                          <td className="px-3 py-2 text-right">{log.rows_collected}</td>
                          <td className="px-3 py-2 text-right">{log.rows_saved}</td>
                          <td className="max-w-[200px] truncate px-3 py-2 text-destructive">
                            {log.error_message || "-"}
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                )}
              </div>
            </div>
          </>
        ) : (
          <div className="flex flex-1 items-center justify-center rounded-lg border bg-card text-sm text-muted-foreground">
            좌측에서 크롤링 설정을 선택하세요.
          </div>
        )}
      </div>

      {/* 추가/수정 모달 */}
      <Dialog open={modalOpen} onOpenChange={setModalOpen}>
        <DialogContent className="max-h-[85vh] max-w-3xl overflow-auto">
          <DialogHeader>
            <DialogTitle>{modalMode === "add" ? "크롤링 설정 추가" : "크롤링 설정 수정"}</DialogTitle>
          </DialogHeader>
          <div className="space-y-4">
            {/* STEP 1: 기본 정보 */}
            <div className="rounded-lg border p-3 space-y-3">
              <h4 className="text-xs font-semibold text-muted-foreground">1. 기본 정보</h4>
              <div className="grid grid-cols-2 gap-3">
                <div className="space-y-1">
                  <Label className="text-xs">이름 *</Label>
                  <Input value={form.name || ""} onChange={(e) => setForm((p) => ({ ...p, name: e.target.value }))} placeholder="예: 철강 시세 수집" className="h-8 text-xs" />
                </div>
                <div className="grid grid-cols-2 gap-2">
                  <div className="space-y-1">
                    <Label className="text-xs">스케줄 (cron)</Label>
                    <Input value={form.cron_schedule || ""} onChange={(e) => setForm((p) => ({ ...p, cron_schedule: e.target.value }))} placeholder="0 9 * * 1-5" className="h-8 text-xs font-mono" />
                  </div>
                  <div className="flex items-end gap-2 pb-0.5">
                    <Switch checked={form.is_active === "Y"} onCheckedChange={(v) => setForm((p) => ({ ...p, is_active: v ? "Y" : "N" }))} />
                    <Label className="text-xs">활성화</Label>
                  </div>
                </div>
              </div>
            </div>

            {/* STEP 2: URL 입력 + 분석 */}
            <div className="rounded-lg border p-3 space-y-3">
              <h4 className="text-xs font-semibold text-muted-foreground">2. 수집할 웹페이지</h4>
              <div className="flex gap-2">
                <Input value={form.url || ""} onChange={(e) => setForm((p) => ({ ...p, url: e.target.value }))} placeholder="https://example.com/prices" className="h-8 flex-1 text-xs font-mono" />
                <Button variant="outline" size="sm" onClick={handleAnalyze} disabled={analyzing || !form.url}>
                  {analyzing ? <Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" /> : <Search className="mr-1 h-3.5 w-3.5" />}
                  페이지 분석
                </Button>
              </div>

              {/* 분석 결과: 감지된 테이블 목록 */}
              {analyzedTables.length > 0 && (
                <div className="space-y-2">
                  <Label className="text-xs text-muted-foreground">{analyzedTables.length}개 테이블 감지 — 수집할 테이블을 선택하세요</Label>
                  <div className="space-y-2 max-h-[200px] overflow-auto">
                    {analyzedTables.map((t, idx) => (
                      <div
                        key={idx}
                        className={cn(
                          "cursor-pointer rounded border p-2 text-xs transition-colors hover:bg-muted/50",
                          selectedAnalyzedIdx === idx && "border-primary bg-primary/5"
                        )}
                        onClick={() => handleSelectAnalyzedTable(idx)}
                      >
                        <div className="flex items-center justify-between">
                          <span className="font-medium">{t.caption || `테이블 ${idx + 1}`}</span>
                          <Badge variant="secondary" className="text-[10px]">{t.rowCount}행 · {t.headers.length}열</Badge>
                        </div>
                        {t.headers.length > 0 && (
                          <div className="mt-1 text-[10px] text-muted-foreground truncate">
                            컬럼: {t.headers.join(", ")}
                          </div>
                        )}
                        {t.sampleRows.length > 0 && (
                          <div className="mt-1 text-[10px] text-muted-foreground truncate">
                            샘플: {t.sampleRows[0].join(" | ")}
                          </div>
                        )}
                      </div>
                    ))}
                  </div>
                </div>
              )}
            </div>

            {/* STEP 3: 컬럼 매핑 (시각적 폼) */}
            <div className="rounded-lg border p-3 space-y-3">
              <div className="flex items-center justify-between">
                <h4 className="text-xs font-semibold text-muted-foreground">3. 컬럼 매핑</h4>
                <Button variant="ghost" size="sm" className="h-6 text-[10px]" onClick={() => setMappingRows((p) => [...p, { selector: "", column: "", type: "text" }])}>
                  <Plus className="mr-0.5 h-3 w-3" /> 행 추가
                </Button>
              </div>
              {mappingRows.length === 0 ? (
                <div className="text-center text-xs text-muted-foreground py-3">
                  위에서 "페이지 분석"을 클릭하면 자동으로 매핑이 생성됩니다.
                </div>
              ) : (
                <div className="space-y-1">
                  <div className="grid grid-cols-[1fr_1fr_80px_32px] gap-1.5 text-[10px] text-muted-foreground px-1">
                    <span>CSS 선택자</span>
                    <span>저장 컬럼명</span>
                    <span>타입</span>
                    <span></span>
                  </div>
                  {mappingRows.map((row, i) => (
                    <div key={i} className="grid grid-cols-[1fr_1fr_80px_32px] gap-1.5">
                      <Input value={row.selector} onChange={(e) => { const n = [...mappingRows]; n[i] = { ...n[i], selector: e.target.value }; setMappingRows(n); }} placeholder="td:nth-child(1)" className="h-7 text-xs font-mono" />
                      <Input value={row.column} onChange={(e) => { const n = [...mappingRows]; n[i] = { ...n[i], column: e.target.value }; setMappingRows(n); }} placeholder="item_name" className="h-7 text-xs" />
                      <Select value={row.type} onValueChange={(v) => { const n = [...mappingRows]; n[i] = { ...n[i], type: v }; setMappingRows(n); }}>
                        <SelectTrigger className="h-7 text-[10px]"><SelectValue /></SelectTrigger>
                        <SelectContent>
                          <SelectItem value="text">텍스트</SelectItem>
                          <SelectItem value="number">숫자</SelectItem>
                          <SelectItem value="date">날짜</SelectItem>
                        </SelectContent>
                      </Select>
                      <Button variant="ghost" size="sm" className="h-7 w-7 p-0" onClick={() => setMappingRows((p) => p.filter((_, j) => j !== i))}>
                        <Trash2 className="h-3 w-3 text-muted-foreground" />
                      </Button>
                    </div>
                  ))}
                </div>
              )}
              {form.row_selector && (
                <div className="text-[10px] text-muted-foreground">
                  행 선택자: <code className="bg-muted px-1 rounded">{form.row_selector}</code>
                </div>
              )}
            </div>

            {/* STEP 4: 저장 대상 */}
            <div className="rounded-lg border p-3 space-y-3">
              <h4 className="text-xs font-semibold text-muted-foreground">4. 저장 설정</h4>
              <div className="grid grid-cols-2 gap-3">
                <div className="space-y-1">
                  <Label className="text-xs">대상 테이블 *</Label>
                  <Popover open={tablePopoverOpen} onOpenChange={setTablePopoverOpen}>
                    <PopoverTrigger asChild>
                      <Button variant="outline" role="combobox" className="h-8 w-full justify-between text-xs font-normal">
                        {form.target_table ? allTables.find((t) => t.tableName === form.target_table)?.displayName || form.target_table : "테이블 선택"}
                        <ChevronsUpDown className="ml-1 h-3 w-3 shrink-0 opacity-50" />
                      </Button>
                    </PopoverTrigger>
                    <PopoverContent className="w-[280px] p-0">
                      <Command>
                        <CommandInput placeholder="테이블 검색..." className="text-xs" />
                        <CommandEmpty>테이블을 찾을 수 없습니다.</CommandEmpty>
                        <CommandGroup className="max-h-[200px] overflow-auto">
                          {allTables.map((t) => (
                            <CommandItem key={t.tableName} value={`${t.displayName || ""} ${t.tableName}`} onSelect={() => { setForm((p) => ({ ...p, target_table: t.tableName, upsert_key: "" })); setTablePopoverOpen(false); }}>
                              <Check className={cn("mr-2 h-3 w-3", form.target_table === t.tableName ? "opacity-100" : "opacity-0")} />
                              <span className="text-xs">{t.displayName || t.tableName}</span>
                              {t.displayName && <span className="ml-1 text-[10px] text-muted-foreground">({t.tableName})</span>}
                            </CommandItem>
                          ))}
                        </CommandGroup>
                      </Command>
                    </PopoverContent>
                  </Popover>
                </div>
                <div className="space-y-1">
                  <Label className="text-xs">중복 기준 컬럼</Label>
                  <Select value={form.upsert_key || "__none__"} onValueChange={(v) => setForm((p) => ({ ...p, upsert_key: v === "__none__" ? "" : v }))}>
                    <SelectTrigger className="h-8 text-xs"><SelectValue placeholder="없음 (항상 추가)" /></SelectTrigger>
                    <SelectContent>
                      <SelectItem value="__none__">없음 (항상 추가)</SelectItem>
                      {targetColumns.map((col) => (
                        <SelectItem key={col.columnName} value={col.columnName}>{col.columnLabel || col.columnName}</SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                </div>
              </div>
            </div>
          </div>

          <DialogFooter className="gap-2">
            <Button variant="outline" size="sm" onClick={handlePreview} disabled={previewing || !form.url || mappingRows.length === 0}>
              {previewing ? <Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" /> : <Eye className="mr-1 h-3.5 w-3.5" />}
              미리보기
            </Button>
            <Button size="sm" onClick={handleSave} disabled={saving}>
              {saving ? <Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" /> : null}
              {modalMode === "add" ? "생성" : "저장"}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* 미리보기 모달 */}
      <Dialog open={previewOpen} onOpenChange={setPreviewOpen}>
        <DialogContent className="max-h-[70vh] max-w-3xl overflow-auto">
          <DialogHeader>
            <DialogTitle>크롤링 미리보기</DialogTitle>
          </DialogHeader>
          {previewData && (
            <div className="space-y-3">
              <div className="flex gap-4 text-xs">
                <span>
                  총 요소: <strong>{previewData.totalElements}</strong>
                </span>
                <span>
                  HTML 크기: <strong>{(previewData.htmlLength / 1024).toFixed(1)}KB</strong>
                </span>
                <span>
                  미리보기 행: <strong>{previewData.previewRows?.length || 0}</strong>
                </span>
              </div>
              {previewData.previewRows?.length > 0 ? (
                <div className="overflow-auto rounded border">
                  <table className="w-full text-xs">
                    <thead className="bg-muted/50">
                      <tr>
                        {Object.keys(previewData.previewRows[0]).map((key) => (
                          <th key={key} className="px-3 py-2 text-left font-medium">
                            {key}
                          </th>
                        ))}
                      </tr>
                    </thead>
                    <tbody>
                      {previewData.previewRows.map((row: any, i: number) => (
                        <tr key={i} className="border-t">
                          {Object.values(row).map((val: any, j: number) => (
                            <td key={j} className="max-w-[200px] truncate px-3 py-1.5">
                              {val != null ? String(val) : "-"}
                            </td>
                          ))}
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              ) : (
                <div className="p-4 text-center text-xs text-muted-foreground">
                  추출된 데이터가 없습니다. 선택자를 확인하세요.
                </div>
              )}
            </div>
          )}
        </DialogContent>
      </Dialog>

      {ConfirmDialogComponent}
    </div>
  );
}
