"use client";

import { getAdaptiveLabelColor } from "@/lib/utils/darkModeColor";

/**
 * V2Input
 *
 * 통합 입력 컴포넌트
 * - text: 텍스트 입력
 * - number: 숫자 입력
 * - password: 비밀번호 입력
 * - slider: 슬라이더 입력
 * - color: 색상 선택
 * - button: 버튼 (입력이 아닌 액션)
 */

import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Input } from "@/components/ui/input";
import { Slider } from "@/components/ui/slider";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
import { V2InputProps, V2InputConfig, V2InputFormat } from "@/types/v2-components";
import { AutoGenerationUtils } from "@/lib/utils/autoGeneration";
import { AutoGenerationConfig } from "@/types/screen";
import { previewNumberingCode } from "@/lib/api/numberingRule";

// 형식별 입력 마스크 및 검증 패턴
const FORMAT_PATTERNS: Record<V2InputFormat, { pattern: RegExp; placeholder: string; errorMessage: string }> = {
  none: { pattern: /.*/, placeholder: "", errorMessage: "" },
  email: {
    pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
    placeholder: "example@email.com",
    errorMessage: "올바른 이메일 형식이 아닙니다",
  },
  tel: {
    pattern:
      /^(01[016789]|02|0[3-7]1|0[3-6][2-5]|050[2-8]|070|080)-\d{3,4}-\d{4}$|^(15|16|18)\d{2}-\d{4}$/,
    placeholder: "010-1234-5678",
    errorMessage: "올바른 전화번호 형식이 아닙니다",
  },
  url: {
    pattern: /^https?:\/\/.+/,
    placeholder: "https://example.com",
    errorMessage: "올바른 URL 형식이 아닙니다 (https://로 시작)",
  },
  currency: { pattern: /^[\d,]+$/, placeholder: "1,000,000", errorMessage: "숫자만 입력 가능합니다" },
  biz_no: {
    pattern: /^\d{3}-\d{2}-\d{5}$/,
    placeholder: "123-45-67890",
    errorMessage: "올바른 사업자번호 형식이 아닙니다",
  },
};

// 형식 검증 함수 (외부에서도 사용 가능)
export function validateInputFormat(value: string, format: V2InputFormat): { isValid: boolean; errorMessage: string } {
  if (!value || value.trim() === "" || format === "none") {
    return { isValid: true, errorMessage: "" };
  }
  const formatConfig = FORMAT_PATTERNS[format];
  if (!formatConfig) return { isValid: true, errorMessage: "" };
  const isValid = formatConfig.pattern.test(value);
  return { isValid, errorMessage: isValid ? "" : formatConfig.errorMessage };
}

// 통화 형식 변환
function formatCurrency(value: string | number): string {
  const num = typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : value;
  if (isNaN(num)) return "";
  return num.toLocaleString("ko-KR");
}

// 사업자번호 형식 변환
function formatBizNo(value: string): string {
  const digits = value.replace(/\D/g, "");
  if (digits.length <= 3) return digits;
  if (digits.length <= 5) return `${digits.slice(0, 3)}-${digits.slice(3)}`;
  return `${digits.slice(0, 3)}-${digits.slice(3, 5)}-${digits.slice(5, 10)}`;
}

// 전화번호 형식 변환
function formatTel(value: string): string {
  const digits = value.replace(/\D/g, "");
  if (digits.length === 0) return "";

  // 대표번호: 15xx, 16xx, 18xx → 4-4
  if (/^(15|16|18)/.test(digits)) {
    if (digits.length <= 4) return digits;
    return `${digits.slice(0, 4)}-${digits.slice(4, 8)}`;
  }

  // 서울: 02 → 2-4-4
  if (digits.startsWith("02")) {
    if (digits.length <= 2) return digits;
    if (digits.length <= 6) return `${digits.slice(0, 2)}-${digits.slice(2)}`;
    if (digits.length <= 10) return `${digits.slice(0, 2)}-${digits.slice(2, 6)}-${digits.slice(6)}`;
    return `${digits.slice(0, 2)}-${digits.slice(2, 6)}-${digits.slice(6, 10)}`;
  }

  // 안심번호: 050x → 4-4-4
  if (/^050[2-8]/.test(digits)) {
    if (digits.length <= 4) return digits;
    if (digits.length <= 8) return `${digits.slice(0, 4)}-${digits.slice(4)}`;
    if (digits.length <= 12) return `${digits.slice(0, 4)}-${digits.slice(4, 8)}-${digits.slice(8)}`;
    return `${digits.slice(0, 4)}-${digits.slice(4, 8)}-${digits.slice(8, 12)}`;
  }

  // 나머지 (010, 031, 070, 080 등)
  if (digits.length <= 3) return digits;
  if (digits.length <= 7) return `${digits.slice(0, 3)}-${digits.slice(3)}`;
  if (digits.length === 10) return `${digits.slice(0, 3)}-${digits.slice(3, 6)}-${digits.slice(6)}`;
  if (digits.length <= 11) return `${digits.slice(0, 3)}-${digits.slice(3, 7)}-${digits.slice(7)}`;
  return `${digits.slice(0, 3)}-${digits.slice(3, 7)}-${digits.slice(7, 11)}`;
}

/**
 * 텍스트 입력 컴포넌트
 */
const TextInput = forwardRef<
  HTMLInputElement,
  {
    value?: string | number;
    onChange?: (value: string) => void;
    format?: V2InputFormat;
    mask?: string;
    placeholder?: string;
    readonly?: boolean;
    disabled?: boolean;
    className?: string;
    columnName?: string;
    inputStyle?: React.CSSProperties;
  }
>(({ value, onChange, format = "none", placeholder, readonly, disabled, className, columnName, inputStyle }, ref) => {
  // 검증 상태
  const [hasBlurred, setHasBlurred] = useState(false);
  const [validationError, setValidationError] = useState<string>("");

  // 형식에 따른 값 포맷팅
  const formatValue = useCallback(
    (val: string): string => {
      switch (format) {
        case "currency":
          return formatCurrency(val);
        case "biz_no":
          return formatBizNo(val);
        case "tel":
          return formatTel(val);
        default:
          return val;
      }
    },
    [format],
  );

  const handleChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      let newValue = e.target.value;

      // 형식에 따른 자동 포맷팅
      if (format === "currency") {
        // 숫자와 쉼표만 허용
        newValue = newValue.replace(/[^\d,]/g, "");
        newValue = formatCurrency(newValue);
      } else if (format === "biz_no") {
        newValue = formatBizNo(newValue);
      } else if (format === "tel") {
        newValue = formatTel(newValue);
      }

      // 입력 중 에러 표시 해제 (입력 중에는 관대하게)
      if (hasBlurred && validationError) {
        const { isValid } = validateInputFormat(newValue, format);
        if (isValid) {
          setValidationError("");
        }
      }

      onChange?.(newValue);
    },
    [format, onChange, hasBlurred, validationError],
  );

  // blur 시 형식 검증
  const handleBlur = useCallback(() => {
    setHasBlurred(true);
    const currentValue = value !== undefined && value !== null ? String(value) : "";
    if (currentValue && format !== "none") {
      const { isValid, errorMessage } = validateInputFormat(currentValue, format);
      setValidationError(isValid ? "" : errorMessage);
    } else {
      setValidationError("");
    }
  }, [value, format]);

  // 값 변경 시 검증 상태 업데이트
  useEffect(() => {
    if (hasBlurred) {
      const currentValue = value !== undefined && value !== null ? String(value) : "";
      if (currentValue && format !== "none") {
        const { isValid, errorMessage } = validateInputFormat(currentValue, format);
        setValidationError(isValid ? "" : errorMessage);
      } else {
        setValidationError("");
      }
    }
  }, [value, format, hasBlurred]);

  // 글로벌 폼 검증 이벤트 리스너 (저장 시 호출)
  useEffect(() => {
    if (format === "none" || !columnName) return;

    const handleValidateForm = (event: CustomEvent) => {
      const currentValue = value !== undefined && value !== null ? String(value) : "";
      if (currentValue) {
        const { isValid, errorMessage } = validateInputFormat(currentValue, format);
        if (!isValid) {
          setHasBlurred(true);
          setValidationError(errorMessage);
          // 검증 결과를 이벤트에 기록
          if (event.detail?.errors) {
            event.detail.errors.push({
              columnName,
              message: errorMessage,
            });
          }
        }
      }
    };

    window.addEventListener("validateFormInputs", handleValidateForm as EventListener);
    return () => {
      window.removeEventListener("validateFormInputs", handleValidateForm as EventListener);
    };
  }, [format, value, columnName]);

  const displayValue = useMemo(() => {
    if (value === undefined || value === null) return "";
    return formatValue(String(value));
  }, [value, formatValue]);

  const inputPlaceholder = placeholder || FORMAT_PATTERNS[format].placeholder;
  const hasError = hasBlurred && !!validationError;

  return (
    <div className="relative h-full w-full">
      <Input
        ref={ref}
        type="text"
        value={displayValue}
        onChange={handleChange}
        onBlur={handleBlur}
        placeholder={inputPlaceholder}
        readOnly={readonly}
        disabled={disabled}
        className={cn("h-full w-full", hasError && "border-destructive focus-visible:ring-destructive", className)}
        style={inputStyle}
      />
      {hasError && <p className="text-destructive absolute top-full left-0 mt-0.5 text-[11px]">{validationError}</p>}
    </div>
  );
});
TextInput.displayName = "TextInput";

/**
 * 숫자 입력 컴포넌트
 */
const NumberInput = forwardRef<
  HTMLInputElement,
  {
    value?: number;
    onChange?: (value: number | undefined) => void;
    min?: number;
    max?: number;
    step?: number;
    placeholder?: string;
    readonly?: boolean;
    disabled?: boolean;
    className?: string;
    inputStyle?: React.CSSProperties;
  }
>(({ value, onChange, min, max, step = 1, placeholder, readonly, disabled, className, inputStyle }, ref) => {
  const handleChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const val = e.target.value;
      if (val === "") {
        onChange?.(undefined);
        return;
      }

      let num = parseFloat(val);

      // 범위 제한
      if (min !== undefined && num < min) num = min;
      if (max !== undefined && num > max) num = max;

      onChange?.(num);
    },
    [min, max, onChange],
  );

  return (
    <Input
      ref={ref}
      type="number"
      value={value ?? ""}
      onChange={handleChange}
      min={min}
      max={max}
      step={step}
      placeholder={placeholder || "숫자 입력"}
      readOnly={readonly}
      disabled={disabled}
      className={cn("h-full w-full", className)}
      style={inputStyle}
    />
  );
});
NumberInput.displayName = "NumberInput";

/**
 * 비밀번호 입력 컴포넌트
 */
const PasswordInput = forwardRef<
  HTMLInputElement,
  {
    value?: string;
    onChange?: (value: string) => void;
    placeholder?: string;
    readonly?: boolean;
    disabled?: boolean;
    className?: string;
    inputStyle?: React.CSSProperties;
  }
>(({ value, onChange, placeholder, readonly, disabled, className, inputStyle }, ref) => {
  const [showPassword, setShowPassword] = useState(false);

  return (
    <div className="relative">
      <Input
        ref={ref}
        type={showPassword ? "text" : "password"}
        value={value ?? ""}
        onChange={(e) => onChange?.(e.target.value)}
        placeholder={placeholder || "비밀번호 입력"}
        readOnly={readonly}
        disabled={disabled}
        className={cn("h-full w-full pr-10", className)}
        style={inputStyle}
      />
      <button
        type="button"
        onClick={() => setShowPassword(!showPassword)}
        className="text-muted-foreground hover:text-foreground absolute top-1/2 right-2 -translate-y-1/2 text-xs"
      >
        {showPassword ? "숨김" : "보기"}
      </button>
    </div>
  );
});
PasswordInput.displayName = "PasswordInput";

/**
 * 슬라이더 입력 컴포넌트
 */
const SliderInput = forwardRef<
  HTMLDivElement,
  {
    value?: number;
    onChange?: (value: number) => void;
    min?: number;
    max?: number;
    step?: number;
    disabled?: boolean;
    className?: string;
  }
>(({ value, onChange, min = 0, max = 100, step = 1, disabled, className }, ref) => {
  return (
    <div ref={ref} className={cn("flex items-center gap-4", className)}>
      <Slider
        value={[value ?? min]}
        onValueChange={(values) => onChange?.(values[0])}
        min={min}
        max={max}
        step={step}
        disabled={disabled}
        className="flex-1"
      />
      <span className="w-12 text-right text-sm font-medium">{value ?? min}</span>
    </div>
  );
});
SliderInput.displayName = "SliderInput";

/**
 * 색상 선택 컴포넌트
 */
const ColorInput = forwardRef<
  HTMLInputElement,
  {
    value?: string;
    onChange?: (value: string) => void;
    disabled?: boolean;
    className?: string;
  }
>(({ value, onChange, disabled, className }, ref) => {
  return (
    <div className={cn("flex items-center gap-2", className)}>
      <Input
        ref={ref}
        type="color"
        value={value || "#000000"}
        onChange={(e) => onChange?.(e.target.value)}
        disabled={disabled}
        className="h-full w-12 cursor-pointer p-1"
      />
      <Input
        type="text"
        value={value || "#000000"}
        onChange={(e) => onChange?.(e.target.value)}
        disabled={disabled}
        className="h-full flex-1 uppercase"
        maxLength={7}
      />
    </div>
  );
});
ColorInput.displayName = "ColorInput";

/**
 * 여러 줄 텍스트 입력 컴포넌트
 */
const TextareaInput = forwardRef<
  HTMLTextAreaElement,
  {
    value?: string;
    onChange?: (value: string) => void;
    placeholder?: string;
    rows?: number;
    readonly?: boolean;
    disabled?: boolean;
    className?: string;
    inputStyle?: React.CSSProperties;
  }
>(({ value = "", onChange, placeholder, rows = 3, readonly, disabled, className, inputStyle }, ref) => {
  return (
    <textarea
      ref={ref}
      value={value}
      onChange={(e) => onChange?.(e.target.value)}
      placeholder={placeholder}
      rows={rows}
      readOnly={readonly}
      disabled={disabled}
      className={cn(
        "border-input placeholder:text-muted-foreground focus-visible:ring-ring flex h-full w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-sm focus-visible:ring-1 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
        className,
      )}
      style={inputStyle}
    />
  );
});
TextareaInput.displayName = "TextareaInput";

/**
 * 메인 V2Input 컴포넌트
 */
export const V2Input = forwardRef<HTMLDivElement, V2InputProps>((props, ref) => {
  const { id, label, required, readonly, disabled, style, size, config: configProp, value, onChange } = props;

  // formData 추출 (채번규칙 날짜 컬럼 기준 생성 시 사용)
  const formData = (props as any).formData || {};
  const columnName = (props as any).columnName;
  // onFormDataChange 추출 (채번 규칙 ID를 formData에 저장하기 위함)
  const onFormDataChange = (props as any).onFormDataChange;

  // config가 없으면 기본값 사용
  const config = (configProp || { type: "text" }) as V2InputConfig & {
    inputType?: string;
    rows?: number;
    autoGeneration?: AutoGenerationConfig;
  };

  // 자동생성 설정 추출
  const autoGeneration: AutoGenerationConfig = (props as any).autoGeneration ||
    (config as any).autoGeneration || {
      type: "none",
      enabled: false,
    };

  // 자동생성 상태 관리
  const [autoGeneratedValue, setAutoGeneratedValue] = useState<string | null>(null);
  const isGeneratingRef = useRef(false);
  const hasGeneratedRef = useRef(false);
  const lastFormDataRef = useRef<string>(""); // 마지막 formData 추적 (채번 규칙용)

  // 채번 타입 자동생성 상태
  const [isGeneratingNumbering, setIsGeneratingNumbering] = useState(false);
  const hasGeneratedNumberingRef = useRef(false);

  // formData를 ref로 관리하여 closure 문제 해결 (채번 코드 생성 시 최신 값 사용)
  const formDataRef = useRef(formData);
  formDataRef.current = formData;

  // tableName 추출 (여러 소스에서 확인)
  // 1. props에서 직접 전달받은 값
  // 2. config에서 설정된 값
  // 3. 컴포넌트 overrides에서 설정된 값 (V2 레이아웃)
  // 4. screenInfo에서 화면 테이블명
  const tableName =
    (props as any).tableName ||
    (config as any).tableName ||
    (props as any).component?.tableName ||
    (props as any).component?.overrides?.tableName ||
    (props as any).screenInfo?.tableName;

  // 수정 모드 여부 확인
  const originalData = (props as any).originalData || (props as any)._originalData;
  const isEditMode = originalData && Object.keys(originalData).length > 0;

  // 채번 규칙인 경우 formData 변경 감지 (자기 자신 필드 제외)
  const formDataForNumbering = useMemo(() => {
    if (autoGeneration.type !== "numbering_rule") return "";
    // 자기 자신의 값은 제외 (무한 루프 방지)
    const { [columnName]: _, ...rest } = formData;
    return JSON.stringify(rest);
  }, [autoGeneration.type, formData, columnName]);

  // 자동생성 로직
  useEffect(() => {
    const generateValue = async () => {
      // 자동생성 비활성화 또는 생성 중
      if (!autoGeneration.enabled || isGeneratingRef.current) {
        return;
      }

      // 수정 모드에서는 자동생성 안함
      if (isEditMode) {
        return;
      }

      // 채번 규칙인 경우: formData가 변경되었는지 확인
      const isNumberingRule = autoGeneration.type === "numbering_rule";
      const formDataChanged =
        isNumberingRule && formDataForNumbering !== lastFormDataRef.current && lastFormDataRef.current !== "";

      // 이미 생성되었고, formData 변경이 아닌 경우 스킵
      if (hasGeneratedRef.current && !formDataChanged) {
        return;
      }

      // 첫 생성 시: 값이 이미 있으면 스킵 (formData 변경 시에는 강제 재생성)
      if (!formDataChanged && value !== undefined && value !== null && value !== "") {
        return;
      }

      isGeneratingRef.current = true;

      try {
        // formData를 전달하여 날짜 컬럼 기준 생성 지원
        const generatedValue = await AutoGenerationUtils.generateValue(autoGeneration, columnName, formData);

        if (generatedValue !== null && generatedValue !== undefined) {
          setAutoGeneratedValue(generatedValue);
          onChange?.(generatedValue);
          hasGeneratedRef.current = true;

          // formData 기록
          if (isNumberingRule) {
            lastFormDataRef.current = formDataForNumbering;
          }
        }
      } catch (error) {
        console.error("자동생성 실패:", error);
      } finally {
        isGeneratingRef.current = false;
      }
    };

    generateValue();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [autoGeneration.enabled, autoGeneration.type, isEditMode, formDataForNumbering]);

  // 채번 규칙 ID 캐싱
  const numberingRuleIdRef = useRef<string | null>(null);
  const lastCategoryValuesRef = useRef<string>("");
  // 사용자가 직접 입력 중인지 추적 (재생성 방지)
  const userEditedNumberingRef = useRef<boolean>(false);
  // 원래 수동 입력 부분이 있었는지 추적 (____가 있었으면 계속 편집 가능)
  const hadManualPartRef = useRef<boolean>(false);
  // 채번 템플릿 저장 (____가 포함된 원본 형태)
  const numberingTemplateRef = useRef<string>("");
  // 사용자가 수동 입력한 값 저장
  const [manualInputValue, setManualInputValue] = useState<string>("");

  // formData에서 카테고리 관련 값 추출 (채번 파트에서 카테고리 사용 시)
  // 채번 필드 자체의 값은 제외해야 함 (무한 루프 방지)
  // inputType을 여러 소스에서 확인
  const propsInputType = (props as any).inputType;
  const categoryValuesForNumbering = useMemo(() => {
    const inputType = propsInputType || config.inputType || config.type || "text";
    if (inputType !== "numbering") return "";
    // formData에서 category 타입 필드 값들을 추출 (채번 필드 자체는 제외)
    const categoryFields: Record<string, string> = {};
    for (const [key, val] of Object.entries(formData)) {
      // 현재 채번 필드(columnName)는 제외
      if (key === columnName) continue;
      if (typeof val === "string" && val) {
        categoryFields[key] = val;
      }
    }
    return JSON.stringify(categoryFields);
  }, [propsInputType, config.inputType, config.type, formData, columnName]);

  // 채번 타입 자동생성 로직 (테이블 관리에서 설정된 numberingRuleId 사용)
  useEffect(() => {
    const generateNumberingCode = async () => {
      // inputType을 여러 소스에서 확인 (props에서 직접 전달받거나 config에서)
      const inputType = (props as any).inputType || config.inputType || config.type || "text";

      // numbering 타입이 아니면 스킵
      if (inputType !== "numbering") {
        return;
      }

      // 수정 모드에서는 자동생성 안함
      if (isEditMode) {
        return;
      }

      // 생성 중이면 스킵
      if (isGeneratingNumbering) {
        return;
      }

      // 사용자가 직접 편집한 경우 재생성 안함 (단, 카테고리 변경 시에는 재생성)
      const categoryChanged = categoryValuesForNumbering !== lastCategoryValuesRef.current;
      if (userEditedNumberingRef.current && !categoryChanged) {
        return;
      }

      // 이미 생성되었고 카테고리 값이 변경되지 않았으면 스킵
      if (hasGeneratedNumberingRef.current && !categoryChanged) {
        return;
      }

      // 첫 생성 시: 값이 이미 있고 카테고리 변경이 아니면 스킵
      if (!categoryChanged && value !== undefined && value !== null && value !== "") {
        return;
      }

      // tableName과 columnName이 필요
      if (!tableName || !columnName) {
        console.warn("채번 타입: tableName 또는 columnName이 없습니다", { tableName, columnName });
        return;
      }

      setIsGeneratingNumbering(true);

      try {
        // 채번 규칙 ID 캐싱 (한 번만 조회)
        if (!numberingRuleIdRef.current) {
          // table_name + column_name 기반으로 채번 규칙 조회
          try {
            const { apiClient } = await import("@/lib/api/client");
            const ruleResponse = await apiClient.get(`/numbering-rules/by-column/${tableName}/${columnName}`);
            if (ruleResponse.data?.success && ruleResponse.data?.data?.ruleId) {
              numberingRuleIdRef.current = ruleResponse.data.data.ruleId;

              if (onFormDataChange && columnName) {
                onFormDataChange(`${columnName}_numberingRuleId`, ruleResponse.data.data.ruleId);
              }
            }
          } catch {
            // by-column 조회 실패 시 detailSettings fallback
            try {
              const { getTableColumns } = await import("@/lib/api/tableManagement");
              const columnsResponse = await getTableColumns(tableName);
              if (columnsResponse.success && columnsResponse.data) {
                const columns = columnsResponse.data.columns || columnsResponse.data;
                const targetColumn = columns.find((col: { columnName: string }) => col.columnName === columnName);
                if (targetColumn?.detailSettings) {
                  const parsed =
                    typeof targetColumn.detailSettings === "string"
                      ? JSON.parse(targetColumn.detailSettings)
                      : targetColumn.detailSettings;
                  numberingRuleIdRef.current = parsed.numberingRuleId || null;
                }
              }
            } catch {
              /* ignore */
            }
          }
        }

        const numberingRuleId = numberingRuleIdRef.current;

        if (!numberingRuleId) {
          console.warn("채번 규칙을 찾을 수 없습니다. 옵션설정 > 채번설정에서 규칙을 생성하세요.", {
            tableName,
            columnName,
          });
          return;
        }

        // 채번 코드 생성 (formDataRef.current 사용하여 최신 formData 전달)
        const currentFormData = formDataRef.current;
        const previewResponse = await previewNumberingCode(numberingRuleId, currentFormData);

        if (previewResponse.success && previewResponse.data?.generatedCode) {
          const generatedCode = previewResponse.data.generatedCode;
          hasGeneratedNumberingRef.current = true;
          lastCategoryValuesRef.current = categoryValuesForNumbering;

          // 수동 입력 부분이 있는 경우
          if (generatedCode.includes("____")) {
            hadManualPartRef.current = true;
            const oldTemplate = numberingTemplateRef.current;
            numberingTemplateRef.current = generatedCode;

            // 카테고리 변경으로 템플릿이 바뀌었을 때 기존 사용자 입력값 유지
            if (oldTemplate && oldTemplate !== generatedCode) {
              // 템플릿이 변경되었지만 사용자 입력값은 유지
              const templateParts = generatedCode.split("____");
              const templatePrefix = templateParts[0] || "";
              const templateSuffix = templateParts.length > 1 ? templateParts.slice(1).join("") : "";

              // 기존 manualInputValue를 사용하여 새 값 조합 (상태는 유지)
              // 참고: setManualInputValue는 호출하지 않음 (기존 값 유지)
              const finalValue = templatePrefix + (userEditedNumberingRef.current ? "" : "") + templateSuffix;

              // 사용자가 입력한 적이 없으면 템플릿 그대로
              if (!userEditedNumberingRef.current) {
                setAutoGeneratedValue(generatedCode);
                onChange?.(generatedCode);
              }
              // 사용자가 입력한 적이 있으면 입력값 유지하며 템플릿만 변경
              // (manualInputValue 상태는 유지되므로 UI에서 자동 반영)
            } else {
              // 첫 생성
              setAutoGeneratedValue(generatedCode);
              onChange?.(generatedCode);
              userEditedNumberingRef.current = false;
            }
          } else {
            // 수동 입력 부분 없음
            setAutoGeneratedValue(generatedCode);
            onChange?.(generatedCode);
            userEditedNumberingRef.current = false;
          }

          // 채번 코드 생성 성공
        } else {
          console.warn("채번 코드 생성 실패:", previewResponse);
        }
      } catch (error) {
        console.error("채번 자동생성 오류:", error);
      } finally {
        setIsGeneratingNumbering(false);
      }
    };

    generateNumberingCode();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [tableName, columnName, isEditMode, categoryValuesForNumbering]);

  // 🆕 beforeFormSave 이벤트 리스너 - 저장 직전에 현재 조합된 값을 formData에 주입
  useEffect(() => {
    const inputType = propsInputType || config.inputType || config.type || "text";
    if (inputType !== "numbering" || !columnName) return;

    const handleBeforeFormSave = (event: CustomEvent) => {
      const template = numberingTemplateRef.current;
      if (!template || !template.includes("____")) return;

      // 템플릿에서 prefix와 suffix 추출
      const templateParts = template.split("____");
      const templatePrefix = templateParts[0] || "";
      const templateSuffix = templateParts.length > 1 ? templateParts.slice(1).join("") : "";

      // 현재 조합된 값 생성
      const currentValue = templatePrefix + manualInputValue + templateSuffix;

      // formData에 직접 주입
      if (event.detail?.formData && columnName) {
        event.detail.formData[columnName] = currentValue;
      }
    };

    window.addEventListener("beforeFormSave", handleBeforeFormSave as EventListener);
    return () => {
      window.removeEventListener("beforeFormSave", handleBeforeFormSave as EventListener);
    };
  }, [columnName, manualInputValue, propsInputType, config.inputType, config.type]);

  // 실제 표시할 값 (자동생성 값 또는 props value)
  const displayValue = autoGeneratedValue ?? value;

  // 조건부 렌더링 체크
  // TODO: conditional 처리 로직 추가

  // 타입별 입력 컴포넌트 렌더링
  const renderInput = () => {
    const inputType = propsInputType || config.inputType || config.type || "text";
    switch (inputType) {
      case "text":
        return (
          <TextInput
            value={displayValue}
            onChange={(v) => {
              setAutoGeneratedValue(null); // 사용자 입력 시 자동생성 값 초기화
              onChange?.(v);
            }}
            format={config.format}
            mask={config.mask}
            placeholder={config.placeholder}
            readonly={readonly || (autoGeneration.enabled && hasGeneratedRef.current)}
            disabled={disabled}
            columnName={columnName}
            inputStyle={inputTextStyle}
          />
        );

      case "number":
        // DB에서 문자열("325")로 반환되는 경우도 숫자로 변환하여 표시
        const numValue =
          typeof displayValue === "number"
            ? displayValue
            : displayValue !== undefined && displayValue !== null && displayValue !== "" && !isNaN(Number(displayValue))
              ? Number(displayValue)
              : undefined;
        return (
          <NumberInput
            value={numValue}
            onChange={(v) => {
              setAutoGeneratedValue(null);
              onChange?.(v ?? 0);
            }}
            min={config.min}
            max={config.max}
            step={config.step}
            placeholder={config.placeholder}
            readonly={readonly}
            disabled={disabled}
            inputStyle={inputTextStyle}
          />
        );

      case "password":
        return (
          <PasswordInput
            value={typeof displayValue === "string" ? displayValue : ""}
            onChange={(v) => {
              setAutoGeneratedValue(null);
              onChange?.(v);
            }}
            placeholder={config.placeholder}
            readonly={readonly}
            disabled={disabled}
            inputStyle={inputTextStyle}
          />
        );

      case "slider":
        // DB에서 문자열로 반환되는 경우도 숫자로 변환
        const sliderValue =
          typeof displayValue === "number"
            ? displayValue
            : displayValue !== undefined && displayValue !== null && displayValue !== "" && !isNaN(Number(displayValue))
              ? Number(displayValue)
              : (config.min ?? 0);
        return (
          <SliderInput
            value={sliderValue}
            onChange={(v) => {
              setAutoGeneratedValue(null);
              onChange?.(v);
            }}
            min={config.min}
            max={config.max}
            step={config.step}
            disabled={disabled}
          />
        );

      case "color":
        return (
          <ColorInput
            value={typeof displayValue === "string" ? displayValue : "#000000"}
            onChange={(v) => {
              setAutoGeneratedValue(null);
              onChange?.(v);
            }}
            disabled={disabled}
          />
        );

      case "textarea":
        return (
          <TextareaInput
            value={displayValue as string}
            onChange={(v) => {
              setAutoGeneratedValue(null);
              onChange?.(v);
            }}
            placeholder={config.placeholder}
            rows={config.rows}
            readonly={readonly}
            disabled={disabled}
            inputStyle={inputTextStyle}
          />
        );

      case "numbering": {
        // 채번 타입: ____ 부분만 편집 가능하게 처리
        const template = numberingTemplateRef.current;
        const canEdit = hadManualPartRef.current && template;

        // 채번 필드 렌더링

        // 템플릿이 없으면 읽기 전용 (아직 생성 전이거나 수동 입력 부분 없음)
        if (!canEdit) {
          return (
            <TextInput
              value={displayValue || ""}
              onChange={() => {}}
              placeholder={isGeneratingNumbering ? "생성 중..." : "자동 생성됩니다"}
              readonly={true}
              disabled={disabled || isGeneratingNumbering}
              inputStyle={inputTextStyle}
            />
          );
        }

        // 템플릿에서 prefix와 suffix 추출
        const templateParts = template.split("____");
        const templatePrefix = templateParts[0] || "";
        const templateSuffix = templateParts.length > 1 ? templateParts.slice(1).join("") : "";

        return (
          <div className="numbering-segment border-input flex h-full items-center rounded-md border outline-none transition-[color,box-shadow]">
            {/* 고정 접두어 */}
            {templatePrefix && (
              <span className="text-muted-foreground bg-muted flex h-full items-center rounded-l-[5px] px-2 text-sm">
                {templatePrefix}
              </span>
            )}
            {/* 편집 가능한 부분 */}
            <input
              type="text"
              value={manualInputValue}
              onChange={(e) => {
                const newUserInput = e.target.value;
                setManualInputValue(newUserInput);

                // 전체 값 조합
                const newValue = templatePrefix + newUserInput + templateSuffix;
                userEditedNumberingRef.current = true;
                setAutoGeneratedValue(newValue);

                // 모든 방법으로 formData 업데이트 시도
                onChange?.(newValue);
                if (onFormDataChange && columnName) {
                  onFormDataChange(columnName, newValue);
                }

                // 커스텀 이벤트로도 전달 (최후의 보루)
                if (typeof window !== "undefined" && columnName) {
                  window.dispatchEvent(
                    new CustomEvent("numberingValueChanged", {
                      detail: { columnName, value: newValue },
                    }),
                  );
                }
              }}
              placeholder="입력"
              className="h-full min-w-[60px] flex-1 border-0 bg-transparent px-2 text-sm ring-0"
              disabled={disabled || isGeneratingNumbering}
              style={{ ...inputTextStyle, outline: 'none' }}
            />
            {/* 고정 접미어 */}
            {templateSuffix && (
              <span className="text-muted-foreground bg-muted flex h-full items-center rounded-r-[5px] px-2 text-sm">
                {templateSuffix}
              </span>
            )}
          </div>
        );
      }

      default:
        return (
          <TextInput
            value={displayValue}
            onChange={(v) => {
              setAutoGeneratedValue(null);
              onChange?.(v);
            }}
            format={config.format}
            placeholder={config.placeholder}
            readonly={readonly}
            disabled={disabled}
            columnName={columnName}
            inputStyle={inputTextStyle}
          />
        );
    }
  };

  const actualLabel = label || style?.labelText;
  const showLabel = actualLabel && style?.labelDisplay !== false;
  const componentWidth = size?.width || style?.width;
  const componentHeight = size?.height || style?.height;

  // 라벨 위치 및 높이 계산
  const labelPos = style?.labelPosition || "top";
  const isHorizLabel = labelPos === "left" || labelPos === "right";
  const labelFontSize = style?.labelFontSize ? parseInt(String(style.labelFontSize)) : 14;
  const labelMarginBottom = style?.labelMarginBottom ? parseInt(String(style.labelMarginBottom)) : 4;
  const estimatedLabelHeight = labelFontSize + labelMarginBottom + 2;
  const labelGapValue = style?.labelGap || "8px";

  // 커스텀 스타일 감지
  const hasCustomBorder = !!(style?.borderWidth || style?.borderColor || style?.borderStyle || style?.border);
  const hasCustomBackground = !!style?.backgroundColor;
  const hasCustomRadius = !!style?.borderRadius;

  const customTextStyle: React.CSSProperties = {};
  if (style?.color) customTextStyle.color = style.color;
  if (style?.fontSize) customTextStyle.fontSize = style.fontSize;
  if (style?.fontWeight) customTextStyle.fontWeight = style.fontWeight;
  if (style?.textAlign) customTextStyle.textAlign = style.textAlign as React.CSSProperties["textAlign"];
  const hasCustomText = Object.keys(customTextStyle).length > 0;
  const inputTextStyle: React.CSSProperties | undefined = hasCustomText ? customTextStyle : undefined;

  const labelElement = showLabel ? (
    <Label
      htmlFor={id}
      style={{
        ...(labelPos === "top" ? { position: "absolute" as const, top: `-${estimatedLabelHeight}px`, left: 0 } : {}),
        ...(labelPos === "bottom"
          ? { position: "absolute" as const, bottom: `-${estimatedLabelHeight}px`, left: 0 }
          : {}),
        fontSize: style?.labelFontSize || "14px",
        color: getAdaptiveLabelColor(style?.labelColor),
        fontWeight: style?.labelFontWeight || "500",
      }}
      className="text-sm font-medium whitespace-nowrap"
    >
      {actualLabel}
      {required && <span className="ml-0.5 text-amber-500">*</span>}
    </Label>
  ) : null;

  const inputContent = (
    <div
      className={cn(
        isHorizLabel ? "min-w-0 flex-1" : "h-full w-full",
        hasCustomBorder && "[&_.border]:border-0! [&_input]:border-0! [&_textarea]:border-0!",
        (hasCustomBorder || hasCustomRadius) &&
          "[&_.rounded-md]:rounded-none! [&_input]:rounded-none! [&_textarea]:rounded-none!",
        hasCustomBackground && "[&_input]:bg-transparent! [&_textarea]:bg-transparent!",
      )}
      style={{ ...(hasCustomText ? customTextStyle : {}), ...(isHorizLabel ? { height: "100%" } : {}) }}
    >
      {renderInput()}
    </div>
  );

  if (isHorizLabel && showLabel) {
    return (
      <div
        ref={ref}
        id={id}
        className={cn(
          "flex gap-1",
          labelPos === "left" ? "flex-row items-center" : "flex-row-reverse items-center",
        )}
        style={{
          width: componentWidth,
          height: componentHeight,
          gap: labelGapValue,
        }}
      >
        <Label
          htmlFor={id}
          style={{
            fontSize: style?.labelFontSize || "14px",
            color: getAdaptiveLabelColor(style?.labelColor),
            fontWeight: style?.labelFontWeight || "500",
          }}
          className="text-sm font-medium whitespace-nowrap w-[120px] shrink-0"
        >
          {actualLabel}
          {required && <span className="ml-0.5 text-amber-500">*</span>}
        </Label>
        <div
          className={cn(
            "w-full min-w-0 flex-1",
            hasCustomBorder && "[&_.border]:border-0! [&_input]:border-0! [&_textarea]:border-0!",
            (hasCustomBorder || hasCustomRadius) &&
              "[&_.rounded-md]:rounded-none! [&_input]:rounded-none! [&_textarea]:rounded-none!",
            hasCustomBackground && "[&_input]:bg-transparent! [&_textarea]:bg-transparent!",
          )}
          style={{ ...(hasCustomText ? customTextStyle : {}) }}
        >
          {renderInput()}
        </div>
      </div>
    );
  }

  return (
    <div
      ref={ref}
      id={id}
      className="relative"
      style={{
        width: componentWidth,
        height: componentHeight,
      }}
    >
      {labelElement}
      {inputContent}
    </div>
  );
});

V2Input.displayName = "V2Input";

export default V2Input;
