"use client";

import { useState, useCallback, useEffect, useRef, useMemo } from "react";
import { cn } from "@/lib/utils";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { Switch } from "@/components/ui/switch";
import { Search, ChevronRight, Loader2, X, CalendarDays } from "lucide-react";
import { Calendar } from "@/components/ui/calendar";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import { format, startOfWeek, endOfWeek, startOfMonth, endOfMonth } from "date-fns";
import { ko } from "date-fns/locale";
import { usePopEvent } from "@/hooks/pop";
import { dataApi } from "@/lib/api/data";
import type {
  PopSearchConfig,
  DatePresetOption,
  DateSelectionMode,
  CalendarDisplayMode,
  ModalSelectConfig,
  ModalSearchMode,
  ModalFilterTab,
} from "./types";
import {
  DATE_PRESET_LABELS,
  computeDateRange,
  DEFAULT_SEARCH_CONFIG,
  normalizeInputType,
  MODAL_FILTER_TAB_LABELS,
  getGroupKey,
} from "./types";

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

interface PopSearchComponentProps {
  config: PopSearchConfig;
  label?: string;
  screenId?: string;
  componentId?: string;
}

const DEFAULT_CONFIG = DEFAULT_SEARCH_CONFIG;

export function PopSearchComponent({
  config: rawConfig,
  label,
  screenId,
  componentId,
}: PopSearchComponentProps) {
  const config = { ...DEFAULT_CONFIG, ...(rawConfig || {}) };
  const { publish, subscribe, setSharedData } = usePopEvent(screenId || "");
  const [value, setValue] = useState<unknown>(config.defaultValue ?? "");
  const [modalDisplayText, setModalDisplayText] = useState("");
  const [simpleModalOpen, setSimpleModalOpen] = useState(false);

  const normalizedType = normalizeInputType(config.inputType as string);
  const isModalType = normalizedType === "modal";
  const fieldKey = isModalType
    ? (config.modalConfig?.valueField || config.fieldName || componentId || "search")
    : (config.fieldName || componentId || "search");

  const resolveFilterMode = useCallback(() => {
    if (config.filterMode) return config.filterMode;
    if (normalizedType === "date") {
      const mode: DateSelectionMode = config.dateSelectionMode || "single";
      return mode === "range" ? "range" : "equals";
    }
    return "contains";
  }, [config.filterMode, config.dateSelectionMode, normalizedType]);

  const emitFilterChanged = useCallback(
    (newValue: unknown) => {
      setValue(newValue);
      setSharedData(`search_${fieldKey}`, newValue);

      if (componentId) {
        const filterColumns = config.filterColumns?.length ? config.filterColumns : [fieldKey];
        publish(`__comp_output__${componentId}__filter_value`, {
          fieldName: fieldKey,
          filterColumns,
          value: newValue,
          filterMode: resolveFilterMode(),
        });
      }

      publish("filter_changed", { [fieldKey]: newValue });
    },
    [fieldKey, publish, setSharedData, componentId, resolveFilterMode, config.filterColumns]
  );

  useEffect(() => {
    if (!componentId) return;
    const unsub = subscribe(
      `__comp_input__${componentId}__set_value`,
      (payload: unknown) => {
        const data = payload as { value?: unknown; displayText?: string } | unknown;
        const incoming = typeof data === "object" && data && "value" in data
          ? (data as { value: unknown }).value
          : data;
        if (isModalType && incoming != null) {
          setModalDisplayText(String(incoming));
        }
        emitFilterChanged(incoming);
      }
    );
    return unsub;
  }, [componentId, subscribe, emitFilterChanged, isModalType]);

  useEffect(() => {
    const unsub = subscribe("scan_auto_fill", (payload: unknown) => {
      const data = payload as Record<string, unknown> | null;
      if (!data || typeof data !== "object") return;
      const myKey = config.fieldName;
      if (!myKey) return;
      const targetKeys = config.filterColumns?.length ? config.filterColumns : [myKey];
      for (const key of targetKeys) {
        if (key in data) {
          if (isModalType) setModalDisplayText(String(data[key]));
          emitFilterChanged(data[key]);
          return;
        }
      }
      if (myKey in data) {
        if (isModalType) setModalDisplayText(String(data[myKey]));
        emitFilterChanged(data[myKey]);
      }
    });
    return unsub;
  }, [subscribe, emitFilterChanged, config.fieldName, config.filterColumns, isModalType]);

  const handleModalOpen = useCallback(() => {
    if (!config.modalConfig) return;
    setSimpleModalOpen(true);
  }, [config.modalConfig]);

  const handleSimpleModalSelect = useCallback(
    (row: Record<string, unknown>) => {
      const mc = config.modalConfig;
      const display = mc?.displayField ? String(row[mc.displayField] ?? "") : "";
      const filterVal = mc?.valueField ? String(row[mc.valueField] ?? "") : "";

      setModalDisplayText(display);
      emitFilterChanged(filterVal);
      setSimpleModalOpen(false);
    },
    [config.modalConfig, emitFilterChanged]
  );

  const handleModalClear = useCallback(() => {
    setModalDisplayText("");
    emitFilterChanged("");
  }, [emitFilterChanged]);

  const showLabel = config.labelVisible !== false && !!config.labelText;

  return (
    <div
      className="flex h-full w-full flex-col items-center justify-center gap-0.5 overflow-hidden p-1.5"
    >
      {showLabel && (
        <span className="w-full shrink-0 truncate text-[10px] font-medium text-muted-foreground">
          {config.labelText}
        </span>
      )}
      <div className="min-w-0 w-full flex-1 flex flex-col justify-center">
        <SearchInputRenderer
          config={config}
          value={value}
          onChange={emitFilterChanged}
          modalDisplayText={modalDisplayText}
          onModalOpen={handleModalOpen}
          onModalClear={handleModalClear}
        />
      </div>

      {isModalType && config.modalConfig && (
        <ModalDialog
          open={simpleModalOpen}
          onOpenChange={setSimpleModalOpen}
          modalConfig={config.modalConfig}
          title={config.labelText || "선택"}
          onSelect={handleSimpleModalSelect}
        />
      )}
    </div>
  );
}

// ========================================
// 서브타입 분기 렌더러
// ========================================

interface InputRendererProps {
  config: PopSearchConfig;
  value: unknown;
  onChange: (v: unknown) => void;
  modalDisplayText?: string;
  onModalOpen?: () => void;
  onModalClear?: () => void;
}

function SearchInputRenderer({ config, value, onChange, modalDisplayText, onModalOpen, onModalClear }: InputRendererProps) {
  const normalized = normalizeInputType(config.inputType as string);
  switch (normalized) {
    case "text":
    case "number":
      return <TextSearchInput config={config} value={String(value ?? "")} onChange={onChange} />;
    case "select":
      return <SelectSearchInput config={config} value={String(value ?? "")} onChange={onChange} />;
    case "date": {
      const dateMode: DateSelectionMode = config.dateSelectionMode || "single";
      return dateMode === "range"
        ? <DateRangeInput config={config} value={value} onChange={onChange} />
        : <DateSingleInput config={config} value={String(value ?? "")} onChange={onChange} />;
    }
    case "date-preset":
      return <DatePresetSearchInput config={config} value={value} onChange={onChange} />;
    case "toggle":
      return <ToggleSearchInput value={Boolean(value)} onChange={onChange} />;
    case "modal":
      return <ModalSearchInput config={config} displayText={modalDisplayText || ""} onClick={onModalOpen} onClear={onModalClear} />;
    default:
      return <PlaceholderInput inputType={config.inputType} />;
  }
}

// ========================================
// text 서브타입
// ========================================

function TextSearchInput({ config, value, onChange }: { config: PopSearchConfig; value: string; onChange: (v: unknown) => void }) {
  const [inputValue, setInputValue] = useState(value);
  const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(() => { setInputValue(value); }, [value]);
  useEffect(() => { return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, []);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const v = e.target.value;
    setInputValue(v);
    if (debounceRef.current) clearTimeout(debounceRef.current);
    const ms = config.debounceMs ?? 500;
    if (ms > 0) debounceRef.current = setTimeout(() => onChange(v), ms);
  };

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === "Enter" && config.triggerOnEnter !== false) {
      if (debounceRef.current) clearTimeout(debounceRef.current);
      onChange(inputValue);
    }
  };

  const isNumber = config.inputType === "number";

  return (
    <div className="relative h-full">
      <Search className="absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
      <Input
        type={isNumber ? "number" : "text"}
        inputMode={isNumber ? "numeric" : undefined}
        value={inputValue}
        onChange={handleChange}
        onKeyDown={handleKeyDown}
        placeholder={config.placeholder || (isNumber ? "숫자 입력" : "검색어 입력")}
        className="h-full min-h-8 pl-7 text-xs"
      />
    </div>
  );
}

// ========================================
// date 서브타입 - 단일 날짜
// ========================================

function DateSingleInput({ config, value, onChange }: { config: PopSearchConfig; value: string; onChange: (v: unknown) => void }) {
  const [open, setOpen] = useState(false);
  const useModal = config.calendarDisplay === "modal";
  const selected = value ? new Date(value + "T00:00:00") : undefined;

  const handleSelect = useCallback(
    (day: Date | undefined) => {
      if (!day) return;
      onChange(format(day, "yyyy-MM-dd"));
      setOpen(false);
    },
    [onChange]
  );

  const handleClear = useCallback(
    (e: React.MouseEvent) => {
      e.stopPropagation();
      onChange("");
    },
    [onChange]
  );

  const triggerButton = (
    <Button
      variant="outline"
      className={cn(
        "h-full min-h-8 w-full justify-start gap-1.5 px-2 text-xs font-normal",
        !value && "text-muted-foreground"
      )}
      onClick={useModal ? () => setOpen(true) : undefined}
    >
      <CalendarDays className="h-3.5 w-3.5 shrink-0" />
      <span className="flex-1 truncate text-left">
        {value ? format(new Date(value + "T00:00:00"), "yyyy.MM.dd (EEE)", { locale: ko }) : (config.placeholder || "날짜 선택")}
      </span>
      {value && (
        <span
          role="button"
          tabIndex={-1}
          onClick={handleClear}
          onKeyDown={(e) => { if (e.key === "Enter") handleClear(e as unknown as React.MouseEvent); }}
          className="shrink-0 text-muted-foreground hover:text-foreground"
        >
          <X className="h-3 w-3" />
        </span>
      )}
    </Button>
  );

  if (useModal) {
    return (
      <>
        {triggerButton}
        <Dialog open={open} onOpenChange={setOpen}>
          <DialogContent className="max-w-[95vw] sm:max-w-[360px] p-0">
            <DialogHeader className="px-4 pt-4 pb-0">
              <DialogTitle className="text-sm">날짜 선택</DialogTitle>
            </DialogHeader>
            <div className="flex justify-center pb-4">
              <Calendar
                mode="single"
                selected={selected}
                onSelect={handleSelect}
                locale={ko}
                defaultMonth={selected || new Date()}
                className="touch-date-calendar"
              />
            </div>
          </DialogContent>
        </Dialog>
      </>
    );
  }

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        {triggerButton}
      </PopoverTrigger>
      <PopoverContent className="w-auto p-0" align="start">
        <Calendar
          mode="single"
          selected={selected}
          onSelect={handleSelect}
          locale={ko}
          defaultMonth={selected || new Date()}
        />
      </PopoverContent>
    </Popover>
  );
}

// ========================================
// date 서브타입 - 기간 선택 (프리셋 + Calendar Range)
// ========================================

interface DateRangeValue { from?: string; to?: string }

const RANGE_PRESETS = [
  { key: "today", label: "오늘" },
  { key: "this-week", label: "이번주" },
  { key: "this-month", label: "이번달" },
] as const;

function computeRangePreset(key: string): DateRangeValue {
  const now = new Date();
  const fmt = (d: Date) => format(d, "yyyy-MM-dd");
  switch (key) {
    case "today":
      return { from: fmt(now), to: fmt(now) };
    case "this-week":
      return { from: fmt(startOfWeek(now, { weekStartsOn: 1 })), to: fmt(endOfWeek(now, { weekStartsOn: 1 })) };
    case "this-month":
      return { from: fmt(startOfMonth(now)), to: fmt(endOfMonth(now)) };
    default:
      return {};
  }
}

function DateRangeInput({ config, value, onChange }: { config: PopSearchConfig; value: unknown; onChange: (v: unknown) => void }) {
  const [open, setOpen] = useState(false);
  const useModal = config.calendarDisplay === "modal";

  const rangeVal: DateRangeValue = (typeof value === "object" && value !== null)
    ? value as DateRangeValue
    : (typeof value === "string" && value ? { from: value, to: value } : {});

  const calendarRange = useMemo(() => {
    if (!rangeVal.from) return undefined;
    return {
      from: new Date(rangeVal.from + "T00:00:00"),
      to: rangeVal.to ? new Date(rangeVal.to + "T00:00:00") : undefined,
    };
  }, [rangeVal.from, rangeVal.to]);

  const activePreset = RANGE_PRESETS.find((p) => {
    const preset = computeRangePreset(p.key);
    return preset.from === rangeVal.from && preset.to === rangeVal.to;
  })?.key ?? null;

  const handlePreset = useCallback(
    (key: string) => {
      const preset = computeRangePreset(key);
      onChange(preset);
    },
    [onChange]
  );

  const handleRangeSelect = useCallback(
    (range: { from?: Date; to?: Date } | undefined) => {
      if (!range?.from) return;
      const from = format(range.from, "yyyy-MM-dd");
      const to = range.to ? format(range.to, "yyyy-MM-dd") : from;
      onChange({ from, to });
      if (range.to) setOpen(false);
    },
    [onChange]
  );

  const handleClear = useCallback(
    (e: React.MouseEvent) => {
      e.stopPropagation();
      onChange({});
    },
    [onChange]
  );

  const displayText = rangeVal.from
    ? rangeVal.from === rangeVal.to
      ? format(new Date(rangeVal.from + "T00:00:00"), "MM/dd (EEE)", { locale: ko })
      : `${format(new Date(rangeVal.from + "T00:00:00"), "MM/dd", { locale: ko })} ~ ${rangeVal.to ? format(new Date(rangeVal.to + "T00:00:00"), "MM/dd", { locale: ko }) : ""}`
    : "";

  const presetBar = (
    <div className="flex gap-1 px-3">
      {RANGE_PRESETS.map((p) => (
        <Button
          key={p.key}
          variant={activePreset === p.key ? "default" : "outline"}
          size="sm"
          className="h-7 flex-1 px-1 text-[10px]"
          onClick={() => {
            handlePreset(p.key);
            setOpen(false);
          }}
        >
          {p.label}
        </Button>
      ))}
    </div>
  );

  const calendarEl = (
    <Calendar
      mode="range"
      selected={calendarRange}
      onSelect={handleRangeSelect}
      locale={ko}
      defaultMonth={calendarRange?.from || new Date()}
      numberOfMonths={1}
      className={useModal ? "touch-date-calendar" : undefined}
    />
  );

  const triggerButton = (
    <Button
      variant="outline"
      className={cn(
        "h-full min-h-8 w-full justify-start gap-1.5 px-2 text-xs font-normal",
        !rangeVal.from && "text-muted-foreground"
      )}
      onClick={useModal ? () => setOpen(true) : undefined}
    >
      <CalendarDays className="h-3.5 w-3.5 shrink-0" />
      <span className="flex-1 truncate text-left">
        {displayText || (config.placeholder || "기간 선택")}
      </span>
      {rangeVal.from && (
        <span
          role="button"
          tabIndex={-1}
          onClick={handleClear}
          onKeyDown={(e) => { if (e.key === "Enter") handleClear(e as unknown as React.MouseEvent); }}
          className="shrink-0 text-muted-foreground hover:text-foreground"
        >
          <X className="h-3 w-3" />
        </span>
      )}
    </Button>
  );

  if (useModal) {
    return (
      <>
        {triggerButton}
        <Dialog open={open} onOpenChange={setOpen}>
          <DialogContent className="max-w-[95vw] sm:max-w-[360px] p-0">
            <DialogHeader className="px-4 pt-4 pb-0">
              <DialogTitle className="text-sm">기간 선택</DialogTitle>
            </DialogHeader>
            <div className="space-y-2 pb-4">
              {presetBar}
              <div className="flex justify-center">
                {calendarEl}
              </div>
            </div>
          </DialogContent>
        </Dialog>
      </>
    );
  }

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        {triggerButton}
      </PopoverTrigger>
      <PopoverContent className="w-auto p-0" align="start">
        <div className="space-y-2 pt-2 pb-1">
          {presetBar}
          {calendarEl}
        </div>
      </PopoverContent>
    </Popover>
  );
}

// ========================================
// select 서브타입
// ========================================

function SelectSearchInput({ config, value, onChange }: { config: PopSearchConfig; value: string; onChange: (v: unknown) => void }) {
  return (
    <Select value={value || undefined} onValueChange={(v) => onChange(v)}>
      <SelectTrigger className="h-full min-h-8 text-xs">
        <SelectValue placeholder={config.placeholder || "선택"} />
      </SelectTrigger>
      <SelectContent>
        {(config.options || []).map((opt) => (
          <SelectItem key={opt.value} value={opt.value} className="text-xs">{opt.label}</SelectItem>
        ))}
      </SelectContent>
    </Select>
  );
}

// ========================================
// date-preset 서브타입
// ========================================

function DatePresetSearchInput({ config, value, onChange }: { config: PopSearchConfig; value: unknown; onChange: (v: unknown) => void }) {
  const presets: DatePresetOption[] = config.datePresets || ["today", "this-week", "this-month"];
  const currentPreset = value && typeof value === "object" && "preset" in (value as Record<string, unknown>)
    ? (value as Record<string, unknown>).preset
    : value;

  const handleSelect = (preset: DatePresetOption) => {
    if (preset === "custom") { onChange({ preset: "custom", from: "", to: "" }); return; }
    const range = computeDateRange(preset);
    if (range) onChange(range);
  };

  return (
    <div className="flex h-full flex-wrap items-center gap-1">
      {presets.map((preset) => (
        <Button key={preset} variant={currentPreset === preset ? "default" : "outline"} size="sm" className="h-7 px-2 text-[10px]" onClick={() => handleSelect(preset)}>
          {DATE_PRESET_LABELS[preset]}
        </Button>
      ))}
    </div>
  );
}

// ========================================
// toggle 서브타입
// ========================================

function ToggleSearchInput({ value, onChange }: { value: boolean; onChange: (v: unknown) => void }) {
  return (
    <div className="flex h-full items-center gap-2">
      <Switch checked={value} onCheckedChange={(checked) => onChange(checked)} />
      <span className="text-xs text-muted-foreground">{value ? "ON" : "OFF"}</span>
    </div>
  );
}

// ========================================
// modal 서브타입: 읽기 전용 표시 + 클릭으로 모달 열기
// ========================================

function ModalSearchInput({ config, displayText, onClick, onClear }: { config: PopSearchConfig; displayText: string; onClick?: () => void; onClear?: () => void }) {
  const hasValue = !!displayText;

  return (
    <div
      className="flex h-full min-h-8 cursor-pointer items-center rounded-md border border-input bg-background px-3 transition-colors hover:bg-accent"
      role="button"
      tabIndex={0}
      onClick={onClick}
      onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") onClick?.(); }}
    >
      <span className={`flex-1 truncate text-xs ${hasValue ? "" : "text-muted-foreground"}`}>
        {displayText || config.placeholder || "선택..."}
      </span>
      {hasValue && onClear ? (
        <button
          type="button"
          className="ml-1 shrink-0 rounded-full p-0.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive"
          onClick={(e) => { e.stopPropagation(); onClear(); }}
          aria-label="선택 해제"
        >
          <X className="h-3.5 w-3.5" />
        </button>
      ) : (
        <ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
      )}
    </div>
  );
}

// ========================================
// 미구현 서브타입 플레이스홀더
// ========================================

function PlaceholderInput({ inputType }: { inputType: string }) {
  return (
    <div className="flex h-full min-h-8 items-center rounded-md border border-dashed border-muted-foreground/30 px-3">
      <span className="text-[10px] text-muted-foreground">{inputType} (후속 구현 예정)</span>
    </div>
  );
}

// ========================================
// 검색 방식별 문자열 매칭
// ========================================

function matchSearchMode(cellValue: string, term: string, mode: ModalSearchMode): boolean {
  const lower = cellValue.toLowerCase();
  const tLower = term.toLowerCase();
  switch (mode) {
    case "starts-with": return lower.startsWith(tLower);
    case "equals": return lower === tLower;
    case "contains":
    default: return lower.includes(tLower);
  }
}

// ========================================
// 아이콘 색상 생성 (이름 기반 결정적 색상)
// ========================================

const ICON_COLORS = [
  "bg-red-500", "bg-orange-500", "bg-amber-500", "bg-yellow-500",
  "bg-lime-500", "bg-green-500", "bg-emerald-500", "bg-teal-500",
  "bg-cyan-500", "bg-sky-500", "bg-blue-500", "bg-indigo-500",
  "bg-violet-500", "bg-purple-500", "bg-fuchsia-500", "bg-pink-500",
];

function getIconColor(text: string): string {
  let hash = 0;
  for (let i = 0; i < text.length; i++) {
    hash = text.charCodeAt(i) + ((hash << 5) - hash);
  }
  return ICON_COLORS[Math.abs(hash) % ICON_COLORS.length];
}

// ========================================
// 모달 Dialog: 테이블 / 아이콘 뷰 + 필터 탭
// ========================================

interface ModalDialogProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  modalConfig: ModalSelectConfig;
  title: string;
  onSelect: (row: Record<string, unknown>) => void;
}

function ModalDialog({ open, onOpenChange, modalConfig, title, onSelect }: ModalDialogProps) {
  const [searchText, setSearchText] = useState("");
  const [allRows, setAllRows] = useState<Record<string, unknown>[]>([]);
  const [loading, setLoading] = useState(false);
  const [activeFilterTab, setActiveFilterTab] = useState<ModalFilterTab | null>(null);
  const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  const {
    tableName,
    displayColumns,
    searchColumns,
    searchMode = "contains",
    filterTabs,
    columnLabels,
    displayStyle = "table",
    displayField,
    distinct,
  } = modalConfig;

  const colsToShow = displayColumns && displayColumns.length > 0 ? displayColumns : [];
  const hasFilterTabs = filterTabs && filterTabs.length > 0;

  // 데이터 로드
  const fetchData = useCallback(async () => {
    if (!tableName) return;
    setLoading(true);
    try {
      const result = await dataApi.getTableData(tableName, { page: 1, size: 200 });
      let rows = result.data || [];

      if (distinct && displayField) {
        const seen = new Set<string>();
        rows = rows.filter((row) => {
          const val = String(row[displayField] ?? "");
          if (seen.has(val)) return false;
          seen.add(val);
          return true;
        });
      }

      setAllRows(rows);
    } catch {
      setAllRows([]);
    } finally {
      setLoading(false);
    }
  }, [tableName, distinct, displayField]);

  useEffect(() => {
    if (open) {
      setSearchText("");
      setActiveFilterTab(hasFilterTabs ? filterTabs![0] : null);
      fetchData();
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open, fetchData, hasFilterTabs]);

  // 필터링된 행 계산
  const filteredRows = useMemo(() => {
    let items = allRows;

    // 텍스트 검색 필터
    if (searchText.trim()) {
      const cols = searchColumns && searchColumns.length > 0 ? searchColumns : colsToShow;
      items = items.filter((row) =>
        cols.some((col) => {
          const val = row[col];
          return val != null && matchSearchMode(String(val), searchText, searchMode);
        })
      );
    }

    // 필터 탭 (초성/알파벳) 적용
    if (activeFilterTab && displayField) {
      items = items.filter((row) => {
        const val = row[displayField];
        if (val == null) return false;
        const key = getGroupKey(String(val), activeFilterTab);
        return key !== "#";
      });
    }

    return items;
  }, [allRows, searchText, searchColumns, colsToShow, searchMode, activeFilterTab, displayField]);

  // 그룹화 (필터 탭 활성화 시)
  const groupedRows = useMemo(() => {
    if (!activeFilterTab || !displayField) return null;

    const groups = new Map<string, Record<string, unknown>[]>();
    for (const row of filteredRows) {
      const val = row[displayField];
      const key = val != null ? getGroupKey(String(val), activeFilterTab) : "#";
      if (key === "#") continue;
      if (!groups.has(key)) groups.set(key, []);
      groups.get(key)!.push(row);
    }

    // 정렬
    const sorted = [...groups.entries()].sort(([a], [b]) => a.localeCompare(b, "ko"));
    return sorted;
  }, [filteredRows, activeFilterTab, displayField]);

  const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const v = e.target.value;
    setSearchText(v);
    if (debounceRef.current) clearTimeout(debounceRef.current);
    debounceRef.current = setTimeout(() => {}, 300);
  };

  const getColLabel = (colName: string) => columnLabels?.[colName] || colName;

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="max-w-[95vw] sm:max-w-[500px]">
        <DialogHeader className="flex flex-row items-center justify-between">
          <DialogTitle className="text-base sm:text-lg">{title} 선택</DialogTitle>
          {/* 필터 탭 버튼 */}
          {hasFilterTabs && (
            <div className="flex gap-1">
              {filterTabs!.map((tab) => (
                <Button
                  key={tab}
                  variant={activeFilterTab === tab ? "default" : "outline"}
                  size="sm"
                  className="h-7 px-3 text-[11px]"
                  onClick={() => setActiveFilterTab(activeFilterTab === tab ? null : tab)}
                >
                  {MODAL_FILTER_TAB_LABELS[tab]}
                </Button>
              ))}
            </div>
          )}
        </DialogHeader>

        {/* 검색 입력 */}
        <div className="relative">
          <Search className="absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
          <Input
            value={searchText}
            onChange={handleSearchChange}
            placeholder="검색..."
            className="h-9 pl-8 text-sm"
            autoFocus
          />
          {searchText && (
            <button
              type="button"
              onClick={() => setSearchText("")}
              className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
            >
              <X className="h-4 w-4" />
            </button>
          )}
        </div>

        {/* 결과 영역 */}
        <div className="max-h-[50vh] overflow-auto rounded-md border">
          {loading ? (
            <div className="flex items-center justify-center py-8">
              <Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
            </div>
          ) : filteredRows.length === 0 ? (
            <div className="py-8 text-center text-sm text-muted-foreground">
              {searchText ? "검색 결과가 없습니다" : "데이터가 없습니다"}
            </div>
          ) : displayStyle === "icon" ? (
            <IconView
              rows={filteredRows}
              groupedRows={groupedRows}
              displayField={displayField || ""}
              onSelect={onSelect}
            />
          ) : (
            <TableView
              rows={filteredRows}
              groupedRows={groupedRows}
              colsToShow={colsToShow}
              displayField={displayField || ""}
              getColLabel={getColLabel}
              activeFilterTab={activeFilterTab}
              onSelect={onSelect}
            />
          )}
        </div>

        <p className="text-[10px] text-muted-foreground">
          {filteredRows.length}건 표시 / {displayStyle === "icon" ? "아이콘" : "행"}을 클릭하면 선택됩니다
        </p>
      </DialogContent>
    </Dialog>
  );
}

// ========================================
// 테이블 뷰
// ========================================

function TableView({
  rows,
  groupedRows,
  colsToShow,
  displayField,
  getColLabel,
  activeFilterTab,
  onSelect,
}: {
  rows: Record<string, unknown>[];
  groupedRows: [string, Record<string, unknown>[]][] | null;
  colsToShow: string[];
  displayField: string;
  getColLabel: (col: string) => string;
  activeFilterTab: ModalFilterTab | null;
  onSelect: (row: Record<string, unknown>) => void;
}) {
  const renderRow = (row: Record<string, unknown>, i: number) => (
    <tr key={i} className="cursor-pointer border-t transition-colors hover:bg-accent" onClick={() => onSelect(row)}>
      {colsToShow.length > 0
        ? colsToShow.map((col) => (
            <td key={col} className="px-3 py-2 text-xs">{String(row[col] ?? "")}</td>
          ))
        : Object.entries(row).slice(0, 3).map(([k, v]) => (
            <td key={k} className="px-3 py-2 text-xs">{String(v ?? "")}</td>
          ))}
    </tr>
  );

  if (groupedRows && activeFilterTab) {
    return (
      <div>
        {colsToShow.length > 0 && (
          <div className="sticky top-0 z-10 flex bg-muted">
            {colsToShow.map((col) => (
              <div key={col} className="flex-1 px-3 py-2 text-xs font-medium text-muted-foreground">
                {getColLabel(col)}
              </div>
            ))}
          </div>
        )}
        {groupedRows.map(([groupKey, groupRows]) => (
          <div key={groupKey}>
            <div className="sticky top-8 z-5 flex items-center gap-2 bg-background px-3 py-1.5">
              <span className="text-sm font-semibold text-primary">{groupKey}</span>
              <div className="h-px flex-1 bg-border" />
            </div>
            <table className="w-full text-sm">
              <tbody>
                {groupRows.map((row, i) => renderRow(row, i))}
              </tbody>
            </table>
          </div>
        ))}
      </div>
    );
  }

  return (
    <table className="w-full text-sm">
      {colsToShow.length > 0 && (
        <thead className="sticky top-0 bg-muted">
          <tr>
            {colsToShow.map((col) => (
              <th key={col} className="px-3 py-2 text-left text-xs font-medium text-muted-foreground">
                {getColLabel(col)}
              </th>
            ))}
          </tr>
        </thead>
      )}
      <tbody>
        {rows.map((row, i) => renderRow(row, i))}
      </tbody>
    </table>
  );
}

// ========================================
// 아이콘 뷰
// ========================================

function IconView({
  rows,
  groupedRows,
  displayField,
  onSelect,
}: {
  rows: Record<string, unknown>[];
  groupedRows: [string, Record<string, unknown>[]][] | null;
  displayField: string;
  onSelect: (row: Record<string, unknown>) => void;
}) {
  const renderIconCard = (row: Record<string, unknown>, i: number) => {
    const text = displayField ? String(row[displayField] ?? "") : "";
    const firstChar = text.charAt(0) || "?";
    const color = getIconColor(text);

    return (
      <div
        key={i}
        className="flex w-20 cursor-pointer flex-col items-center gap-1.5 rounded-lg p-2 transition-colors hover:bg-accent"
        onClick={() => onSelect(row)}
      >
        <div className={cn("flex h-14 w-14 items-center justify-center rounded-xl text-xl font-bold text-white", color)}>
          {firstChar}
        </div>
        <span className="w-full truncate text-center text-[11px]">{text}</span>
      </div>
    );
  };

  if (groupedRows) {
    return (
      <div className="p-3">
        {groupedRows.map(([groupKey, groupRows]) => (
          <div key={groupKey} className="mb-4">
            <div className="mb-2 flex items-center gap-2">
              <span className="text-sm font-semibold text-primary">{groupKey}</span>
              <div className="h-px flex-1 bg-border" />
            </div>
            <div className="flex flex-wrap gap-2">
              {groupRows.map((row, i) => renderIconCard(row, i))}
            </div>
          </div>
        ))}
      </div>
    );
  }

  return (
    <div className="flex flex-wrap gap-2 p-3">
      {rows.map((row, i) => renderIconCard(row, i))}
    </div>
  );
}
