"use client";

/**
 * EDataTable — 직접 구현 페이지용 공통 데이터 테이블 컴포넌트
 *
 * 프리셋 디자인 규격(Type A~F) 기반, shadcn/ui 위에 구축.
 * 기능: 정렬, 헤더 필터, 컬럼 드래그 이동, 인라인 편집, 체크박스, 페이지네이션
 */

import React, { useState, useEffect, useRef, useCallback, useMemo } from "react";
import {
  DndContext, closestCenter, PointerSensor, useSensor, useSensors, DragEndEvent,
} from "@dnd-kit/core";
import { SortableContext, horizontalListSortingStrategy, useSortable, arrayMove } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Checkbox } from "@/components/ui/checkbox";
import { Badge } from "@/components/ui/badge";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Input } from "@/components/ui/input";
import {
  Filter, Check, Search, X, Loader2, Inbox, GripVertical,
  ChevronLeft, ChevronRight, ChevronDown, ChevronsLeft, ChevronsRight, ArrowUp, ArrowDown,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { toast } from "sonner";
import { apiClient } from "@/lib/api/client";

// ─── 타입 ───

export interface EDataTableColumn<T = any> {
  key: string;
  label: string;
  width?: string;
  minWidth?: string;
  align?: "left" | "center" | "right";
  sortable?: boolean;
  filterable?: boolean;
  editable?: boolean;
  inputType?: "text" | "number" | "date" | "select";
  selectOptions?: { value: string; label: string }[];
  formatNumber?: boolean;
  truncate?: boolean;
  render?: (value: any, row: T, rowIndex: number) => React.ReactNode;
}

export interface SortState {
  key: string;
  direction: "asc" | "desc";
}

export interface EDataTableProps<T extends Record<string, any> = any> {
  columns: EDataTableColumn<T>[];
  data: T[];
  rowKey?: (row: T) => string;

  loading?: boolean;
  emptyMessage?: string;
  emptyIcon?: React.ReactNode;

  selectedId?: string | null;
  onSelect?: (id: string | null) => void;

  showCheckbox?: boolean;
  checkedIds?: string[];
  onCheckedChange?: (ids: string[]) => void;

  onRowClick?: (row: T, index: number) => void;
  onRowDoubleClick?: (row: T, index: number) => void;

  onCellEdit?: (rowId: string, columnKey: string, newValue: any, row: T) => void;
  tableName?: string;

  sort?: SortState | null;
  onSortChange?: (sort: SortState | null) => void;

  draggableColumns?: boolean;
  onColumnOrderChange?: (columns: EDataTableColumn<T>[]) => void;
  columnOrderKey?: string;

  showRowNumber?: boolean;
  showPagination?: boolean;
  defaultPageSize?: number;

  className?: string;
}

// ─── 유틸 ───

const fmtNum = (val: any) => {
  if (val == null || val === "") return "";
  const n = Number(String(val).replace(/,/g, ""));
  if (isNaN(n)) return String(val);
  return n.toLocaleString();
};

const getRowId = (row: any, rowKey?: (row: any) => string) => {
  if (rowKey) return rowKey(row);
  return row.id ?? row._id ?? "";
};

// ─── SortableHeaderCell ───

function SortableHeaderCell({
  col, sortKey, sortDir, onSort,
  headerFilterValues, uniqueValues, onToggleFilter, onClearFilter,
  draggable,
}: {
  col: EDataTableColumn;
  sortKey: string | null;
  sortDir: "asc" | "desc";
  onSort: (key: string) => void;
  headerFilterValues: Set<string>;
  uniqueValues: string[];
  onToggleFilter: (colKey: string, value: string) => void;
  onClearFilter: (colKey: string) => void;
  draggable: boolean;
}) {
  const [filterSearch, setFilterSearch] = useState("");
  const {
    attributes, listeners, setNodeRef, transform, transition, isDragging,
  } = useSortable({ id: col.key, disabled: !draggable });

  const style: React.CSSProperties = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0.5 : 1,
  };

  const isSorted = sortKey === col.key;
  const hasFilter = headerFilterValues.size > 0;
  const filteredUniqueValues = uniqueValues.filter(
    (v) => !filterSearch || v.toLowerCase().includes(filterSearch.toLowerCase())
  );

  return (
    <TableHead
      ref={setNodeRef}
      style={style}
      className={cn(
        col.width, col.minWidth,
        "text-[11px] font-bold uppercase tracking-wide text-muted-foreground select-none relative overflow-hidden",
        col.align === "right" && "text-right",
        col.align === "center" && "text-center",
      )}
    >
      <div className={cn(
        "inline-flex items-center gap-1",
        col.align === "right" && "justify-end w-full",
        col.align === "center" && "justify-center w-full",
      )}>
        {/* 드래그 핸들 */}
        {draggable && (
          <div
            {...attributes}
            {...listeners}
            className="cursor-grab text-muted-foreground/40 hover:text-muted-foreground shrink-0"
          >
            <GripVertical className="h-3 w-3" />
          </div>
        )}

        {/* 컬럼 라벨 + 정렬 */}
        <div
          className="flex items-center gap-1 cursor-pointer min-w-0"
          onClick={(e) => {
            e.stopPropagation();
            if (col.sortable !== false) onSort(col.key);
          }}
        >
          <span className="truncate">{col.label}</span>
          {isSorted && (
            sortDir === "asc"
              ? <ArrowUp className="h-3 w-3 text-primary shrink-0" />
              : <ArrowDown className="h-3 w-3 text-primary shrink-0" />
          )}
        </div>

        {/* 필터 아이콘 + Popover */}
        {col.filterable !== false && uniqueValues.length > 0 && (
          <Popover>
            <PopoverTrigger asChild>
              <button
                onClick={(e) => e.stopPropagation()}
                className={cn(
                  "hover:bg-primary/20 rounded p-0.5 transition-colors shrink-0",
                  hasFilter && "text-primary bg-primary/10",
                )}
                title="필터"
              >
                <Filter className="h-3 w-3" />
              </button>
            </PopoverTrigger>
            <PopoverContent className="w-56 p-2" align="start" onClick={(e) => e.stopPropagation()}>
              <div className="space-y-2">
                <div className="flex items-center justify-between border-b pb-2">
                  <span className="text-xs font-medium">필터: {col.label}</span>
                  {hasFilter && (
                    <button onClick={() => onClearFilter(col.key)} className="text-primary text-xs hover:underline">
                      초기화
                    </button>
                  )}
                </div>
                <div className="relative">
                  <Search className="absolute left-2 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground" />
                  <Input
                    value={filterSearch}
                    onChange={(e) => setFilterSearch(e.target.value)}
                    placeholder="검색..."
                    className="h-7 text-xs pl-7"
                  />
                </div>
                <div className="max-h-52 space-y-0.5 overflow-y-auto">
                  {filteredUniqueValues.slice(0, 100).map((val) => {
                    const isSelected = headerFilterValues.has(val);
                    return (
                      <div
                        key={val}
                        className={cn(
                          "hover:bg-muted flex cursor-pointer items-center gap-2 rounded px-2 py-1.5 text-xs",
                          isSelected && "bg-primary/10",
                        )}
                        onClick={() => onToggleFilter(col.key, val)}
                      >
                        <div className={cn(
                          "flex h-4 w-4 items-center justify-center rounded border shrink-0",
                          isSelected ? "bg-primary border-primary" : "border-input",
                        )}>
                          {isSelected && <Check className="text-primary-foreground h-3 w-3" />}
                        </div>
                        <span className="truncate">{val || "(빈 값)"}</span>
                      </div>
                    );
                  })}
                  {filteredUniqueValues.length > 100 && (
                    <div className="text-muted-foreground px-2 py-1 text-xs">
                      ...외 {filteredUniqueValues.length - 100}개
                    </div>
                  )}
                </div>
              </div>
            </PopoverContent>
          </Popover>
        )}
      </div>
    </TableHead>
  );
}

// ─── EDataTable ───

export function EDataTable<T extends Record<string, any> = any>({
  columns: initialColumns,
  data,
  rowKey,
  loading = false,
  emptyMessage = "데이터가 없어요",
  emptyIcon,
  selectedId,
  onSelect,
  showCheckbox = false,
  checkedIds = [],
  onCheckedChange,
  onRowClick,
  onRowDoubleClick,
  onCellEdit,
  tableName,
  sort: externalSort,
  onSortChange,
  draggableColumns = true,
  onColumnOrderChange,
  columnOrderKey,
  showRowNumber = false,
  showPagination = true,
  defaultPageSize = 50,
  className,
}: EDataTableProps<T>) {
  const [columns, setColumns] = useState(initialColumns);
  useEffect(() => { setColumns(initialColumns); }, [initialColumns]);

  // 정렬
  const [internalSort, setInternalSort] = useState<SortState | null>(null);
  const sortState = externalSort !== undefined ? externalSort : internalSort;

  // 헤더 필터
  const [headerFilters, setHeaderFilters] = useState<Record<string, Set<string>>>({});

  // 페이지네이션
  const [currentPage, setCurrentPage] = useState(1);
  const [pageSize, setPageSize] = useState(defaultPageSize);
  const [pageSizeInput, setPageSizeInput] = useState(String(defaultPageSize));

  // 그룹 접기/펼치기
  const [collapsedGroups, setCollapsedGroups] = useState<Set<string>>(new Set());
  const toggleGroup = (groupValue: string) => {
    setCollapsedGroups((prev) => {
      const next = new Set(prev);
      if (next.has(groupValue)) next.delete(groupValue); else next.add(groupValue);
      return next;
    });
  };

  // 인라인 편집
  const [editingCell, setEditingCell] = useState<{ rowId: string; colKey: string } | null>(null);
  const [editValue, setEditValue] = useState("");
  const editRef = useRef<HTMLInputElement | HTMLSelectElement>(null);

  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 8 } })
  );

  // localStorage에서 컬럼 순서 복원
  useEffect(() => {
    if (!columnOrderKey) return;
    const saved = localStorage.getItem(`edatatable_col_order_${columnOrderKey}`);
    if (saved) {
      try {
        const order = JSON.parse(saved) as string[];
        const reordered = order
          .map((key) => initialColumns.find((c) => c.key === key))
          .filter(Boolean) as EDataTableColumn<T>[];
        const remaining = initialColumns.filter((c) => !order.includes(c.key));
        setColumns([...reordered, ...remaining]);
      } catch { /* skip */ }
    }
  }, [columnOrderKey]); // eslint-disable-line react-hooks/exhaustive-deps

  // 컬럼별 고유값
  const columnUniqueValues = useMemo(() => {
    const result: Record<string, string[]> = {};
    for (const col of columns) {
      if (col.filterable === false) continue;
      const values = new Set<string>();
      data.forEach((row) => {
        const val = row[col.key];
        if (val !== null && val !== undefined && val !== "") {
          values.add(String(val));
        }
      });
      result[col.key] = Array.from(values).sort();
    }
    return result;
  }, [data, columns]);

  // 드래그 완료
  const handleDragEnd = (event: DragEndEvent) => {
    const { active, over } = event;
    if (!over || active.id === over.id) return;
    setColumns((prev) => {
      const oldIndex = prev.findIndex((c) => c.key === active.id);
      const newIndex = prev.findIndex((c) => c.key === over.id);
      const next = arrayMove(prev, oldIndex, newIndex);
      if (columnOrderKey) {
        localStorage.setItem(`edatatable_col_order_${columnOrderKey}`, JSON.stringify(next.map((c) => c.key)));
      }
      onColumnOrderChange?.(next);
      return next;
    });
  };

  // 정렬
  const handleSort = (key: string) => {
    const newSort: SortState | null = sortState?.key === key
      ? sortState.direction === "asc"
        ? { key, direction: "desc" }
        : null
      : { key, direction: "asc" };

    if (onSortChange) {
      onSortChange(newSort);
    } else {
      setInternalSort(newSort);
    }
  };

  // 헤더 필터
  const toggleHeaderFilter = (colKey: string, value: string) => {
    setHeaderFilters((prev) => {
      const next = { ...prev };
      const set = new Set(next[colKey] || []);
      if (set.has(value)) set.delete(value); else set.add(value);
      if (set.size === 0) delete next[colKey]; else next[colKey] = set;
      return next;
    });
  };

  const clearHeaderFilter = (colKey: string) => {
    setHeaderFilters((prev) => {
      const next = { ...prev };
      delete next[colKey];
      return next;
    });
  };

  // 필터 + 정렬
  const processedData = useMemo(() => {
    let result = [...data];

    // 헤더 필터
    if (Object.keys(headerFilters).length > 0) {
      result = result.filter((row) =>
        Object.entries(headerFilters).every(([colKey, values]) => {
          if (values.size === 0) return true;
          const cellVal = row[colKey] != null ? String(row[colKey]) : "";
          return values.has(cellVal);
        })
      );
    }

    // 정렬 (외부 정렬이 아닌 경우만)
    if (sortState && !onSortChange) {
      const { key, direction } = sortState;
      result.sort((a, b) => {
        const av = a[key] ?? "";
        const bv = b[key] ?? "";
        const na = Number(av);
        const nb = Number(bv);
        if (!isNaN(na) && !isNaN(nb)) return direction === "asc" ? na - nb : nb - na;
        return direction === "asc"
          ? String(av).localeCompare(String(bv))
          : String(bv).localeCompare(String(av));
      });
    }

    return result;
  }, [data, headerFilters, sortState, onSortChange]);

  // 필터/데이터 변경 시 1페이지 리셋
  useEffect(() => { setCurrentPage(1); }, [data, headerFilters]);

  // 페이지네이션
  const totalItems = processedData.length;
  const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
  const safePage = Math.min(currentPage, totalPages);

  useEffect(() => {
    if (currentPage > totalPages) setCurrentPage(totalPages);
  }, [currentPage, totalPages]);

  const pageOffset = (safePage - 1) * pageSize;
  const paginatedDataRaw = showPagination
    ? processedData.slice(pageOffset, pageOffset + pageSize)
    : processedData;

  // 접힌 그룹의 데이터 행 숨김
  const paginatedData = useMemo(() => {
    if (collapsedGroups.size === 0) return paginatedDataRaw;
    let currentGroup: string | null = null;
    return paginatedDataRaw.filter((row) => {
      if ((row as any)._isGroupHeader) {
        currentGroup = (row as any)._groupValue;
        return true; // 헤더는 항상 표시
      }
      if ((row as any)._isGroupSummary) {
        return !collapsedGroups.has((row as any)._groupValue);
      }
      // 일반 행: 현재 그룹이 접혀있으면 숨김
      return !currentGroup || !collapsedGroups.has(currentGroup);
    });
  }, [paginatedDataRaw, collapsedGroups]);

  const applyPageSize = () => {
    const n = parseInt(pageSizeInput, 10);
    if (!isNaN(n) && n >= 1) {
      setPageSize(n);
      setCurrentPage(1);
      setPageSizeInput(String(n));
    } else {
      setPageSizeInput(String(pageSize));
    }
  };

  const getPageNumbers = () => {
    const delta = 2;
    let start = Math.max(1, safePage - delta);
    let end = Math.min(totalPages, safePage + delta);
    if (end - start < delta * 2) {
      if (start === 1) end = Math.min(totalPages, start + delta * 2);
      else if (end === totalPages) start = Math.max(1, end - delta * 2);
    }
    const pages: (number | "...")[] = [];
    if (start > 1) { pages.push(1); if (start > 2) pages.push("..."); }
    for (let i = start; i <= end; i++) pages.push(i);
    if (end < totalPages) { if (end < totalPages - 1) pages.push("..."); pages.push(totalPages); }
    return pages;
  };

  // 인라인 편집
  const startEdit = (rowId: string, colKey: string, currentVal: any) => {
    const col = columns.find((c) => c.key === colKey);
    if (!col?.editable) return;
    setEditingCell({ rowId, colKey });
    setEditValue(currentVal != null ? String(currentVal) : "");
  };

  const saveEdit = useCallback(async () => {
    if (!editingCell) return;
    const { rowId, colKey } = editingCell;
    const row = paginatedData.find((r) => getRowId(r, rowKey) === rowId);
    if (!row) { setEditingCell(null); return; }

    const originalVal = String(row[colKey] ?? "");
    if (originalVal === editValue) { setEditingCell(null); return; }

    if (tableName && row.id) {
      try {
        await apiClient.put(`/table-management/tables/${tableName}/edit`, {
          originalData: { id: row.id },
          updatedData: { [colKey]: editValue || null },
        });
        (row as any)[colKey] = editValue;
        toast.success("저장되었어요");
      } catch {
        toast.error("저장에 실패했어요");
        setEditingCell(null);
        return;
      }
    }

    onCellEdit?.(rowId, colKey, editValue, row as T);
    setEditingCell(null);
  }, [editingCell, editValue, paginatedData, tableName, onCellEdit, rowKey]);

  const cancelEdit = () => setEditingCell(null);

  const handleEditKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === "Enter") { e.preventDefault(); saveEdit(); }
    else if (e.key === "Escape") { e.preventDefault(); cancelEdit(); }
    else if (e.key === "Tab") { e.preventDefault(); saveEdit(); }
  };

  useEffect(() => {
    if (editingCell && editRef.current) {
      editRef.current.focus();
      if ("select" in editRef.current) editRef.current.select();
    }
  }, [editingCell]);

  // 체크박스
  const allChecked = processedData.length > 0 && checkedIds.length === processedData.length;

  // colSpan 계산
  const colSpan = columns.length + (showCheckbox ? 1 : 0) + (showRowNumber ? 1 : 0);

  // 셀 렌더링
  const renderCell = (row: T, col: EDataTableColumn<T>, rowIdx: number) => {
    const id = getRowId(row, rowKey);
    const isEditing = editingCell?.rowId === id && editingCell?.colKey === col.key;
    const val = row[col.key];

    // 편집 모드
    if (isEditing) {
      if (col.inputType === "select" && col.selectOptions) {
        return (
          <select
            ref={editRef as any}
            value={editValue}
            onChange={(e) => setEditValue(e.target.value)}
            onKeyDown={handleEditKeyDown}
            onBlur={() => saveEdit()}
            className="h-8 w-full rounded border border-primary bg-background px-2 text-[13px] focus:ring-1 focus:ring-primary"
          >
            <option value="">선택</option>
            {col.selectOptions.map((o) => (
              <option key={o.value} value={o.value}>{o.label}</option>
            ))}
          </select>
        );
      }
      return (
        <input
          ref={editRef as any}
          type={col.inputType === "number" ? "number" : col.inputType === "date" ? "date" : "text"}
          value={editValue}
          onChange={(e) => setEditValue(e.target.value)}
          onKeyDown={handleEditKeyDown}
          onBlur={() => saveEdit()}
          className={cn(
            "h-8 w-full rounded border border-primary bg-background px-2 text-[13px] focus:ring-1 focus:ring-primary",
            col.align === "right" && "text-right"
          )}
        />
      );
    }

    // 커스텀 렌더러
    if (col.render) {
      return col.render(val, row, rowIdx);
    }

    // 기본 렌더링
    let display: React.ReactNode = val ?? "";
    if (col.formatNumber || col.inputType === "number") display = fmtNum(val);

    return (
      <span
        className={cn(
          col.truncate !== false && "block truncate",
          col.align === "right" && "text-right w-full inline-block",
          col.align === "center" && "text-center w-full inline-block",
        )}
        title={String(val ?? "")}
      >
        {display}
      </span>
    );
  };

  return (
    <div className={cn("flex flex-col h-full flex-1 min-h-0", className)}>
      <div className="flex-1 min-h-0 overflow-auto">
        <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
          <Table noWrapper className="min-w-max">
            <TableHeader className="sticky top-0 z-10 bg-muted shadow-[0_1px_0_0_hsl(var(--border))]">
              <SortableContext items={columns.map((c) => c.key)} strategy={horizontalListSortingStrategy}>
                <TableRow className="bg-muted hover:bg-muted h-10">
                  {/* 체크박스 */}
                  {showCheckbox && (
                    <TableHead className="w-10 text-center">
                      <Checkbox
                        checked={allChecked}
                        onCheckedChange={(checked) => {
                          onCheckedChange?.(checked ? processedData.map((r) => getRowId(r, rowKey)) : []);
                        }}
                      />
                    </TableHead>
                  )}
                  {/* 행번호 */}
                  {showRowNumber && (
                    <TableHead className="w-10 text-center text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
                      #
                    </TableHead>
                  )}
                  {/* 데이터 컬럼 */}
                  {columns.map((col) => (
                    <SortableHeaderCell
                      key={col.key}
                      col={col}
                      sortKey={sortState?.key ?? null}
                      sortDir={sortState?.direction ?? "asc"}
                      onSort={handleSort}
                      headerFilterValues={headerFilters[col.key] || new Set()}
                      uniqueValues={columnUniqueValues[col.key] || []}
                      onToggleFilter={toggleHeaderFilter}
                      onClearFilter={clearHeaderFilter}
                      draggable={draggableColumns}
                    />
                  ))}
                </TableRow>
              </SortableContext>
            </TableHeader>

            <TableBody>
              {loading ? (
                <TableRow>
                  <TableCell colSpan={colSpan} className="py-16 text-center">
                    <Loader2 className="mx-auto h-6 w-6 animate-spin text-primary" />
                  </TableCell>
                </TableRow>
              ) : paginatedData.length === 0 ? (
                <TableRow>
                  <TableCell colSpan={colSpan} className="py-16 text-center">
                    <div className="flex flex-col items-center gap-2 text-muted-foreground">
                      {emptyIcon || <Inbox className="h-8 w-8 opacity-30" />}
                      <span className="text-sm">{emptyMessage}</span>
                    </div>
                  </TableCell>
                </TableRow>
              ) : (
                paginatedData.map((row, rowIdx) => {
                  // 그룹 헤더 행 처리
                  if ((row as any)._isGroupHeader) {
                    const gv = (row as any)._groupValue || "";
                    const gc = (row as any)._groupCount || 0;
                    const isCollapsed = collapsedGroups.has(gv);
                    const totalCols = columns.length + (showCheckbox ? 1 : 0) + (showRowNumber ? 1 : 0);
                    return (
                      <TableRow key={`group-${gv}-${rowIdx}`} className="bg-primary/5 hover:bg-primary/10 cursor-pointer border-t-2 border-primary/20" onClick={() => toggleGroup(gv)}>
                        <TableCell colSpan={totalCols} className="py-2 px-3">
                          <div className="flex items-center gap-2">
                            {isCollapsed ? <ChevronRight className="h-4 w-4 text-primary" /> : <ChevronDown className="h-4 w-4 text-primary" />}
                            <span className="text-sm font-semibold text-primary">{gv}</span>
                            <Badge variant="secondary" className="text-[10px] bg-primary/10 text-primary">{gc}건</Badge>
                          </div>
                        </TableCell>
                      </TableRow>
                    );
                  }

                  // 그룹 소계 행 처리
                  if ((row as any)._isGroupSummary) {
                    const gv = (row as any)._groupValue || "";
                    if (collapsedGroups.has(gv)) return null;
                    return (
                      <TableRow key={`summary-${rowIdx}`} className="bg-muted/60 font-semibold border-t border-primary/20">
                        {showCheckbox && <TableCell />}
                        {showRowNumber && <TableCell />}
                        {columns.map((col) => (
                          <TableCell
                            key={col.key}
                            className={cn(
                              typeof row[col.key] === "number" ? "text-right font-mono text-[13px]" : "text-[13px] text-primary",
                              col.width, col.minWidth,
                            )}
                          >
                            {typeof row[col.key] === "number" ? Number(row[col.key]).toLocaleString() : (row[col.key] || "")}
                          </TableCell>
                        ))}
                      </TableRow>
                    );
                  }

                  const id = getRowId(row, rowKey);
                  const isSelected = selectedId != null && String(selectedId) === String(id);
                  const isChecked = checkedIds.includes(id);
                  const highlighted = isSelected || isChecked;

                  return (
                    <TableRow
                      key={id || rowIdx}
                      className={cn(
                        "cursor-pointer border-l-[3px] border-l-transparent transition-all h-[41px]",
                        highlighted
                          ? "border-l-primary bg-primary/20 dark:bg-primary/15 row-selected"
                          : "hover:bg-accent"
                      )}
                      onClick={() => {
                        onSelect?.(id);
                        onRowClick?.(row, pageOffset + rowIdx);
                        if (showCheckbox && onCheckedChange) {
                          const next = checkedIds.includes(id)
                            ? checkedIds.filter((cid) => cid !== id)
                            : [...checkedIds, id];
                          onCheckedChange(next);
                        }
                      }}
                      onDoubleClick={() => onRowDoubleClick?.(row, pageOffset + rowIdx)}
                    >
                      {showCheckbox && (
                        <TableCell className="text-center" onClick={(e) => e.stopPropagation()}>
                          <Checkbox
                            checked={isChecked}
                            onCheckedChange={(checked) => {
                              const next = checked
                                ? [...checkedIds, id]
                                : checkedIds.filter((cid) => cid !== id);
                              onCheckedChange?.(next);
                            }}
                          />
                        </TableCell>
                      )}
                      {showRowNumber && (
                        <TableCell className="text-center text-[11px] text-muted-foreground font-mono">
                          {pageOffset + rowIdx + 1}
                        </TableCell>
                      )}
                      {columns.map((col) => (
                        <TableCell
                          key={col.key}
                          className={cn(
                            col.width, col.minWidth,
                            col.editable && "cursor-text",
                            col.align === "right" && "text-right",
                            col.align === "center" && "text-center",
                          )}
                          onDoubleClick={(e) => {
                            if (col.editable) {
                              e.stopPropagation();
                              startEdit(id, col.key, row[col.key]);
                            }
                          }}
                        >
                          {renderCell(row, col, pageOffset + rowIdx)}
                        </TableCell>
                      ))}
                    </TableRow>
                  );
                })
              )}
            </TableBody>
          </Table>
        </DndContext>
      </div>

      {/* 페이지네이션 */}
      {showPagination && (
        <div className="flex items-center justify-between border-t px-4 py-2.5 text-xs text-muted-foreground shrink-0">
          <div className="flex items-center gap-3">
            <div className="flex items-center gap-1">
              <span>전체</span>
              <span className="font-medium text-foreground">{totalItems.toLocaleString()}</span>
              <span>건</span>
            </div>
            <div className="flex items-center gap-1.5">
              <Input
                type="number"
                min={1}
                value={pageSizeInput}
                onChange={(e) => setPageSizeInput(e.target.value)}
                onBlur={applyPageSize}
                onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); applyPageSize(); } }}
                className="h-7 w-16 text-center text-xs"
              />
              <span>건씩 보기</span>
            </div>
          </div>

          <div className="flex items-center gap-0.5">
            <button onClick={() => setCurrentPage(1)} disabled={safePage === 1}
              className="h-7 w-7 flex items-center justify-center rounded hover:bg-muted disabled:opacity-30 disabled:cursor-not-allowed">
              <ChevronsLeft className="h-3.5 w-3.5" />
            </button>
            <button onClick={() => setCurrentPage((p) => Math.max(1, p - 1))} disabled={safePage === 1}
              className="h-7 w-7 flex items-center justify-center rounded hover:bg-muted disabled:opacity-30 disabled:cursor-not-allowed">
              <ChevronLeft className="h-3.5 w-3.5" />
            </button>
            {getPageNumbers().map((page, idx) =>
              page === "..." ? (
                <span key={`dot-${idx}`} className="h-7 w-7 flex items-center justify-center text-muted-foreground">...</span>
              ) : (
                <button key={page} onClick={() => setCurrentPage(page as number)}
                  className={cn("h-7 w-7 flex items-center justify-center rounded text-xs",
                    page === safePage ? "bg-primary text-primary-foreground font-medium" : "hover:bg-muted")}>
                  {page}
                </button>
              )
            )}
            <button onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))} disabled={safePage === totalPages}
              className="h-7 w-7 flex items-center justify-center rounded hover:bg-muted disabled:opacity-30 disabled:cursor-not-allowed">
              <ChevronRight className="h-3.5 w-3.5" />
            </button>
            <button onClick={() => setCurrentPage(totalPages)} disabled={safePage === totalPages}
              className="h-7 w-7 flex items-center justify-center rounded hover:bg-muted disabled:opacity-30 disabled:cursor-not-allowed">
              <ChevronsRight className="h-3.5 w-3.5" />
            </button>
          </div>

          <div className="flex items-center gap-1.5">
            <Input
              type="number"
              min={1}
              max={totalPages}
              placeholder={String(safePage)}
              className="h-7 w-14 text-center text-xs"
              onKeyDown={(e) => {
                if (e.key === "Enter") {
                  const val = parseInt((e.target as HTMLInputElement).value, 10);
                  if (!isNaN(val) && val >= 1 && val <= totalPages) {
                    setCurrentPage(val);
                    (e.target as HTMLInputElement).value = "";
                    (e.target as HTMLInputElement).blur();
                  }
                }
              }}
              onBlur={(e) => {
                const val = parseInt(e.target.value, 10);
                if (!isNaN(val) && val >= 1 && val <= totalPages) {
                  setCurrentPage(val);
                }
                e.target.value = "";
              }}
            />
            <span>/ {totalPages} 페이지</span>
          </div>
        </div>
      )}
    </div>
  );
}
