"use client";

import { useState, useCallback, useEffect, useRef, useMemo } from "react";
import { cn } from "@/lib/utils";
import { Input } from "@/components/ui/input";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Loader2 } from "lucide-react";
import { usePopEvent } from "@/hooks/pop";
import { dataApi } from "@/lib/api/data";
import type {
  PopFieldConfig,
  PopFieldItem,
  PopFieldSection,
  FieldSectionStyle,
  PopFieldReadSource,
  PopFieldAutoGenMapping,
  SelectLinkedFilter,
} from "./types";
import type { CollectDataRequest, CollectedDataResponse } from "../types";
import { DEFAULT_FIELD_CONFIG, DEFAULT_SECTION_APPEARANCES } from "./types";

// ========================================
// Props
// ========================================

interface PopFieldComponentProps {
  config?: PopFieldConfig;
  screenId?: string;
  componentId?: string;
}

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

export function PopFieldComponent({
  config,
  screenId,
  componentId,
}: PopFieldComponentProps) {
  const cfg: PopFieldConfig = {
    ...DEFAULT_FIELD_CONFIG,
    ...config,
    sections: config?.sections?.length ? config.sections : DEFAULT_FIELD_CONFIG.sections,
  };
  const { publish, subscribe } = usePopEvent(screenId || "default");
  const containerRef = useRef<HTMLDivElement>(null);

  const [allValues, setAllValues] = useState<Record<string, unknown>>({});
  const [hiddenValues, setHiddenValues] = useState<Record<string, unknown>>({});
  const [errors, setErrors] = useState<Record<string, string>>({});
  const [containerWidth, setContainerWidth] = useState(0);

  const hiddenMappings = cfg.saveConfig?.hiddenMappings ?? [];
  const autoGenMappings = cfg.saveConfig?.autoGenMappings ?? [];
  const visibleAutoGens = autoGenMappings.filter((m) => m.showInForm);

  const fieldIdToName = useMemo(() => {
    const map: Record<string, string> = {};
    for (const section of cfg.sections) {
      for (const f of section.fields ?? []) {
        map[f.id] = f.fieldName || f.id;
      }
    }
    return map;
  }, [cfg.sections]);

  // ResizeObserver로 컨테이너 너비 감시
  useEffect(() => {
    if (typeof window === "undefined" || !containerRef.current) return;
    const ro = new ResizeObserver(([entry]) => {
      setContainerWidth(entry.contentRect.width);
    });
    ro.observe(containerRef.current);
    return () => ro.disconnect();
  }, []);

  // readSource 기반 DB 조회 + JSON 파싱
  const fetchReadSourceData = useCallback(
    async (pkValue: unknown, readSource: PopFieldReadSource) => {
      if (!readSource.tableName || !readSource.pkColumn || !pkValue) return;
      try {
        const res = await dataApi.getTableData(readSource.tableName, {
          page: 1,
          size: 1,
          filters: { [readSource.pkColumn]: String(pkValue) },
        });
        if (!Array.isArray(res.data) || res.data.length === 0) return;
        const row = res.data[0] as Record<string, unknown>;

        const extracted: Record<string, unknown> = {};
        const lookupQueue: { fieldId: string; codeValue: string; table: string; codeCol: string; displayCol: string }[] = [];
        for (const mapping of readSource.fieldMappings || []) {
          if (mapping.valueSource === "json_extract" && mapping.columnName && mapping.jsonKey) {
            const raw = row[mapping.columnName];
            let parsed: Record<string, unknown> = {};
            if (typeof raw === "string") {
              try { parsed = JSON.parse(raw); } catch { /* ignore */ }
            } else if (typeof raw === "object" && raw !== null) {
              parsed = raw as Record<string, unknown>;
            }
            extracted[mapping.fieldId] = parsed[mapping.jsonKey] ?? "";
          } else if (mapping.valueSource === "json_extract_lookup" && mapping.columnName && mapping.jsonKey && mapping.lookupTable && mapping.lookupCodeColumn && mapping.lookupDisplayColumn) {
            const raw = row[mapping.columnName];
            let parsed: Record<string, unknown> = {};
            if (typeof raw === "string") {
              try { parsed = JSON.parse(raw); } catch { /* ignore */ }
            } else if (typeof raw === "object" && raw !== null) {
              parsed = raw as Record<string, unknown>;
            }
            const codeValue = String(parsed[mapping.jsonKey] ?? "");
            if (codeValue) {
              lookupQueue.push({ fieldId: mapping.fieldId, codeValue, table: mapping.lookupTable, codeCol: mapping.lookupCodeColumn, displayCol: mapping.lookupDisplayColumn });
            } else {
              extracted[mapping.fieldId] = "";
            }
          } else if (mapping.valueSource === "db_column" && mapping.columnName) {
            extracted[mapping.fieldId] = row[mapping.columnName] ?? "";
          }
        }

        // json_extract_lookup: 코드 값으로 참조 테이블 조회하여 표시명 획득
        if (lookupQueue.length > 0) {
          const lookupResults = await Promise.allSettled(
            lookupQueue.map(async (lq) => {
              const lookupRes = await dataApi.getTableData(lq.table, {
                page: 1,
                size: 1,
                filters: { [lq.codeCol]: lq.codeValue },
              });
              if (Array.isArray(lookupRes.data) && lookupRes.data.length > 0) {
                const lookupRow = lookupRes.data[0] as Record<string, unknown>;
                return { fieldId: lq.fieldId, value: lookupRow[lq.displayCol] ?? lq.codeValue };
              }
              return { fieldId: lq.fieldId, value: lq.codeValue };
            }),
          );
          for (const result of lookupResults) {
            if (result.status === "fulfilled") {
              extracted[result.value.fieldId] = result.value.value;
            }
          }
        }

        const allFieldsInConfig = cfg.sections.flatMap((s) => s.fields || []);
        const valuesUpdate: Record<string, unknown> = {};
        for (const [fieldId, val] of Object.entries(extracted)) {
          const f = allFieldsInConfig.find((fi) => fi.id === fieldId);
          const key = f?.fieldName || f?.id || fieldId;
          valuesUpdate[key] = val;
        }
        if (Object.keys(valuesUpdate).length > 0) {
          setAllValues((prev) => ({ ...prev, ...valuesUpdate }));
        }
      } catch {
        // 조회 실패 시 무시
      }
    },
    [cfg.sections]
  );

  // set_value 이벤트 수신 (useConnectionResolver의 enrichedPayload도 처리)
  useEffect(() => {
    if (!componentId) return;
    const unsub = subscribe(
      `__comp_input__${componentId}__set_value`,
      (payload: unknown) => {
        const raw = payload as Record<string, unknown> | undefined;
        if (!raw) return;

        // useConnectionResolver가 감싼 enrichedPayload인지 확인
        const isConnectionPayload = raw._connectionId !== undefined;
        const actual = isConnectionPayload
          ? (raw.value as Record<string, unknown> | undefined)
          : raw;
        if (!actual) return;

        const data = actual as {
          fieldName?: string;
          value?: unknown;
          values?: Record<string, unknown>;
          pkValue?: unknown;
        };

        // row 객체가 통째로 온 경우 (pop-card-list selected_row 등)
        if (!data.fieldName && !data.values && !data.pkValue && typeof actual === "object") {
          const rowObj = actual as Record<string, unknown>;
          setAllValues((prev) => ({ ...prev, ...rowObj }));
          // 숨은 필드 값 추출 (valueSource 기반)
          if (hiddenMappings.length > 0) {
            const extracted: Record<string, unknown> = {};
            for (const hm of hiddenMappings) {
              if (hm.valueSource === "db_column" && hm.sourceDbColumn) {
                if (rowObj[hm.sourceDbColumn] !== undefined) {
                  extracted[hm.targetColumn] = rowObj[hm.sourceDbColumn];
                }
              } else if (hm.valueSource === "json_extract" && hm.sourceJsonColumn && hm.sourceJsonKey) {
                const raw = rowObj[hm.sourceJsonColumn];
                let parsed: Record<string, unknown> = {};
                if (typeof raw === "string") {
                  try { parsed = JSON.parse(raw); } catch { /* ignore */ }
                } else if (typeof raw === "object" && raw !== null) {
                  parsed = raw as Record<string, unknown>;
                }
                if (parsed[hm.sourceJsonKey] !== undefined) {
                  extracted[hm.targetColumn] = parsed[hm.sourceJsonKey];
                }
              }
            }
            if (Object.keys(extracted).length > 0) {
              setHiddenValues((prev) => ({ ...prev, ...extracted }));
            }
          }
          const pkCol = cfg.readSource?.pkColumn;
          const pkVal = pkCol ? rowObj[pkCol] : undefined;
          if (pkVal && cfg.readSource) {
            fetchReadSourceData(pkVal, cfg.readSource);
          }
          return;
        }

        if (data.values) {
          setAllValues((prev) => ({ ...prev, ...data.values }));
        } else if (data.fieldName) {
          setAllValues((prev) => ({
            ...prev,
            [data.fieldName!]: data.value,
          }));
        }
        if (data.pkValue && cfg.readSource) {
          fetchReadSourceData(data.pkValue, cfg.readSource);
        }
      }
    );
    return unsub;
  }, [componentId, subscribe, cfg.readSource, fetchReadSourceData]);

  useEffect(() => {
    const unsub = subscribe("scan_auto_fill", (payload: unknown) => {
      const data = payload as Record<string, unknown> | null;
      if (!data || typeof data !== "object") return;

      const fieldNames = new Set<string>();
      for (const section of cfg.sections) {
        for (const f of section.fields ?? []) {
          if (f.fieldName) fieldNames.add(f.fieldName);
        }
      }

      const matched: Record<string, unknown> = {};
      for (const [key, value] of Object.entries(data)) {
        if (fieldNames.has(key)) {
          matched[key] = value;
        }
      }

      if (Object.keys(matched).length > 0) {
        setAllValues((prev) => ({ ...prev, ...matched }));
      }
    });
    return unsub;
  }, [subscribe, cfg.sections]);

  // 데이터 수집 요청 수신: 버튼에서 collect_data 요청 → allValues + saveConfig 응답
  useEffect(() => {
    if (!componentId) return;
    const unsub = subscribe(
      `__comp_input__${componentId}__collect_data`,
      (payload: unknown) => {
        const request = (payload as Record<string, unknown>)?.value as CollectDataRequest | undefined;

        const response: CollectedDataResponse = {
          requestId: request?.requestId ?? "",
          componentId: componentId,
          componentType: "pop-field",
          data: { values: allValues },
          mapping: cfg.saveConfig?.tableName
            ? {
                targetTable: cfg.saveConfig.tableName,
                columnMapping: Object.fromEntries(
                  (cfg.saveConfig.fieldMappings || []).map((m) => [fieldIdToName[m.fieldId] || m.fieldId, m.targetColumn])
                ),
                autoGenMappings: (cfg.saveConfig.autoGenMappings || [])
                  .filter((m) => m.numberingRuleId)
                  .map((m) => ({
                    numberingRuleId: m.numberingRuleId!,
                    targetColumn: m.targetColumn,
                    showResultModal: m.showResultModal,
                    shareAcrossItems: m.shareAcrossItems ?? false,
                  })),
                hiddenMappings: (cfg.saveConfig.hiddenMappings || [])
                  .filter((m) => m.targetColumn)
                  .map((m) => ({
                    valueSource: m.valueSource,
                    targetColumn: m.targetColumn,
                    staticValue: m.staticValue,
                    sourceJsonColumn: m.sourceJsonColumn,
                    sourceJsonKey: m.sourceJsonKey,
                    sourceDbColumn: m.sourceDbColumn,
                  })),
              }
            : null,
        };

        publish(`__comp_output__${componentId}__collected_data`, response);
      }
    );
    return unsub;
  }, [componentId, subscribe, publish, allValues, cfg.saveConfig, fieldIdToName]);

  // 필드 값 변경 핸들러
  const handleFieldChange = useCallback(
    (fieldName: string, value: unknown) => {
      setAllValues((prev) => {
        const next = { ...prev, [fieldName]: value };
        if (componentId) {
          publish(`__comp_output__${componentId}__value_changed`, {
            fieldName,
            value,
            allValues: next,
            hiddenValues,
            targetTable: cfg.saveConfig?.tableName || cfg.targetTable,
            saveConfig: cfg.saveConfig,
            readSource: cfg.readSource,
          });
        }
        return next;
      });
      setErrors((prev) => {
        if (!prev[fieldName]) return prev;
        const next = { ...prev };
        delete next[fieldName];
        return next;
      });
    },
    [componentId, publish, cfg.targetTable, cfg.saveConfig, cfg.readSource, hiddenValues]
  );

  // readSource 설정 시 자동 샘플 데이터 조회 (디자인 모드 미리보기)
  const readSourceKey = cfg.readSource
    ? `${cfg.readSource.tableName}__${cfg.readSource.pkColumn}__${(cfg.readSource.fieldMappings || []).map((m) => `${m.fieldId}:${m.columnName}:${m.jsonKey || ""}`).join(",")}`
    : "";
  const previewFetchedRef = useRef("");
  useEffect(() => {
    if (!cfg.readSource?.tableName || !cfg.readSource.fieldMappings?.length) return;
    if (previewFetchedRef.current === readSourceKey) return;
    previewFetchedRef.current = readSourceKey;

    (async () => {
      try {
        const res = await dataApi.getTableData(cfg.readSource!.tableName, {
          page: 1,
          size: 1,
        });
        if (!Array.isArray(res.data) || res.data.length === 0) return;
        const row = res.data[0] as Record<string, unknown>;

        const extracted: Record<string, unknown> = {};
        for (const mapping of cfg.readSource!.fieldMappings || []) {
          if (mapping.valueSource === "json_extract" && mapping.columnName && mapping.jsonKey) {
            const rawVal = row[mapping.columnName];
            let parsed: Record<string, unknown> = {};
            if (typeof rawVal === "string") {
              try { parsed = JSON.parse(rawVal); } catch { /* ignore */ }
            } else if (typeof rawVal === "object" && rawVal !== null) {
              parsed = rawVal as Record<string, unknown>;
            }
            extracted[mapping.fieldId] = parsed[mapping.jsonKey] ?? "";
          } else if (mapping.valueSource === "db_column" && mapping.columnName) {
            extracted[mapping.fieldId] = row[mapping.columnName] ?? "";
          }
        }

        const allFieldsInConfig = cfg.sections.flatMap((s) => s.fields || []);
        const valuesUpdate: Record<string, unknown> = {};
        for (const [fieldId, val] of Object.entries(extracted)) {
          const f = allFieldsInConfig.find((fi) => fi.id === fieldId);
          const key = f?.fieldName || f?.id || fieldId;
          valuesUpdate[key] = val;
        }
        if (Object.keys(valuesUpdate).length > 0) {
          setAllValues((prev) => ({ ...prev, ...valuesUpdate }));
        }
      } catch {
        // 미리보기 조회 실패 시 무시
      }
    })();
  }, [readSourceKey, cfg.readSource, cfg.sections]);

  // "auto" 열 수 계산
  function resolveColumns(
    columns: "auto" | 1 | 2 | 3 | 4,
    fieldCount: number
  ): number {
    if (columns !== "auto") return columns;
    if (containerWidth < 200) return 1;
    if (containerWidth < 400) return Math.min(2, fieldCount);
    if (containerWidth < 600) return Math.min(3, fieldCount);
    return Math.min(4, fieldCount);
  }

  function migrateStyle(style: string): FieldSectionStyle {
    if (style === "display" || style === "input") return style;
    if (style === "summary") return "display";
    if (style === "form") return "input";
    return "input";
  }

  function sectionClassName(section: PopFieldSection): string {
    const resolved = migrateStyle(section.style);
    const defaults = DEFAULT_SECTION_APPEARANCES[resolved];
    const a = section.appearance || {};
    const bg = a.bgColor || defaults.bgColor;
    const border = a.borderColor || defaults.borderColor;
    return cn("rounded-lg border px-4", bg, border, resolved === "display" ? "py-2" : "py-3");
  }

  return (
    <div
      ref={containerRef}
      className="flex h-full w-full flex-col gap-2 overflow-auto p-1"
    >
      {cfg.sections.map((section) => {
        const fields = section.fields || [];
        const fieldCount = fields.length;
        if (fieldCount === 0) return null;
        const cols = resolveColumns(section.columns, fieldCount);
        return (
          <div key={section.id} className={sectionClassName(section)}>
            {section.label && (
              <div className="mb-1 text-xs font-medium text-muted-foreground">
                {section.label}
              </div>
            )}
            <div
              className="grid gap-3"
              style={{ gridTemplateColumns: `repeat(${cols}, 1fr)` }}
            >
              {fields.map((field) => {
                const fKey = field.fieldName || field.id;
                return (
                  <FieldRenderer
                    key={field.id}
                    field={{ ...field, fieldName: fKey }}
                    value={allValues[fKey]}
                    showLabel={section.showLabels}
                    error={errors[fKey]}
                    onChange={handleFieldChange}
                    sectionStyle={section.style}
                    allValues={allValues}
                    fieldIdToName={fieldIdToName}
                  />
                );
              })}
            </div>
          </div>
        );
      })}
      {visibleAutoGens.length > 0 && (
        <div className="rounded-lg border border-border bg-background px-4 py-3">
          <div
            className="grid gap-3"
            style={{ gridTemplateColumns: `repeat(${resolveColumns("auto", visibleAutoGens.length)}, 1fr)` }}
          >
            {visibleAutoGens.map((ag) => (
              <AutoGenFieldDisplay key={ag.id} mapping={ag} />
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

// ========================================
// FieldRenderer: 개별 필드 렌더링
// ========================================

interface FieldRendererProps {
  field: PopFieldItem;
  value: unknown;
  showLabel: boolean;
  error?: string;
  onChange: (fieldName: string, value: unknown) => void;
  sectionStyle: FieldSectionStyle;
  allValues?: Record<string, unknown>;
  fieldIdToName?: Record<string, string>;
}

function FieldRenderer({
  field,
  value,
  showLabel,
  error,
  onChange,
  sectionStyle,
  allValues,
  fieldIdToName,
}: FieldRendererProps) {
  const handleChange = useCallback(
    (v: unknown) => onChange(field.fieldName, v),
    [onChange, field.fieldName]
  );

  const resolvedStyle = sectionStyle === "summary" ? "display" : sectionStyle === "form" ? "input" : sectionStyle;
  const inputClassName = cn(
    "h-9 w-full rounded-md border px-3 text-sm",
    field.readOnly
      ? "cursor-default bg-muted text-muted-foreground"
      : "bg-background",
    resolvedStyle === "display" &&
      "border-transparent bg-transparent text-sm font-medium"
  );

  return (
    <div className="flex flex-col gap-1">
      {showLabel && field.labelText && (
        <label className="text-xs font-medium text-muted-foreground">
          {field.labelText}
          {field.validation?.required && (
            <span className="ml-0.5 text-destructive">*</span>
          )}
        </label>
      )}
      {renderByType(field, value, handleChange, inputClassName, allValues, fieldIdToName)}
      {error && <p className="text-[10px] text-destructive">{error}</p>}
    </div>
  );
}

// ========================================
// 서브타입별 렌더링 분기
// ========================================

function renderByType(
  field: PopFieldItem,
  value: unknown,
  onChange: (v: unknown) => void,
  className: string,
  allValues?: Record<string, unknown>,
  fieldIdToName?: Record<string, string>,
) {
  switch (field.inputType) {
    case "text":
      return (
        <Input
          value={String(value ?? "")}
          onChange={(e) => onChange(e.target.value)}
          readOnly={field.readOnly}
          placeholder={field.placeholder}
          className={className}
        />
      );
    case "number":
      return (
        <NumberFieldInput
          field={field}
          value={value}
          onChange={onChange}
          className={className}
        />
      );
    case "date":
      return (
        <Input
          type="date"
          value={String(value ?? "")}
          onChange={(e) => onChange(e.target.value)}
          readOnly={field.readOnly}
          className={className}
        />
      );
    case "select":
      return (
        <SelectFieldInput
          field={field}
          value={value}
          onChange={onChange}
          className={className}
          allValues={allValues}
          fieldIdToName={fieldIdToName}
        />
      );
    case "auto":
      return <AutoFieldInput field={field} value={value} className={className} />;
    case "numpad":
      return (
        <NumpadFieldInput
          field={field}
          value={value}
          onChange={onChange}
          className={className}
        />
      );
    default:
      return (
        <Input
          value={String(value ?? "")}
          readOnly
          className={className}
        />
      );
  }
}

// ========================================
// NumberFieldInput
// ========================================

function NumberFieldInput({
  field,
  value,
  onChange,
  className,
}: {
  field: PopFieldItem;
  value: unknown;
  onChange: (v: unknown) => void;
  className: string;
}) {
  return (
    <div className="flex items-center gap-1">
      <Input
        type="number"
        value={value !== undefined && value !== null ? String(value) : ""}
        onChange={(e) => {
          const num = e.target.value === "" ? "" : Number(e.target.value);
          onChange(num);
        }}
        readOnly={field.readOnly}
        placeholder={field.placeholder}
        min={field.validation?.min}
        max={field.validation?.max}
        className={cn(className, "flex-1")}
      />
      {field.unit && (
        <span className="shrink-0 text-xs text-muted-foreground">
          {field.unit}
        </span>
      )}
    </div>
  );
}

// ========================================
// SelectFieldInput
// ========================================

function SelectFieldInput({
  field,
  value,
  onChange,
  className,
  allValues,
  fieldIdToName,
}: {
  field: PopFieldItem;
  value: unknown;
  onChange: (v: unknown) => void;
  className: string;
  allValues?: Record<string, unknown>;
  fieldIdToName?: Record<string, string>;
}) {
  const [options, setOptions] = useState<{ value: string; label: string }[]>(
    []
  );
  const [loading, setLoading] = useState(false);

  const source = field.selectSource;
  const linkedFilters = source?.linkedFilters;
  const hasLinkedFilters = !!linkedFilters?.length;

  // 연동 필터에서 참조하는 필드의 현재 값들을 안정적인 문자열로 직렬화
  const linkedFilterKey = useMemo(() => {
    if (!hasLinkedFilters || !allValues || !fieldIdToName) return "";
    return linkedFilters!
      .map((lf) => {
        const fieldName = fieldIdToName[lf.sourceFieldId] ?? lf.sourceFieldId;
        const val = allValues[fieldName] ?? "";
        return `${lf.filterColumn}=${String(val)}`;
      })
      .join("&");
  }, [hasLinkedFilters, linkedFilters, allValues, fieldIdToName]);

  // 연동 필터의 소스 값이 모두 채워졌는지 확인
  const linkedFiltersFilled = useMemo(() => {
    if (!hasLinkedFilters || !allValues || !fieldIdToName) return true;
    return linkedFilters!.every((lf) => {
      const fieldName = fieldIdToName[lf.sourceFieldId] ?? lf.sourceFieldId;
      const val = allValues[fieldName];
      return val != null && val !== "";
    });
  }, [hasLinkedFilters, linkedFilters, allValues, fieldIdToName]);

  useEffect(() => {
    if (!source) return;

    if (source.type === "static" && source.staticOptions) {
      setOptions(source.staticOptions);
      return;
    }

    if (
      source.type === "table" &&
      source.tableName &&
      source.valueColumn &&
      source.labelColumn
    ) {
      // 연동 필터가 있는데 소스 값이 비어있으면 빈 옵션 표시
      if (hasLinkedFilters && !linkedFiltersFilled) {
        setOptions([]);
        return;
      }

      // 동적 필터 구성
      const dynamicFilters: Record<string, string> = {};
      if (hasLinkedFilters && allValues && fieldIdToName) {
        for (const lf of linkedFilters!) {
          const fieldName = fieldIdToName[lf.sourceFieldId] ?? lf.sourceFieldId;
          const val = allValues[fieldName];
          if (val != null && val !== "" && lf.filterColumn) {
            dynamicFilters[lf.filterColumn] = String(val);
          }
        }
      }

      setLoading(true);
      dataApi
        .getTableData(source.tableName, {
          page: 1,
          size: 500,
          sortBy: source.labelColumn,
          sortOrder: "asc",
          ...(Object.keys(dynamicFilters).length > 0 ? { filters: dynamicFilters } : {}),
        })
        .then((res) => {
          if (Array.isArray(res.data)) {
            const seen = new Set<string>();
            const deduped: { value: string; label: string }[] = [];
            for (const row of res.data) {
              const v = String(row[source.valueColumn!] ?? "");
              if (!v || seen.has(v)) continue;
              seen.add(v);
              deduped.push({ value: v, label: String(row[source.labelColumn!] ?? "") });
            }
            setOptions(deduped);
          }
        })
        .catch(() => {
          setOptions([]);
        })
        .finally(() => setLoading(false));
    }
  }, [source?.type, source?.tableName, source?.valueColumn, source?.labelColumn, source?.staticOptions, linkedFilterKey, linkedFiltersFilled]);

  // W3: 옵션이 바뀌었을 때 현재 선택값이 유효하지 않으면 자동 초기화
  useEffect(() => {
    if (!hasLinkedFilters || !value || loading) return;
    const currentStr = String(value);
    if (options.length > 0 && !options.some((o) => o.value === currentStr)) {
      onChange("");
    }
  }, [options, hasLinkedFilters]);

  if (loading) {
    return (
      <div className={cn(className, "flex items-center justify-center")}>
        <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
      </div>
    );
  }

  if (field.readOnly) {
    const selectedLabel =
      options.find((o) => o.value === String(value ?? ""))?.label ??
      String(value ?? "-");
    return (
      <Input value={selectedLabel} readOnly className={className} />
    );
  }

  if (!source) {
    return (
      <div className={cn(className, "flex items-center text-muted-foreground")}>
        옵션 소스를 설정해주세요
      </div>
    );
  }

  // W2: 연동 필터의 소스 값이 비어있으면 안내 메시지
  const emptyMessage = hasLinkedFilters && !linkedFiltersFilled
    ? "상위 필드를 먼저 선택하세요"
    : "옵션이 없습니다";

  return (
    <Select
      value={String(value ?? "")}
      onValueChange={(v) => onChange(v)}
    >
      <SelectTrigger className={cn(className, "justify-between")}>
        <SelectValue placeholder={field.placeholder || "선택"} />
      </SelectTrigger>
      <SelectContent>
        {options.length === 0 ? (
          <div className="px-2 py-1.5 text-xs text-muted-foreground">
            {emptyMessage}
          </div>
        ) : (
          options.map((opt) => (
            <SelectItem key={opt.value} value={opt.value}>
              {opt.label}
            </SelectItem>
          ))
        )}
      </SelectContent>
    </Select>
  );
}

// ========================================
// AutoFieldInput (자동 채번 - 읽기전용)
// ========================================

function AutoFieldInput({
  field,
  value,
  className,
}: {
  field: PopFieldItem;
  value: unknown;
  className: string;
}) {
  const displayValue = useMemo(() => {
    if (value) return String(value);
    if (!field.autoNumber) return "자동생성";

    const { prefix, separator, dateFormat, sequenceDigits } = field.autoNumber;
    const parts: string[] = [];
    if (prefix) parts.push(prefix);
    if (dateFormat) {
      const now = new Date();
      const dateStr = dateFormat
        .replace("YYYY", String(now.getFullYear()))
        .replace("MM", String(now.getMonth() + 1).padStart(2, "0"))
        .replace("DD", String(now.getDate()).padStart(2, "0"));
      parts.push(dateStr);
    }
    if (sequenceDigits) {
      parts.push("0".repeat(sequenceDigits));
    }
    return parts.join(separator || "-") || "자동생성";
  }, [value, field.autoNumber]);

  return (
    <Input
      value={displayValue}
      readOnly
      className={cn(className, "cursor-default bg-muted text-muted-foreground")}
      placeholder="자동생성"
    />
  );
}

// ========================================
// AutoGenFieldDisplay (자동생성 필드 - showInForm일 때 표시)
// ========================================

function AutoGenFieldDisplay({ mapping }: { mapping: PopFieldAutoGenMapping }) {
  return (
    <div className="flex flex-col gap-1">
      {mapping.label && (
        <label className="text-xs font-medium text-muted-foreground">
          {mapping.label}
        </label>
      )}
      <div className="flex h-9 items-center rounded-md border border-dashed border-muted-foreground/30 bg-muted px-3">
        <span className="text-xs text-muted-foreground">
          저장 시 자동발급
        </span>
      </div>
    </div>
  );
}

// ========================================
// NumpadFieldInput (클릭 시 숫자 직접 입력)
// ========================================

function NumpadFieldInput({
  field,
  value,
  onChange,
  className,
}: {
  field: PopFieldItem;
  value: unknown;
  onChange: (v: unknown) => void;
  className: string;
}) {
  const displayValue =
    value !== undefined && value !== null ? String(value) : "";

  return (
    <div className="flex items-center gap-1">
      <Input
        type="number"
        value={displayValue}
        onChange={(e) => {
          const num = e.target.value === "" ? "" : Number(e.target.value);
          onChange(num);
        }}
        readOnly={field.readOnly}
        placeholder={field.placeholder || "수량 입력"}
        className={cn(className, "flex-1")}
      />
      {field.unit && (
        <span className="shrink-0 text-xs text-muted-foreground">
          {field.unit}
        </span>
      )}
    </div>
  );
}
