"use client";

/**
 * Input 컴포넌트의 select 계열 picker 묶음.
 *
 * 단계적 이식 (옛 선택 컴포넌트 → 핵심만 InvField canonical 모델로):
 *   - SingleSelectPicker  → InvField choice.single.list (드롭다운, 단일)
 *   - MultiSelectPicker   → InvField choice.multi.list  (TODO Phase B.2)
 *   - TagPicker           → InvField choice.multi.tags  (TODO Phase B.3)
 *
 * 외형 통일: outer wrapper transparent + container border 가 box 역할.
 * 모든 picker 는 className prop 으로 border/bg override 가능.
 */

import React, { useState, useRef, useEffect } from "react";
import { ChevronDown, Search, Check, ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";

export interface SelectOption {
  value: string;
  label: string;
  disabled?: boolean;
}

interface SingleSelectPickerProps {
  value?: string;
  onChange?: (value: string) => void;
  options: SelectOption[];
  placeholder?: string;
  searchable?: boolean;
  allowClear?: boolean;
  disabled?: boolean;
  readonly?: boolean;
  className?: string;
}

export const SingleSelectPicker = React.forwardRef<HTMLDivElement, SingleSelectPickerProps>(
  ({ value, onChange, options, placeholder = "선택", searchable, allowClear, disabled, readonly, className }, ref) => {
    const [open, setOpen] = useState(false);
    const [searchQuery, setSearchQuery] = useState("");
    const wrapperRef = useRef<HTMLDivElement>(null);

    const selected = options.find((o) => o.value === value);

    useEffect(() => {
      if (!open) return;
      const handler = (e: MouseEvent) => {
        if (!wrapperRef.current?.contains(e.target as Node)) {
          setOpen(false);
          setSearchQuery("");
        }
      };
      const escHandler = (e: KeyboardEvent) => {
        if (e.key === "Escape") {
          setOpen(false);
          setSearchQuery("");
        }
      };
      document.addEventListener("mousedown", handler);
      document.addEventListener("keydown", escHandler);
      return () => {
        document.removeEventListener("mousedown", handler);
        document.removeEventListener("keydown", escHandler);
      };
    }, [open]);

    const filteredOptions =
      searchable && searchQuery
        ? options.filter((o) => o.label.toLowerCase().includes(searchQuery.toLowerCase()))
        : options;

    const handleSelect = (v: string) => {
      onChange?.(v);
      setOpen(false);
      setSearchQuery("");
    };

    const handleClear = (e: React.MouseEvent) => {
      e.stopPropagation();
      onChange?.("");
    };

    return (
      <div
        ref={wrapperRef}
        className={cn("relative h-full w-full", (disabled || readonly) && "cursor-not-allowed", className)}
      >
        <div
          ref={ref}
          className={cn(
            "flex h-full w-full cursor-pointer items-center gap-1 px-2 text-sm",
            (disabled || readonly) && "cursor-not-allowed",
          )}
          onClick={() => {
            if (!disabled && !readonly) setOpen((v) => !v);
          }}
        >
          <span className={cn("flex-1 truncate", !selected && "text-muted-foreground")}>
            {selected?.label || placeholder}
          </span>
          {allowClear && selected && !disabled && !readonly && (
            <button
              type="button"
              className="text-muted-foreground hover:text-foreground text-xs"
              onClick={handleClear}
            >
              ✕
            </button>
          )}
          <ChevronDown className="text-muted-foreground h-4 w-4 shrink-0" />
        </div>
        {open && (
          <div className="border-border bg-popover absolute top-full right-0 left-0 z-50 mt-1 max-h-64 overflow-auto rounded-md border shadow-md">
            {searchable && (
              <div className="border-border bg-popover sticky top-0 border-b p-2">
                <div className="relative">
                  <Search className="text-muted-foreground absolute top-1/2 left-2 h-3.5 w-3.5 -translate-y-1/2" />
                  <input
                    type="text"
                    value={searchQuery}
                    onChange={(e) => setSearchQuery(e.target.value)}
                    placeholder="검색"
                    className="border-border bg-background h-7 w-full rounded border pr-2 pl-7 text-xs outline-none"
                    autoFocus
                  />
                </div>
              </div>
            )}
            {filteredOptions.length === 0 ? (
              <div className="text-muted-foreground px-3 py-2 text-xs">옵션 없음</div>
            ) : (
              filteredOptions.map((opt) => (
                <button
                  key={opt.value}
                  type="button"
                  disabled={opt.disabled}
                  className={cn(
                    "hover:bg-accent block w-full px-3 py-1.5 text-left text-sm",
                    value === opt.value && "bg-accent font-medium",
                    opt.disabled && "cursor-not-allowed opacity-50",
                  )}
                  onClick={() => !opt.disabled && handleSelect(opt.value)}
                >
                  {opt.label}
                </button>
              ))
            )}
          </div>
        )}
      </div>
    );
  },
);
SingleSelectPicker.displayName = "SingleSelectPicker";

// ─────────────────────────────────────────────────────────
// MultiSelectPicker — InvField choice.multi.list (다중 선택, 라벨 join 트리거)
// ─────────────────────────────────────────────────────────

interface MultiSelectPickerProps {
  value?: string[];
  onChange?: (values: string[]) => void;
  options: SelectOption[];
  placeholder?: string;
  searchable?: boolean;
  allowClear?: boolean;
  maxSelect?: number;
  disabled?: boolean;
  readonly?: boolean;
  className?: string;
}

export const MultiSelectPicker = React.forwardRef<HTMLDivElement, MultiSelectPickerProps>(
  (
    { value, onChange, options, placeholder = "선택", searchable, allowClear, maxSelect, disabled, readonly, className },
    ref,
  ) => {
    const [open, setOpen] = useState(false);
    const [searchQuery, setSearchQuery] = useState("");
    const wrapperRef = useRef<HTMLDivElement>(null);

    // value 정규화 — undefined/string 방어
    const selectedValues: string[] = Array.isArray(value) ? value : value ? [String(value)] : [];
    const selectedOptions = options.filter((o) => selectedValues.includes(o.value));
    const displayLabel = selectedOptions.length === 0 ? "" : selectedOptions.map((o) => o.label).join(", ");

    useEffect(() => {
      if (!open) return;
      const handler = (e: MouseEvent) => {
        if (!wrapperRef.current?.contains(e.target as Node)) {
          setOpen(false);
          setSearchQuery("");
        }
      };
      const escHandler = (e: KeyboardEvent) => {
        if (e.key === "Escape") {
          setOpen(false);
          setSearchQuery("");
        }
      };
      document.addEventListener("mousedown", handler);
      document.addEventListener("keydown", escHandler);
      return () => {
        document.removeEventListener("mousedown", handler);
        document.removeEventListener("keydown", escHandler);
      };
    }, [open]);

    const filteredOptions =
      searchable && searchQuery
        ? options.filter((o) => o.label.toLowerCase().includes(searchQuery.toLowerCase()))
        : options;

    const handleToggle = (v: string) => {
      if (disabled || readonly) return;
      if (selectedValues.includes(v)) {
        onChange?.(selectedValues.filter((x) => x !== v));
      } else {
        if (maxSelect && selectedValues.length >= maxSelect) return; // 차단
        onChange?.([...selectedValues, v]);
      }
    };

    const handleClearAll = (e: React.MouseEvent) => {
      e.stopPropagation();
      onChange?.([]);
    };

    return (
      <div
        ref={wrapperRef}
        className={cn("relative h-full w-full", (disabled || readonly) && "cursor-not-allowed", className)}
      >
        <div
          ref={ref}
          className={cn(
            "flex h-full w-full cursor-pointer items-center gap-1 px-2 text-sm",
            (disabled || readonly) && "cursor-not-allowed",
          )}
          onClick={() => {
            if (!disabled && !readonly) setOpen((v) => !v);
          }}
        >
          <span className={cn("flex-1 truncate", selectedOptions.length === 0 && "text-muted-foreground")}>
            {displayLabel || placeholder}
          </span>
          {allowClear && selectedOptions.length > 0 && !disabled && !readonly && (
            <button
              type="button"
              className="text-muted-foreground hover:text-foreground text-xs"
              onClick={handleClearAll}
            >
              ✕
            </button>
          )}
          <ChevronDown className="text-muted-foreground h-4 w-4 shrink-0" />
        </div>
        {open && (
          <div className="border-border bg-popover absolute top-full right-0 left-0 z-50 mt-1 max-h-64 overflow-auto rounded-md border shadow-md">
            {searchable && (
              <div className="border-border bg-popover sticky top-0 border-b p-2">
                <div className="relative">
                  <Search className="text-muted-foreground absolute top-1/2 left-2 h-3.5 w-3.5 -translate-y-1/2" />
                  <input
                    type="text"
                    value={searchQuery}
                    onChange={(e) => setSearchQuery(e.target.value)}
                    placeholder="검색"
                    className="border-border bg-background h-7 w-full rounded border pr-2 pl-7 text-xs outline-none"
                    autoFocus
                  />
                </div>
              </div>
            )}
            {maxSelect && (
              <div className="text-muted-foreground border-border border-b px-3 py-1 text-[10px]">
                {selectedValues.length} / {maxSelect}
              </div>
            )}
            {filteredOptions.length === 0 ? (
              <div className="text-muted-foreground px-3 py-2 text-xs">옵션 없음</div>
            ) : (
              filteredOptions.map((opt) => {
                const checked = selectedValues.includes(opt.value);
                const blocked = !checked && !!maxSelect && selectedValues.length >= maxSelect;
                return (
                  <button
                    key={opt.value}
                    type="button"
                    disabled={opt.disabled || blocked}
                    className={cn(
                      "hover:bg-accent flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm",
                      checked && "bg-accent/60",
                      (opt.disabled || blocked) && "cursor-not-allowed opacity-50 hover:bg-transparent",
                    )}
                    onClick={() => handleToggle(opt.value)}
                  >
                    <span
                      className={cn(
                        "border-border flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-sm border",
                        checked && "border-primary bg-primary text-primary-foreground",
                      )}
                    >
                      {checked && <Check className="h-3 w-3" />}
                    </span>
                    <span className="flex-1 truncate">{opt.label}</span>
                  </button>
                );
              })
            )}
          </div>
        )}
      </div>
    );
  },
);
MultiSelectPicker.displayName = "MultiSelectPicker";

// ─────────────────────────────────────────────────────────
// RadioPicker — single 의 displayMode=radio (옵션을 라디오 button list)
// ─────────────────────────────────────────────────────────

interface RadioPickerProps {
  value?: string;
  onChange?: (value: string) => void;
  options: SelectOption[];
  disabled?: boolean;
  readonly?: boolean;
  className?: string;
}

export const RadioPicker = React.forwardRef<HTMLDivElement, RadioPickerProps>(
  ({ value, onChange, options, disabled, readonly, className }, ref) => {
    return (
      <div
        ref={ref}
        className={cn(
          "flex h-full w-full flex-wrap content-start items-start gap-x-3 gap-y-1 px-2 py-1 text-sm",
          (disabled || readonly) && "cursor-not-allowed",
          className,
        )}
      >
        {options.length === 0 ? (
          <span className="text-muted-foreground text-xs">옵션 없음</span>
        ) : (
          options.map((opt) => {
            const checked = value === opt.value;
            const optDisabled = disabled || readonly || opt.disabled;
            return (
              <label
                key={opt.value}
                className={cn(
                  "inline-flex cursor-pointer items-center gap-1.5",
                  optDisabled && "cursor-not-allowed",
                )}
              >
                <input
                  type="radio"
                  checked={checked}
                  disabled={optDisabled}
                  onChange={() => !optDisabled && onChange?.(opt.value)}
                  className="h-3.5 w-3.5 shrink-0"
                />
                <span className="select-none">{opt.label}</span>
              </label>
            );
          })
        )}
      </div>
    );
  },
);
RadioPicker.displayName = "RadioPicker";

// ─────────────────────────────────────────────────────────
// CheckboxListPicker — multi 의 displayMode=checkbox (옵션을 체크박스 list)
// ─────────────────────────────────────────────────────────

interface CheckboxListPickerProps {
  value?: string[];
  onChange?: (values: string[]) => void;
  options: SelectOption[];
  maxSelect?: number;
  disabled?: boolean;
  readonly?: boolean;
  className?: string;
}

export const CheckboxListPicker = React.forwardRef<HTMLDivElement, CheckboxListPickerProps>(
  ({ value, onChange, options, maxSelect, disabled, readonly, className }, ref) => {
    const selectedValues: string[] = Array.isArray(value) ? value : value ? [String(value)] : [];

    const handleToggle = (v: string) => {
      if (disabled || readonly) return;
      if (selectedValues.includes(v)) {
        onChange?.(selectedValues.filter((x) => x !== v));
      } else {
        if (maxSelect && selectedValues.length >= maxSelect) return;
        onChange?.([...selectedValues, v]);
      }
    };

    return (
      <div
        ref={ref}
        className={cn(
          "flex h-full w-full flex-wrap content-start items-start gap-x-3 gap-y-1 px-2 py-1 text-sm",
          (disabled || readonly) && "cursor-not-allowed",
          className,
        )}
      >
        {options.length === 0 ? (
          <span className="text-muted-foreground text-xs">옵션 없음</span>
        ) : (
          options.map((opt) => {
            const checked = selectedValues.includes(opt.value);
            const blocked = !checked && !!maxSelect && selectedValues.length >= maxSelect;
            const optDisabled = disabled || readonly || opt.disabled || blocked;
            return (
              <label
                key={opt.value}
                className={cn(
                  "inline-flex cursor-pointer items-center gap-1.5",
                  optDisabled && "cursor-not-allowed",
                )}
              >
                <input
                  type="checkbox"
                  checked={checked}
                  disabled={optDisabled}
                  onChange={() => !optDisabled && handleToggle(opt.value)}
                  className="h-3.5 w-3.5 shrink-0"
                />
                <span className="select-none">{opt.label}</span>
              </label>
            );
          })
        )}
      </div>
    );
  },
);
CheckboxListPicker.displayName = "CheckboxListPicker";

// ─────────────────────────────────────────────────────────
// TogglePicker — single.boolean 의 mode=toggle (Y/N 토글 스위치)
// value: boolean | "Y"/"N" | "true"/"false" | 1/0 모두 받음
// ─────────────────────────────────────────────────────────

interface TogglePickerProps {
  value?: unknown;
  onChange?: (value: boolean) => void;
  trueLabel?: string;
  falseLabel?: string;
  disabled?: boolean;
  readonly?: boolean;
  className?: string;
}

function isTruthy(v: unknown): boolean {
  if (typeof v === "boolean") return v;
  if (typeof v === "number") return v !== 0;
  if (typeof v === "string") {
    const s = v.toLowerCase();
    return s === "true" || s === "y" || s === "yes" || s === "1";
  }
  return false;
}

export const TogglePicker = React.forwardRef<HTMLDivElement, TogglePickerProps>(
  ({ value, onChange, trueLabel = "예", falseLabel = "아니오", disabled, readonly, className }, ref) => {
    const checked = isTruthy(value);
    const lockClick = !!disabled || !!readonly;

    return (
      <div
        ref={ref}
        className={cn(
          "flex h-full w-full items-center gap-2 px-2 text-sm",
          lockClick && "cursor-not-allowed",
          className,
        )}
      >
        <button
          type="button"
          role="switch"
          aria-checked={checked}
          disabled={lockClick}
          onClick={() => !lockClick && onChange?.(!checked)}
          className={cn(
            "relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors outline-none",
            checked ? "bg-primary" : "bg-muted",
            lockClick ? "cursor-not-allowed" : "cursor-pointer",
          )}
        >
          <span
            className={cn(
              "bg-background inline-block h-4 w-4 transform rounded-full shadow transition-transform",
              checked ? "translate-x-[18px]" : "translate-x-[2px]",
            )}
          />
        </button>
        <span className="text-foreground select-none">{checked ? trueLabel : falseLabel}</span>
      </div>
    );
  },
);
TogglePicker.displayName = "TogglePicker";

// ─────────────────────────────────────────────────────────
// TagPicker — multi.tags (chip 입력. Enter / 구분자 로 추가, Backspace / ✕ 로 제거)
// ─────────────────────────────────────────────────────────

interface TagPickerProps {
  value?: string[];
  onChange?: (values: string[]) => void;
  placeholder?: string;
  maxSelect?: number;
  /** 자동 분리 키. ',' 또는 ' ' 등 */
  separator?: string;
  disabled?: boolean;
  readonly?: boolean;
  className?: string;
}

export const TagPicker = React.forwardRef<HTMLDivElement, TagPickerProps>(
  (
    {
      value,
      onChange,
      placeholder = "태그 입력 후 Enter",
      maxSelect,
      separator,
      disabled,
      readonly,
      className,
    },
    ref,
  ) => {
    const [input, setInput] = useState("");
    const tags: string[] = Array.isArray(value) ? value : [];
    const lockEdit = !!disabled || !!readonly;

    const commitTags = (raw: string) => {
      const parts = separator ? raw.split(separator) : [raw];
      const next = [...tags];
      for (const part of parts) {
        const trimmed = part.trim();
        if (!trimmed || next.includes(trimmed)) continue;
        if (maxSelect && next.length >= maxSelect) break;
        next.push(trimmed);
      }
      if (next.length !== tags.length) onChange?.(next);
    };

    const removeTag = (idx: number) => {
      onChange?.(tags.filter((_, i) => i !== idx));
    };

    const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
      if (e.key === "Enter" || (separator && e.key === separator)) {
        e.preventDefault();
        if (input.trim()) {
          commitTags(input);
          setInput("");
        }
      } else if (e.key === "Backspace" && input === "" && tags.length > 0) {
        removeTag(tags.length - 1);
      }
    };

    return (
      <div
        ref={ref}
        className={cn(
          "flex h-full w-full flex-wrap items-center gap-1 px-2 py-1 text-sm",
          "content-start",
          lockEdit && "cursor-not-allowed",
          className,
        )}
        style={{ alignItems: "flex-start" }}
      >
        {tags.map((tag, i) => (
          <span
            key={i}
            className="bg-accent text-accent-foreground inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs"
          >
            <span>{tag}</span>
            {!lockEdit && (
              <button
                type="button"
                onClick={() => removeTag(i)}
                className="text-muted-foreground hover:text-foreground leading-none"
                aria-label={`${tag} 제거`}
              >
                ✕
              </button>
            )}
          </span>
        ))}
        {!lockEdit && (!maxSelect || tags.length < maxSelect) && (
          <input
            type="text"
            value={input}
            onChange={(e) => {
              const next = e.target.value;
              if (separator && next.includes(separator)) {
                commitTags(next);
                setInput("");
                return;
              }
              setInput(next);
            }}
            onKeyDown={handleKeyDown}
            onBlur={() => {
              if (input.trim()) {
                commitTags(input);
                setInput("");
              }
            }}
            placeholder={tags.length === 0 ? placeholder : ""}
            className="min-w-[80px] flex-1 bg-transparent text-sm leading-6 outline-none"
            disabled={lockEdit}
          />
        )}
      </div>
    );
  },
);
TagPicker.displayName = "TagPicker";

// ─────────────────────────────────────────────────────────
// SwapPicker — multi 의 mode=swap (양쪽 list 간 이동 UI)
//
//   왼쪽 (available)            가운데        오른쪽 (selected)
//   ┌──────────────┐   ┌─────┐   ┌──────────────┐
//   │ opt A        │   │  →  │   │ opt B  ✓     │
//   │ opt C        │   │  ←  │   │ opt D        │
//   │ opt E        │   └─────┘   └──────────────┘
//   └──────────────┘
//
// 항목 클릭 → 같은 패널 내 highlight (다중 선택). 이동 버튼은 highlight 된 항목들을
// 반대편으로 이동. 이동 후 highlight 자동 해제.
//
// 순서 정책:
//   - selected 순서가 value 배열 순서. left→right 이동 시 끝에 append.
//   - right→left 이동 시 항목 제거 (available 표시 순서는 options 배열 순서를 유지).
//   - options 배열에 없는 value 가 들어와도 selected 에 표시 (label = value fallback).
//
// disabled / readonly: 이동 버튼 + 항목 클릭 모두 비활성. maxSelect: 좌→우 이동
// 가능 한도 초과분은 잘림.
// ─────────────────────────────────────────────────────────

interface SwapPickerProps {
  value?: string[];
  onChange?: (values: string[]) => void;
  options: SelectOption[];
  maxSelect?: number;
  disabled?: boolean;
  readonly?: boolean;
  className?: string;
  /** 왼쪽 패널 헤더 라벨. 기본 "선택 가능" */
  availableLabel?: string;
  /** 오른쪽 패널 헤더 라벨. 기본 "선택됨" */
  selectedLabel?: string;
}

export const SwapPicker = React.forwardRef<HTMLDivElement, SwapPickerProps>(
  (
    {
      value,
      onChange,
      options,
      maxSelect,
      disabled,
      readonly,
      className,
      availableLabel = "선택 가능",
      selectedLabel = "선택됨",
    },
    ref,
  ) => {
    const selectedValues: string[] = Array.isArray(value) ? value : value ? [String(value)] : [];
    const selectedSet = new Set(selectedValues);

    // option lookup — options 에 없는 value 도 label = value 로 fallback.
    const optionLookup = new Map<string, SelectOption>(options.map((o) => [o.value, o]));
    const getLabel = (v: string): string => optionLookup.get(v)?.label ?? v;

    // available 패널 (options 순서 보존, selected 제외)
    const available = options.filter((o) => !selectedSet.has(o.value));
    // selected 패널 (value 배열 순서 그대로)
    const selected = selectedValues.map((v) => ({
      value: v,
      label: getLabel(v),
      disabled: optionLookup.get(v)?.disabled,
    }));

    // 패널 내 highlight 상태
    const [highlightLeft, setHighlightLeft] = useState<Set<string>>(new Set());
    const [highlightRight, setHighlightRight] = useState<Set<string>>(new Set());

    const lockEdit = !!(disabled || readonly);

    const toggleHighlight = (side: "left" | "right", v: string) => {
      if (lockEdit) return;
      const setter = side === "left" ? setHighlightLeft : setHighlightRight;
      setter((prev) => {
        const next = new Set(prev);
        if (next.has(v)) next.delete(v);
        else next.add(v);
        return next;
      });
    };

    const moveToSelected = () => {
      if (lockEdit) return;
      if (highlightLeft.size === 0) return;
      const additions = available
        .map((o) => o.value)
        .filter((v) => highlightLeft.has(v) && !optionLookup.get(v)?.disabled);
      if (additions.length === 0) {
        setHighlightLeft(new Set());
        return;
      }
      let next = [...selectedValues, ...additions];
      if (maxSelect && next.length > maxSelect) {
        next = next.slice(0, maxSelect);
      }
      onChange?.(next);
      setHighlightLeft(new Set());
    };

    const moveToAvailable = () => {
      if (lockEdit) return;
      if (highlightRight.size === 0) return;
      const next = selectedValues.filter((v) => !highlightRight.has(v));
      onChange?.(next);
      setHighlightRight(new Set());
    };

    const canMoveRight = !lockEdit && highlightLeft.size > 0;
    const canMoveLeft = !lockEdit && highlightRight.size > 0;
    const atLimit = !!maxSelect && selectedValues.length >= maxSelect;

    const renderPanel = (
      side: "left" | "right",
      items: Array<{ value: string; label: string; disabled?: boolean }>,
      headerLabel: string,
      highlight: Set<string>,
    ) => (
      <div className="flex h-full min-w-0 flex-1 flex-col rounded-sm border border-border bg-background">
        <div className="border-b border-border px-2 py-1 text-[11px] font-medium text-muted-foreground">
          {headerLabel}
          <span className="ml-1 text-[10px] text-muted-foreground/70">({items.length})</span>
        </div>
        <ul className="flex-1 overflow-auto py-1 text-sm">
          {items.length === 0 ? (
            <li className="px-2 py-1 text-xs text-muted-foreground">옵션 없음</li>
          ) : (
            items.map((it) => {
              const optDisabled = lockEdit || it.disabled;
              const isHi = highlight.has(it.value);
              return (
                <li
                  key={it.value}
                  onClick={() => !optDisabled && toggleHighlight(side, it.value)}
                  className={cn(
                    "cursor-pointer select-none truncate px-2 py-0.5",
                    isHi && "bg-primary/15 text-primary",
                    !isHi && "hover:bg-muted/50",
                    optDisabled && "cursor-not-allowed opacity-60",
                  )}
                  title={it.label}
                >
                  {it.label}
                </li>
              );
            })
          )}
        </ul>
      </div>
    );

    return (
      <div
        ref={ref}
        className={cn(
          "flex h-full w-full items-stretch gap-1.5 px-1 py-1",
          lockEdit && "cursor-not-allowed",
          className,
        )}
      >
        {renderPanel("left", available, availableLabel, highlightLeft)}

        <div className="flex flex-col items-center justify-center gap-1">
          <button
            type="button"
            onClick={moveToSelected}
            disabled={!canMoveRight || atLimit}
            title={atLimit ? `최대 ${maxSelect}개까지` : "선택"}
            className={cn(
              "flex h-6 w-6 items-center justify-center rounded-sm border border-border bg-background",
              (!canMoveRight || atLimit) && "cursor-not-allowed opacity-40",
              canMoveRight && !atLimit && "hover:bg-muted",
            )}
          >
            <ChevronRight className="h-3.5 w-3.5" />
          </button>
          <button
            type="button"
            onClick={moveToAvailable}
            disabled={!canMoveLeft}
            title="선택 해제"
            className={cn(
              "flex h-6 w-6 items-center justify-center rounded-sm border border-border bg-background",
              !canMoveLeft && "cursor-not-allowed opacity-40",
              canMoveLeft && "hover:bg-muted",
            )}
          >
            <ChevronLeft className="h-3.5 w-3.5" />
          </button>
        </div>

        {renderPanel("right", selected, selectedLabel, highlightRight)}
      </div>
    );
  },
);
SwapPicker.displayName = "SwapPicker";
