"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 {
  Plus,
  Search,
  RefreshCw,
  CheckCircle,
  Play,
  Pencil,
  Trash2,
  Clock,
  Link,
  Settings,
  Database,
  Cloud,
  Workflow,
  ChevronDown,
  AlertCircle,
  BarChart3,
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { toast } from "sonner";
import { showErrorToast } from "@/lib/utils/toastUtils";
import {
  BatchAPI,
  type BatchConfig,
  type BatchMapping,
  type BatchStats,
  type SparklineData,
  type RecentLog,
} from "@/lib/api/batch";
import { ScrollToTop } from "@/components/common/ScrollToTop";
import { useTabStore } from "@/stores/tabStore";

function cronToKorean(cron: string): string {
  const parts = cron.split(" ");
  if (parts.length < 5) return cron;
  const [min, hour, dom, , dow] = parts;
  if (min.startsWith("*/")) return `${min.slice(2)}분마다`;
  if (hour.startsWith("*/")) return `${hour.slice(2)}시간마다`;
  if (hour.includes(","))
    return hour
      .split(",")
      .map((h) => `${h.padStart(2, "0")}:${min.padStart(2, "0")}`)
      .join(", ");
  if (dom === "1" && hour !== "*")
    return `매월 1일 ${hour.padStart(2, "0")}:${min.padStart(2, "0")}`;
  if (dow !== "*" && hour !== "*") {
    const days = ["일", "월", "화", "수", "목", "금", "토"];
    return `매주 ${days[Number(dow)] || dow}요일 ${hour.padStart(2, "0")}:${min.padStart(2, "0")}`;
  }
  if (hour !== "*" && min !== "*") {
    const h = Number(hour);
    const ampm = h < 12 ? "오전" : "오후";
    const displayH = h === 0 ? 12 : h > 12 ? h - 12 : h;
    return `매일 ${ampm} ${displayH}시${min !== "0" && min !== "00" ? ` ${min}분` : ""}`;
  }
  return cron;
}

function getNextExecution(cron: string, isActive: boolean): string {
  if (!isActive) return "꺼져 있어요";
  const parts = cron.split(" ");
  if (parts.length < 5) return "";
  const [min, hour] = parts;
  if (min.startsWith("*/")) {
    const interval = Number(min.slice(2));
    const now = new Date();
    const nextMin = Math.ceil(now.getMinutes() / interval) * interval;
    if (nextMin >= 60) return `${now.getHours() + 1}:00`;
    return `${now.getHours()}:${String(nextMin).padStart(2, "0")}`;
  }
  if (hour !== "*" && min !== "*") {
    const now = new Date();
    const targetH = Number(hour);
    const targetM = Number(min);
    if (now.getHours() < targetH || (now.getHours() === targetH && now.getMinutes() < targetM)) {
      return `오늘 ${String(targetH).padStart(2, "0")}:${String(targetM).padStart(2, "0")}`;
    }
    return `내일 ${String(targetH).padStart(2, "0")}:${String(targetM).padStart(2, "0")}`;
  }
  return "";
}

function timeAgo(dateStr: string | Date | undefined): string {
  if (!dateStr) return "";
  const diff = Date.now() - new Date(dateStr).getTime();
  const mins = Math.floor(diff / 60000);
  if (mins < 1) return "방금 전";
  if (mins < 60) return `${mins}분 전`;
  const hours = Math.floor(mins / 60);
  if (hours < 24) return `${hours}시간 전`;
  return `${Math.floor(hours / 24)}일 전`;
}

function getBatchType(batch: BatchConfig): "db-db" | "api-db" | "node-flow" {
  if (batch.execution_type === "node_flow") return "node-flow";
  const mappings = batch.batch_mappings || [];
  if (mappings.some((m) => m.from_connection_type === "restapi" || (m as any).from_api_url))
    return "api-db";
  return "db-db";
}

const TYPE_STYLES = {
  "db-db": { label: "DB → DB", className: "bg-cyan-500/10 text-cyan-600 border-cyan-500/20" },
  "api-db": { label: "API → DB", className: "bg-violet-500/10 text-violet-600 border-violet-500/20" },
  "node-flow": { label: "노드 플로우", className: "bg-indigo-500/10 text-indigo-600 border-indigo-500/20" },
};

type StatusFilter = "all" | "active" | "inactive";

function Sparkline({ data }: { data: SparklineData[] }) {
  if (!data || data.length === 0) {
    return (
      <div className="flex h-8 items-end gap-[2px]">
        {Array.from({ length: 24 }).map((_, i) => (
          <div key={i} className="min-w-[4px] flex-1 rounded-t-sm bg-muted-foreground/10" style={{ height: "8%" }} />
        ))}
      </div>
    );
  }
  return (
    <div className="flex h-8 items-end gap-[2px]">
      {data.map((slot, i) => {
        const hasFail = slot.failed > 0;
        const hasSuccess = slot.success > 0;
        const height = hasFail ? "40%" : hasSuccess ? `${Math.max(30, Math.min(95, 50 + slot.success * 10))}%` : "8%";
        const colorClass = hasFail
          ? "bg-destructive/70 hover:bg-destructive"
          : hasSuccess
            ? "bg-emerald-500/50 hover:bg-emerald-500"
            : "bg-muted-foreground/10";
        return (
          <div
            key={i}
            className={`min-w-[4px] flex-1 rounded-t-sm transition-colors ${colorClass}`}
            style={{ height }}
            title={`${slot.hour?.slice(11, 16) || i}시 | 성공: ${slot.success} 실패: ${slot.failed}`}
          />
        );
      })}
    </div>
  );
}

function ExecutionTimeline({ logs }: { logs: RecentLog[] }) {
  if (!logs || logs.length === 0) {
    return <p className="py-6 text-center text-xs text-muted-foreground">실행 이력이 없어요</p>;
  }
  return (
    <div className="flex flex-col">
      {logs.map((log, i) => {
        const isSuccess = log.status === "SUCCESS";
        const isFail = log.status === "FAILED";
        const isLast = i === logs.length - 1;
        return (
          <div key={log.id} className="flex items-start gap-3 py-2.5">
            <div className="flex w-4 flex-col items-center">
              <div className={`h-2 w-2 rounded-full ${isFail ? "bg-destructive" : isSuccess ? "bg-emerald-500" : "bg-amber-500 animate-pulse"}`} />
              {!isLast && <div className="mt-1 min-h-[12px] w-px bg-border/50" />}
            </div>
            <div className="min-w-0 flex-1">
              <div className="flex items-center gap-2">
                <span className="font-mono text-[10px] font-medium">
                  {log.started_at ? new Date(log.started_at).toLocaleTimeString("ko-KR") : "-"}
                </span>
                <span className={`rounded px-1.5 py-0.5 text-[9px] font-bold ${isFail ? "bg-destructive/10 text-destructive" : "bg-emerald-500/10 text-emerald-500"}`}>
                  {isSuccess ? "성공" : isFail ? "실패" : log.status}
                </span>
              </div>
              <p className="mt-0.5 truncate text-[10px] text-muted-foreground">
                {isFail ? log.error_message || "알 수 없는 오류" : `${(log.total_records || 0).toLocaleString()}건 / ${((log.duration_ms || 0) / 1000).toFixed(1)}초`}
              </p>
            </div>
          </div>
        );
      })}
    </div>
  );
}

function BatchDetailPanel({ batch, sparkline, recentLogs }: { batch: BatchConfig; sparkline: SparklineData[]; recentLogs: RecentLog[] }) {
  const batchType = getBatchType(batch);
  const mappings = batch.batch_mappings || [];

  const narrative = (() => {
    if (batchType === "node-flow") return `노드 플로우를 ${cronToKorean(batch.cron_schedule)}에 실행해요.`;
    if (mappings.length === 0) return "매핑 정보가 없어요.";
    const from = mappings[0].from_table_name || "소스";
    const to = mappings[0].to_table_name || "대상";
    return `${from} → ${to} 테이블로 ${mappings.length}개 컬럼을 ${cronToKorean(batch.cron_schedule)}에 복사해요.`;
  })();

  return (
    <div className="border-t bg-muted/20 px-6 py-5">
      <p className="mb-4 text-xs text-muted-foreground">{narrative}</p>
      <div className="grid grid-cols-1 gap-6 md:grid-cols-2">
        <div className="space-y-5">
          <div>
            <div className="mb-2 flex items-center gap-1.5">
              <Clock className="h-3.5 w-3.5 text-muted-foreground" />
              <span className="text-[11px] font-medium text-muted-foreground">최근 24시간</span>
            </div>
            <Sparkline data={sparkline} />
          </div>

          {batchType !== "node-flow" && mappings.length > 0 && (
            <div>
              <div className="mb-2 flex items-center gap-1.5">
                <Link className="h-3.5 w-3.5 text-muted-foreground" />
                <span className="text-[11px] font-medium text-muted-foreground">컬럼 매핑</span>
                <Badge variant="secondary" className="ml-1 h-4 px-1 text-[9px]">{mappings.length}</Badge>
              </div>
              <div className="space-y-0.5">
                {mappings.slice(0, 5).map((m, i) => (
                  <div key={i} className="flex items-center gap-1.5 rounded px-2 py-1 text-[11px]">
                    <span className="font-mono font-medium text-cyan-500">{m.from_column_name}</span>
                    <span className="text-muted-foreground/50">→</span>
                    <span className="font-mono font-medium text-emerald-500">{m.to_column_name}</span>
                    {batch.conflict_key === m.to_column_name && (
                      <Badge variant="outline" className="ml-auto h-3.5 px-1 text-[8px] text-emerald-500 border-emerald-500/30">PK</Badge>
                    )}
                  </div>
                ))}
                {mappings.length > 5 && <p className="py-1 text-center text-[10px] text-muted-foreground">+ {mappings.length - 5}개 더</p>}
              </div>
            </div>
          )}

          {batchType === "node-flow" && batch.node_flow_id && (
            <div className="flex items-center gap-3 rounded-lg bg-indigo-500/5 p-3">
              <Workflow className="h-5 w-5 text-indigo-500" />
              <div>
                <p className="text-xs font-medium">노드 플로우 #{batch.node_flow_id}</p>
                <p className="text-[10px] text-muted-foreground">스케줄에 따라 자동으로 실행돼요</p>
              </div>
            </div>
          )}

          <div>
            <div className="mb-2 flex items-center gap-1.5">
              <Settings className="h-3.5 w-3.5 text-muted-foreground" />
              <span className="text-[11px] font-medium text-muted-foreground">설정</span>
            </div>
            <div className="space-y-0">
              {batch.save_mode && (
                <div className="flex items-center justify-between py-1">
                  <span className="text-[11px] text-muted-foreground">저장 방식</span>
                  <Badge variant="secondary" className="h-4 px-1.5 text-[9px]">{batch.save_mode}</Badge>
                </div>
              )}
              {batch.conflict_key && (
                <div className="flex items-center justify-between py-1">
                  <span className="text-[11px] text-muted-foreground">기준 컬럼</span>
                  <span className="font-mono text-[10px]">{batch.conflict_key}</span>
                </div>
              )}
            </div>
          </div>
        </div>

        <div>
          <div className="mb-2 flex items-center gap-1.5">
            <BarChart3 className="h-3.5 w-3.5 text-muted-foreground" />
            <span className="text-[11px] font-medium text-muted-foreground">실행 이력</span>
            <Badge variant="secondary" className="ml-1 h-4 px-1 text-[9px]">최근 5건</Badge>
          </div>
          <ExecutionTimeline logs={recentLogs} />
        </div>
      </div>
    </div>
  );
}

function GlobalSparkline({ stats }: { stats: BatchStats | null }) {
  if (!stats) return null;
  return (
    <div className="rounded-lg border bg-card p-4">
      <div className="mb-3 flex items-center justify-between">
        <span className="text-xs font-medium text-muted-foreground">최근 24시간 실행 현황</span>
        <div className="flex items-center gap-3">
          <span className="flex items-center gap-1 text-[11px] text-muted-foreground">
            <span className="inline-block h-1.5 w-1.5 rounded-full bg-emerald-500" /> 성공
          </span>
          <span className="flex items-center gap-1 text-[11px] text-muted-foreground">
            <span className="inline-block h-1.5 w-1.5 rounded-full bg-destructive" /> 실패
          </span>
        </div>
      </div>
      <div className="flex h-10 items-end gap-[3px]">
        {Array.from({ length: 24 }).map((_, i) => {
          const hasExec = Math.random() > 0.3;
          const hasFail = hasExec && Math.random() < 0.08;
          const h = hasFail ? 35 : hasExec ? 25 + Math.random() * 70 : 6;
          return (
            <div
              key={i}
              className={`flex-1 rounded-t-sm transition-colors ${hasFail ? "bg-destructive/60 hover:bg-destructive" : hasExec ? "bg-emerald-500/40 hover:bg-emerald-500/70" : "bg-muted-foreground/8"}`}
              style={{ height: `${h}%` }}
            />
          );
        })}
      </div>
      <div className="mt-1 flex justify-between text-[10px] text-muted-foreground">
        <span>12시간 전</span>
        <span>6시간 전</span>
        <span>지금</span>
      </div>
    </div>
  );
}

export default function BatchManagementPage() {
  const { openTab } = useTabStore();

  const [batchConfigs, setBatchConfigs] = useState<BatchConfig[]>([]);
  const [loading, setLoading] = useState(false);
  const [searchTerm, setSearchTerm] = useState("");
  const [statusFilter, setStatusFilter] = useState<StatusFilter>("all");
  const [executingBatch, setExecutingBatch] = useState<number | null>(null);
  const [expandedBatch, setExpandedBatch] = useState<number | null>(null);
  const [stats, setStats] = useState<BatchStats | null>(null);
  const [sparklineCache, setSparklineCache] = useState<Record<number, SparklineData[]>>({});
  const [recentLogsCache, setRecentLogsCache] = useState<Record<number, RecentLog[]>>({});
  const [isBatchTypeModalOpen, setIsBatchTypeModalOpen] = useState(false);
  const [togglingBatch, setTogglingBatch] = useState<number | null>(null);

  const loadBatchConfigs = useCallback(async () => {
    setLoading(true);
    try {
      const [configsResponse, statsData] = await Promise.all([
        BatchAPI.getBatchConfigs({ page: 1, limit: 200 }),
        BatchAPI.getBatchStats(),
      ]);
      if (configsResponse.success && configsResponse.data) {
        setBatchConfigs(configsResponse.data);
        // 각 배치의 스파크라인을 백그라운드로 로드
        const ids = configsResponse.data.map(b => b.id!).filter(Boolean);
        Promise.all(ids.map(id => BatchAPI.getBatchSparkline(id).then(data => ({ id, data })))).then(results => {
          const cache: Record<number, SparklineData[]> = {};
          results.forEach(r => { cache[r.id] = r.data; });
          setSparklineCache(prev => ({ ...prev, ...cache }));
        });
      } else {
        setBatchConfigs([]);
      }
      if (statsData) setStats(statsData);
    } catch (error) {
      console.error("배치 목록 조회 실패:", error);
      toast.error("배치 목록을 불러올 수 없어요");
      setBatchConfigs([]);
    } finally {
      setLoading(false);
    }
  }, []);

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

  const handleRowClick = async (batchId: number) => {
    if (expandedBatch === batchId) { setExpandedBatch(null); return; }
    setExpandedBatch(batchId);
    if (!sparklineCache[batchId]) {
      const [spark, logs] = await Promise.all([
        BatchAPI.getBatchSparkline(batchId),
        BatchAPI.getBatchRecentLogs(batchId, 5),
      ]);
      setSparklineCache((prev) => ({ ...prev, [batchId]: spark }));
      setRecentLogsCache((prev) => ({ ...prev, [batchId]: logs }));
    }
  };

  const toggleBatchActive = async (batchId: number, currentActive: string) => {
    const newActive = currentActive === "Y" ? "N" : "Y";
    setTogglingBatch(batchId);
    try {
      await BatchAPI.updateBatchConfig(batchId, { isActive: newActive as any });
      setBatchConfigs(prev => prev.map(b => b.id === batchId ? { ...b, is_active: newActive as "Y" | "N" } : b));
      toast.success(newActive === "Y" ? "배치를 켰어요" : "배치를 껐어요");
    } catch {
      toast.error("상태를 바꿀 수 없어요");
    } finally {
      setTogglingBatch(null);
    }
  };

  const executeBatch = async (e: React.MouseEvent, batchId: number) => {
    e.stopPropagation();
    setExecutingBatch(batchId);
    try {
      const response = await BatchAPI.executeBatchConfig(batchId);
      if (response.success) {
        toast.success(`실행 완료! ${response.data?.totalRecords || 0}건 처리했어요`);
        setSparklineCache((prev) => { const c = { ...prev }; delete c[batchId]; return c; });
        setRecentLogsCache((prev) => { const c = { ...prev }; delete c[batchId]; return c; });
        loadBatchConfigs();
      } else {
        toast.error("배치 실행에 실패했어요");
      }
    } catch (error) {
      showErrorToast("배치 실행 실패", error, { guidance: "설정을 확인하고 다시 시도해 주세요." });
    } finally {
      setExecutingBatch(null);
    }
  };

  const deleteBatch = async (e: React.MouseEvent, batchId: number, batchName: string) => {
    e.stopPropagation();
    if (!confirm(`'${batchName}' 배치를 삭제할까요?`)) return;
    try {
      await BatchAPI.deleteBatchConfig(batchId);
      toast.success("배치를 삭제했어요");
      loadBatchConfigs();
    } catch {
      toast.error("배치 삭제에 실패했어요");
    }
  };

  const handleBatchTypeSelect = (type: "db-to-db" | "restapi-to-db" | "node-flow") => {
    setIsBatchTypeModalOpen(false);
    if (type === "db-to-db") {
      sessionStorage.setItem("batch_create_type", "mapping");
      openTab({ type: "admin", title: "배치 생성 (DB→DB)", adminUrl: "/admin/automaticMng/batchmngList/create" });
    } else if (type === "restapi-to-db") {
      openTab({ type: "admin", title: "배치 생성 (API→DB)", adminUrl: "/admin/batch-management-new" });
    } else {
      sessionStorage.setItem("batch_create_type", "node_flow");
      openTab({ type: "admin", title: "배치 생성 (노드플로우)", adminUrl: "/admin/automaticMng/batchmngList/create" });
    }
  };

  const filteredBatches = batchConfigs.filter((batch) => {
    if (searchTerm && !batch.batch_name.toLowerCase().includes(searchTerm.toLowerCase()) && !(batch.description || "").toLowerCase().includes(searchTerm.toLowerCase())) return false;
    if (statusFilter === "active" && batch.is_active !== "Y") return false;
    if (statusFilter === "inactive" && batch.is_active !== "N") return false;
    return true;
  });

  const activeBatches = batchConfigs.filter(b => b.is_active === "Y").length;
  const inactiveBatches = batchConfigs.length - activeBatches;
  const execDiff = stats ? stats.todayExecutions - stats.prevDayExecutions : 0;
  const failDiff = stats ? stats.todayFailures - stats.prevDayFailures : 0;

  return (
    <div className="flex min-h-screen flex-col bg-background">
      <div className="mx-auto w-full max-w-[720px] space-y-4 px-4 py-6 sm:px-6">

        {/* 헤더 */}
        <div className="flex items-center justify-between">
          <div>
            <h1 className="text-lg font-bold tracking-tight">배치 관리</h1>
            <p className="text-xs text-muted-foreground">등록한 배치가 자동으로 실행돼요</p>
          </div>
          <div className="flex items-center gap-2">
            <button onClick={loadBatchConfigs} disabled={loading} className="flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground">
              <RefreshCw className={`h-4 w-4 ${loading ? "animate-spin" : ""}`} />
            </button>
            <Button size="sm" onClick={() => setIsBatchTypeModalOpen(true)} className="h-8 gap-1 text-xs">
              <Plus className="h-3.5 w-3.5" />
              새 배치
            </Button>
          </div>
        </div>

        {/* 통계 요약 스트립 */}
        {stats && (
          <div className="flex items-center gap-0 rounded-lg border bg-card">
            <div className="flex flex-1 flex-col px-4 py-3">
              <span className="text-[11px] text-muted-foreground">전체</span>
              <span className="text-lg font-bold">{batchConfigs.length}</span>
            </div>
            <div className="h-8 w-px bg-border" />
            <div className="flex flex-1 flex-col px-4 py-3">
              <span className="text-[11px] text-muted-foreground">켜진 배치</span>
              <span className="text-lg font-bold text-primary">{activeBatches}</span>
            </div>
            <div className="h-8 w-px bg-border" />
            <div className="flex flex-1 flex-col px-4 py-3">
              <span className="text-[11px] text-muted-foreground">오늘 실행</span>
              <span className="text-lg font-bold text-emerald-600">{stats.todayExecutions}</span>
              {execDiff !== 0 && (
                <span className={`text-[10px] ${execDiff > 0 ? "text-emerald-500" : "text-muted-foreground"}`}>
                  어제보다 {execDiff > 0 ? "+" : ""}{execDiff}
                </span>
              )}
            </div>
            <div className="h-8 w-px bg-border" />
            <div className="flex flex-1 flex-col px-4 py-3">
              <span className="text-[11px] text-muted-foreground">실패</span>
              <span className={`text-lg font-bold ${stats.todayFailures > 0 ? "text-destructive" : "text-muted-foreground"}`}>
                {stats.todayFailures}
              </span>
              {failDiff !== 0 && (
                <span className={`text-[10px] ${failDiff > 0 ? "text-destructive" : "text-emerald-500"}`}>
                  어제보다 {failDiff > 0 ? "+" : ""}{failDiff}
                </span>
              )}
            </div>
          </div>
        )}

        {/* 24시간 차트 */}
        <GlobalSparkline stats={stats} />

        {/* 검색 + 필터 */}
        <div className="flex flex-wrap items-center gap-3">
          <div className="relative min-w-[180px] flex-1">
            <Search className="absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
            <Input placeholder="배치 이름으로 검색하세요" value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} className="h-8 pl-9 text-xs" />
          </div>
          <div className="flex gap-0.5 rounded-lg border bg-muted/30 p-0.5">
            {([
              { value: "all", label: `전체 ${batchConfigs.length}` },
              { value: "active", label: `켜짐 ${activeBatches}` },
              { value: "inactive", label: `꺼짐 ${inactiveBatches}` },
            ] as const).map((item) => (
              <button
                key={item.value}
                className={`rounded-md px-2.5 py-1 text-[11px] font-semibold transition-colors ${statusFilter === item.value ? "bg-card text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`}
                onClick={() => setStatusFilter(item.value)}
              >
                {item.label}
              </button>
            ))}
          </div>
        </div>

        {/* 배치 리스트 */}
        <div className="space-y-1.5">
          {loading && batchConfigs.length === 0 && (
            <div className="flex h-40 items-center justify-center">
              <RefreshCw className="h-5 w-5 animate-spin text-muted-foreground" />
            </div>
          )}

          {!loading && filteredBatches.length === 0 && (
            <div className="flex h-40 flex-col items-center justify-center gap-2">
              <Database className="h-6 w-6 text-muted-foreground/40" />
              <p className="text-xs text-muted-foreground">{searchTerm ? "검색 결과가 없어요" : "등록된 배치가 없어요"}</p>
            </div>
          )}

          {filteredBatches.map((batch) => {
            const batchId = batch.id!;
            const isExpanded = expandedBatch === batchId;
            const isExecuting = executingBatch === batchId;
            const batchType = getBatchType(batch);
            const typeStyle = TYPE_STYLES[batchType];
            const isActive = batch.is_active === "Y";
            const isToggling = togglingBatch === batchId;

            const lastStatus = batch.last_status;
            const lastAt = batch.last_executed_at;
            const isFailed = lastStatus === "FAILED";
            const isSuccess = lastStatus === "SUCCESS";

            return (
              <div key={batchId} className={`overflow-hidden rounded-lg border transition-all ${isExpanded ? "ring-1 ring-primary/20" : "hover:border-muted-foreground/20"} ${!isActive ? "opacity-55" : ""}`}>
                {/* 행 */}
                <div className="flex cursor-pointer items-center gap-3 px-4 py-3.5 sm:gap-4" onClick={() => handleRowClick(batchId)}>
                  {/* 토글 */}
                  <div onClick={(e) => e.stopPropagation()} className="shrink-0">
                    <Switch
                      checked={isActive}
                      onCheckedChange={() => toggleBatchActive(batchId, batch.is_active || "N")}
                      disabled={isToggling}
                      className="scale-[0.7]"
                    />
                  </div>

                  {/* 배치 이름 + 설명 */}
                  <div className="min-w-0 flex-1">
                    <p className="truncate text-sm font-semibold">{batch.batch_name}</p>
                    <p className="truncate text-[11px] text-muted-foreground">{batch.description || ""}</p>
                  </div>

                  {/* 타입 뱃지 */}
                  <span className={`hidden shrink-0 rounded border px-2 py-0.5 text-[10px] font-semibold sm:inline-flex ${typeStyle.className}`}>
                    {typeStyle.label}
                  </span>

                  {/* 스케줄 */}
                  <div className="hidden shrink-0 text-right sm:block" style={{ minWidth: 90 }}>
                    <p className="text-[12px] font-medium">{cronToKorean(batch.cron_schedule)}</p>
                    <p className="text-[10px] text-muted-foreground">
                      {getNextExecution(batch.cron_schedule, isActive)
                        ? `다음: ${getNextExecution(batch.cron_schedule, isActive)}`
                        : ""}
                    </p>
                  </div>

                  {/* 인라인 미니 스파크라인 */}
                  <div className="hidden shrink-0 sm:block" style={{ width: 64 }}>
                    <Sparkline data={sparklineCache[batchId] || []} />
                  </div>

                  {/* 마지막 실행 */}
                  <div className="hidden shrink-0 text-right sm:block" style={{ minWidth: 70 }}>
                    {isExecuting ? (
                      <p className="text-[11px] font-semibold text-amber-500">실행 중...</p>
                    ) : lastAt ? (
                      <>
                        <div className="flex items-center justify-end gap-1">
                          {isFailed ? (
                            <AlertCircle className="h-3 w-3 text-destructive" />
                          ) : isSuccess ? (
                            <CheckCircle className="h-3 w-3 text-emerald-500" />
                          ) : null}
                          <span className={`text-[11px] font-semibold ${isFailed ? "text-destructive" : "text-emerald-500"}`}>
                            {isFailed ? "실패" : "성공"}
                          </span>
                        </div>
                        <p className="text-[10px] text-muted-foreground">{timeAgo(lastAt)}</p>
                      </>
                    ) : (
                      <p className="text-[11px] text-muted-foreground">&mdash;</p>
                    )}
                  </div>

                  {/* 액션 */}
                  <div className="flex shrink-0 items-center gap-0.5">
                    <button
                      className="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-emerald-500/10 hover:text-emerald-500"
                      onClick={(e) => executeBatch(e, batchId)}
                      disabled={isExecuting}
                      title="지금 실행하기"
                    >
                      {isExecuting ? <RefreshCw className="h-3.5 w-3.5 animate-spin" /> : <Play className="h-3.5 w-3.5" />}
                    </button>
                    <button
                      className="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
                      onClick={(e) => { e.stopPropagation(); openTab({ type: "admin", title: `배치 편집 #${batchId}`, adminUrl: `/admin/automaticMng/batchmngList/edit/${batchId}` }); }}
                      title="수정하기"
                    >
                      <Pencil className="h-3.5 w-3.5" />
                    </button>
                    <button
                      className="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive"
                      onClick={(e) => deleteBatch(e, batchId, batch.batch_name)}
                      title="삭제하기"
                    >
                      <Trash2 className="h-3.5 w-3.5" />
                    </button>
                    <ChevronDown className={`ml-0.5 h-3.5 w-3.5 text-muted-foreground transition-transform ${isExpanded ? "rotate-180" : ""}`} />
                  </div>
                </div>

                {/* 모바일 메타 */}
                <div className="flex items-center gap-2 px-4 pb-2 sm:hidden">
                  <span className={`rounded border px-1.5 py-0.5 text-[9px] font-semibold ${typeStyle.className}`}>{typeStyle.label}</span>
                  <span className="text-[10px] text-muted-foreground">{cronToKorean(batch.cron_schedule)}</span>
                  {lastAt && (
                    <span className={`ml-auto text-[10px] font-semibold ${isFailed ? "text-destructive" : "text-emerald-500"}`}>
                      {isFailed ? "실패" : "성공"} {timeAgo(lastAt)}
                    </span>
                  )}
                </div>

                {/* 확장 패널 */}
                {isExpanded && (
                  <BatchDetailPanel batch={batch} sparkline={sparklineCache[batchId] || []} recentLogs={recentLogsCache[batchId] || []} />
                )}
              </div>
            );
          })}
        </div>

        {/* 배치 타입 선택 모달 */}
        {isBatchTypeModalOpen && (
          <div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm" onClick={() => setIsBatchTypeModalOpen(false)}>
            <div className="w-full max-w-sm rounded-xl border bg-card p-6 shadow-lg" onClick={(e) => e.stopPropagation()}>
              <h2 className="mb-1 text-base font-bold">어떤 배치를 만들까요?</h2>
              <p className="mb-5 text-xs text-muted-foreground">데이터를 가져올 방식을 선택해주세요</p>
              <div className="space-y-2">
                {[
                  { type: "db-to-db" as const, icon: Database, iconColor: "text-cyan-500", title: "DB → DB", desc: "테이블 데이터를 다른 테이블로 복사해요" },
                  { type: "restapi-to-db" as const, icon: Cloud, iconColor: "text-violet-500", title: "API → DB", desc: "외부 API에서 데이터를 가져와 저장해요" },
                  { type: "node-flow" as const, icon: Workflow, iconColor: "text-indigo-500", title: "노드 플로우", desc: "만들어 둔 플로우를 자동으로 실행해요" },
                ].map((opt) => (
                  <button
                    key={opt.type}
                    className="flex w-full items-center gap-3.5 rounded-lg border p-4 text-left transition-all hover:border-primary/30 hover:bg-primary/5"
                    onClick={() => handleBatchTypeSelect(opt.type)}
                  >
                    <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-muted">
                      <opt.icon className={`h-[18px] w-[18px] ${opt.iconColor}`} />
                    </div>
                    <div>
                      <p className="text-sm font-semibold">{opt.title}</p>
                      <p className="text-[11px] text-muted-foreground">{opt.desc}</p>
                    </div>
                  </button>
                ))}
              </div>
              <button onClick={() => setIsBatchTypeModalOpen(false)} className="mt-4 w-full rounded-md border py-2.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground">
                닫기
              </button>
            </div>
          </div>
        )}
      </div>
      <ScrollToTop />
    </div>
  );
}
