"use client";

import React, { useState, useEffect } from "react";
import { ComponentRendererProps } from "@/types/component";
import { InputConfig, InputFieldType } from "./types";
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
import { SingleDatePicker, DateTimePicker, TimePicker, RangeDatePicker } from "./pickers";
import { SingleSelectPicker, MultiSelectPicker, RadioPicker, CheckboxListPicker, TogglePicker, TagPicker, SwapPicker } from "./select-pickers";
import { NumberingPicker } from "./numbering-picker";
import { FilePicker } from "./file-picker";
import { useOptionLoader } from "./use-option-loader";
import { AutoGenerationUtils } from "@/lib/utils/autoGeneration";

/**
 * Input — 통합 필드 입력 컴포넌트
 *
 * FieldConfig.type (10종) 기반 내부 분기 렌더. 하나의 컴포넌트가 text/number/
 * date/datetime/select/entity/checkbox/textarea/file/code 를 전부 처리한다.
 *
 * 관련: notes/gbpark/2026-04-11-component-unification-plan.md §3.4
 */

const VALID_TYPES: InputFieldType[] = [
  "text",
  "number",
  "date",
  "datetime",
  "time",
  "daterange",
  "select",
  "entity",
  "checkbox",
  "textarea",
  "file",
  "code",
];

function isValidType(v: unknown): v is InputFieldType {
  return typeof v === "string" && (VALID_TYPES as string[]).includes(v);
}

/**
 * 텍스트 입력 mask 적용. `#`/`0` = 숫자, `A` = 영문, `*` = 임의 문자, 그 외는 literal.
 * 예: `###-####` + "1234567" → "123-4567"
 *
 * 사용자가 친 값에서 mask 와 매칭되지 않는 문자는 건너뛰고, literal 자리는 항상 그대로 출력.
 * 사용자가 literal 위치에 literal 과 동일한 문자를 친 경우는 1회 소비해서 정렬을 맞춘다.
 */
function applyInputMask(value: string, mask: string): string {
  if (!mask) return value;
  let out = "";
  let vi = 0;
  for (let mi = 0; mi < mask.length; mi++) {
    const m = mask[mi];
    if (vi >= value.length) break;
    if (m === "#" || m === "0") {
      while (vi < value.length && !/[0-9]/.test(value[vi])) vi++;
      if (vi >= value.length) break;
      out += value[vi++];
    } else if (m === "A") {
      while (vi < value.length && !/[A-Za-z]/.test(value[vi])) vi++;
      if (vi >= value.length) break;
      out += value[vi++];
    } else if (m === "*") {
      out += value[vi++];
    } else {
      out += m;
      if (value[vi] === m) vi++;
    }
  }
  return out;
}

const HEX_PATTERN = /^#[0-9a-fA-F]{6}$/;
function normalizeHex(v: unknown, fallback = "#000000"): string {
  if (typeof v === "string" && HEX_PATTERN.test(v)) return v;
  return fallback;
}

/**
 * 색상 텍스트 입력 sanitizer — 항상 `#rrggbb` 6자리 hex 로 강제.
 *   - 선행 `#` 제거
 *   - hex 가 아닌 문자 제거
 *   - 6자 초과는 잘라냄
 *   - 모자란 자리는 `0` 으로 패딩
 *   - 결과는 소문자 `#rrggbb`
 */
function sanitizeHexInput(raw: string): string {
  const stripped = raw.startsWith("#") ? raw.slice(1) : raw;
  const hexOnly = stripped.replace(/[^0-9a-fA-F]/g, "").toLowerCase();
  const trimmed = hexOnly.slice(0, 6);
  const padded = trimmed.padEnd(6, "0");
  return `#${padded}`;
}

export interface InputComponentProps extends ComponentRendererProps {
  config?: InputConfig;
}

export const InputComponent: React.FC<InputComponentProps> = ({
  component,
  isDesignMode = false,
  isSelected = false,
  onClick,
  onDragStart,
  onDragEnd,
  config,
  className,
  style,
  ...props
}) => {
  // ─── 4경로 머지 (표준 패턴) ──────────────────────────────────────────
  // ★ 주의: props.type 은 component.type ('widget'/'component') 일 수 있으므로
  //   InputFieldType 10종에 포함될 때만 사용.
  const fromProps: Partial<InputConfig> = {};
  const p = props as any;
  if (isValidType(p.type)) fromProps.type = p.type;
  if (typeof p.label === "string") fromProps.label = p.label;
  if (typeof p.placeholder === "string") fromProps.placeholder = p.placeholder;
  if (typeof p.helperText === "string") fromProps.helperText = p.helperText;
  if (p.defaultValue !== undefined) fromProps.defaultValue = p.defaultValue;
  if (typeof p.required === "boolean") fromProps.required = p.required;
  if (typeof p.editable === "boolean") fromProps.editable = p.editable;
  if (typeof p.readonly === "boolean") fromProps.readonly = p.readonly;
  if (typeof p.disabled === "boolean") fromProps.disabled = p.disabled;
  if (Array.isArray(p.options)) fromProps.options = p.options;
  if (p.ref && typeof p.ref === "object" && !("current" in p.ref))
    fromProps.ref = p.ref;
  if (typeof p.format === "string") fromProps.format = p.format;
  if (typeof p.computed === "string") fromProps.computed = p.computed;
  if (typeof p.min === "number") fromProps.min = p.min;
  if (typeof p.max === "number") fromProps.max = p.max;
  if (typeof p.step === "number") fromProps.step = p.step;
  if (typeof p.minLength === "number") fromProps.minLength = p.minLength;
  if (typeof p.maxLength === "number") fromProps.maxLength = p.maxLength;
  if (typeof p.rows === "number") fromProps.rows = p.rows;
  if (typeof p.accept === "string") fromProps.accept = p.accept;
  if (typeof p.multiple === "boolean") fromProps.multiple = p.multiple;
  if (typeof p.mask === "string") fromProps.mask = p.mask;

  const componentConfig = {
    ...config,
    ...((component as any).config ?? {}),
    ...((component as any).componentConfig ?? {}),
    ...fromProps,
  } as InputConfig;

  // type 결정 — InvField canonical (kind/type/format) 우선, 옛 inputType/webType 폴백.
  // webType 은 camelCase / snake_case 두 표기 모두 흡수.
  const rawType: any = componentConfig.type;
  const inputType: any = (componentConfig as any).inputType;
  const webType: any =
    (componentConfig as any).webType ?? (componentConfig as any).web_type;
  const fmt: any = (componentConfig as any).format;
  const type: InputFieldType = (() => {
    // ─── InvField (canonical) type 매핑 ─────────────────────────
    // InvField 의 type union 은 text/number/money/date/single/multi/autonum/formula/audit/file.
    // InputFieldType 과 다른 일부는 매핑.
    if (rawType === "money") return "number";       // money → number (currency 표시)
    if (rawType === "single" || rawType === "multi") {
      if (fmt === "boolean") return "checkbox";
      return "select";
    }
    if (rawType === "autonum") return "code";
    if (rawType === "formula") return "text";       // computed readonly
    if (rawType === "audit") return "datetime";     // current_time autoGen
    if (rawType === "date") {
      // InvField 의 date 는 format=date|datetime|time|range 로 sub 분기
      if (fmt === "datetime") return "datetime";
      if (fmt === "time") return "time";
      if (fmt === "range") return "daterange";
      return "date";
    }
    // slider / color — 옛 inputType/webType 진입점. 각각 number/text 로 라우팅 후 format 으로 분기.
    // rawType 이 text/number 같은 유효값으로 같이 들어와도 명시 inputType/webType 을 존중한다.
    if (inputType === "slider" || webType === "slider") return "number";
    if (inputType === "color" || webType === "color") return "text";
    // text/number/file/checkbox 등 InputFieldType 와 동일한 키는 그대로
    if (isValidType(rawType)) return rawType;

    // ─── 옛 inputType / webType 폴백 (점진 폐기 영역) ───────────
    if (isValidType(inputType)) return inputType;
    // (옛 V2 선택 rawType 분기 — Phase D.2 에서 제거. 더 이상 옛 V2 선택 컴포넌트가 생성/렌더되지 않음)
    // V2-era 의 inputType="numbering" → code (autonum 의 옛 키)
    if (inputType === "numbering" || webType === "numbering") return "code";
    if (inputType === "entity") return "entity";
    if (inputType === "select" || inputType === "code" || inputType === "dropdown" || inputType === "radio")
      return "select";
    if (inputType === "number" || inputType === "decimal" || inputType === "currency") return "number";
    if (inputType === "date") return "date";
    if (inputType === "datetime") return "datetime";
    if (inputType === "time") return "time";
    if (inputType === "daterange") return "daterange";
    if (inputType === "textarea") return "textarea";
    if (inputType === "checkbox" || inputType === "boolean") return "checkbox";
    if (inputType === "file" || inputType === "image" || inputType === "img") return "file";
    if (webType === "entity") return "entity";
    if (webType === "select" || webType === "code" || webType === "dropdown" || webType === "radio")
      return "select";
    if (webType === "number" || webType === "decimal") return "number";
    if (webType === "date") return "date";
    if (webType === "datetime") return "datetime";
    if (webType === "time") return "time";
    if (webType === "daterange") return "daterange";
    if (webType === "textarea") return "textarea";
    if (webType === "checkbox" || webType === "boolean") return "checkbox";
    if (webType === "file" || webType === "image" || webType === "img" || webType === "picture" || webType === "photo")
      return "file";
    return "text";
  })();

  const label = componentConfig.label;
  const placeholder = componentConfig.placeholder ?? "";
  const helperText = componentConfig.helperText;
  const required = componentConfig.required ?? false;
  const editable = componentConfig.editable !== false;
  const readonly = componentConfig.readonly ?? !editable;
  const disabled = componentConfig.disabled ?? false;
  const rows = componentConfig.rows ?? 3;

  // 부모가 formData[columnName] 또는 직접 value 를 넘겨주면 controlled. 없으면 로컬 state.
  // 입력 변경 시 부모 콜백(onFormDataChange / onChange) 둘 다 시도해서 BlockRenderer 의
  // formRow 갱신 경로를 끊김 없이 잇는다.
  const formDataProp = (props as any).formData ?? (props as any).form_data;
  const onFormDataChangeProp = (props as any).onFormDataChange;
  const onChangeProp = (props as any).onChange;
  const columnName: string | undefined =
    (component as any).columnName ??
    (component as any).column_name ??
    (componentConfig as any).columnName ??
    (componentConfig as any).column_name;

  // tableName / isEditMode — autonum (code) 채번 hook 용
  // `||` 사용: `??` 는 빈 문자열을 통과시켜 뒤 폴백을 막음.
  const tableName: string | undefined =
    (props as any).tableName ||
    (componentConfig as any).tableName ||
    (component as any).tableName ||
    (component as any).overrides?.tableName ||
    (props as any).screenInfo?.tableName;
  // originalData 와 _originalData (V2-era 별칭) 둘 다 고려
  const originalData = (props as any).originalData || (props as any)._originalData;
  const isEditMode = !!originalData && Object.keys(originalData).length > 0;

  const controlledValue =
    formDataProp && columnName
      ? formDataProp[columnName]
      : (props as any).value !== undefined
        ? (props as any).value
        : undefined;

  // 의미있는 controlled 값 — 빈 문자열 / null 은 "값 없음" 으로 취급 → defaultValue fallback.
  // (ScreenDesigner 가 디자인 모드에서 빈 value 를 넘기는 케이스 방어)
  const hasControlled =
    controlledValue !== undefined && controlledValue !== null && controlledValue !== "";

  const [localValue, setLocalValue] = useState<unknown>(
    hasControlled ? controlledValue : (componentConfig.defaultValue ?? ""),
  );

  useEffect(() => {
    if (hasControlled) {
      setLocalValue((prev: unknown) => (prev === controlledValue ? prev : controlledValue));
    } else if (componentConfig.defaultValue !== undefined && componentConfig.defaultValue !== null) {
      setLocalValue((prev: unknown) =>
        prev === componentConfig.defaultValue ? prev : componentConfig.defaultValue,
      );
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [hasControlled, controlledValue, componentConfig.defaultValue]);

  // value 우선순위:
  //   1) 운영 모드의 controlledValue (formData / props.value)
  //   2) componentConfig.defaultValue (사용자가 ConfigPanel 에 설정한 기본값)
  //   3) localValue (사용자 직접 입력 / 자동생성 결과)
  const dvDefined =
    componentConfig.defaultValue !== undefined &&
    componentConfig.defaultValue !== null &&
    componentConfig.defaultValue !== "";
  const value: unknown = hasControlled
    ? controlledValue
    : (dvDefined ? componentConfig.defaultValue : localValue);



  const propagate = (v: unknown) => {
    setLocalValue(v);
    if (typeof onFormDataChangeProp === "function" && columnName) onFormDataChangeProp(columnName, v);
    if (typeof onChangeProp === "function") onChangeProp(v);
  };

  // ─── 자동생성 hook ──────────────────────────────────────────
  // 디자인 모드 X / 자동생성 enabled / 값 비어있을 때만 trigger.
  // numbering_rule 은 별도 API hook (Phase A.6) — 여기선 skip.
  const autoGen = (componentConfig as any).autoGeneration;
  const autoGenEnabled = !!autoGen?.enabled;
  const autoGenType = autoGen?.type;
  useEffect(() => {
    if (isDesignMode) return;
    if (!autoGenEnabled || !autoGenType || autoGenType === "none") return;
    if (autoGenType === "numbering_rule") return;
    const isEmpty = value === undefined || value === null || value === "";
    if (!isEmpty) return;

    let cancelled = false;
    AutoGenerationUtils.generateValue(autoGen, columnName, formDataProp).then((generated) => {
      if (cancelled || generated == null) return;
      propagate(generated);
    });
    return () => { cancelled = true; };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isDesignMode, autoGenEnabled, autoGenType]);

  // ─── 옵션 loader (select 계열) ──────────────────────────────────────
  // type 이 select 가 아니더라도 hook 은 항상 호출 (React 규칙). source 가 static
  // 이거나 fetch url 이 만들어지지 않으면 hook 내부에서 API 호출 자체 안 일어남.
  // user context — runtime 의 `value_type === "user"` 필터 치환에 사용. props 에서
  // camelCase / snake_case 둘 다 흡수. 값 없으면 user 필터는 hook 내부에서 skip.
  const userContext = {
    companyCode: (props as any).companyCode || (props as any).company_code,
    userId: (props as any).userId || (props as any).user_id,
    deptCode: (props as any).deptCode || (props as any).dept_code,
    userName: (props as any).userName || (props as any).user_name,
  };
  const { options: loadedOptions } = useOptionLoader({
    config: componentConfig as any,
    tableName,
    columnName,
    formData: formDataProp,
    userContext,
    isDesignMode,
  });

  // ─── DOM props filter (React warning 방지) ────────────────────────────
  /* eslint-disable @typescript-eslint/no-unused-vars */
  const {
    selectedScreen: _1,
    onZoneComponentDrop: _2,
    onZoneClick: _3,
    componentConfig: _4,
    component: _5,
    isSelected: _6,
    onClick: _7,
    onDragStart: _8,
    onDragEnd: _9,
    size: _10,
    position: _11,
    style: _12,
    screenId: _13,
    tableName: _14,
    onRefresh: _15,
    onClose: _16,
    web_type: _17,
    webType: _17a,
    autoGeneration: _18,
    isInteractive: _19,
    formData: _20,
    onFormDataChange: _21,
    menuId: _22,
    menuObjid: _23,
    onSave: _24,
    userId: _25,
    userName: _26,
    companyCode: _27,
    deptCode: _27a,
    user_id: _27b,
    user_name: _27c,
    company_code: _27d,
    dept_code: _27e,
    isInModal: _28,
    originalData: _30,
    _originalData: _31,
    _initialData: _32,
    _groupedData: _33,
    allComponents: _34,
    onUpdateLayout: _35,
    selectedRows: _36,
    selectedRowsData: _37,
    onSelectedRowsChange: _38,
    sortBy: _39,
    sortOrder: _40,
    tableDisplayData: _41,
    flowSelectedData: _42,
    flowSelectedStepId: _43,
    onFlowSelectedDataChange: _44,
    onConfigChange: _45,
    refreshKey: _46,
    flowRefreshKey: _47,
    onFlowRefresh: _48,
    isPreview: _49,
    groupedData: _50,
    // ★ InputConfig 필드 — DOM 에 spread 되면 React warning. 제외.
    type: _51,
    label: _52,
    placeholder: _53,
    helperText: _54,
    defaultValue: _55,
    required: _56,
    editable: _57,
    readonly: _58,
    disabled: _59,
    options: _60,
    ref: _61,
    format: _62,
    computed: _63,
    min: _64,
    max: _65,
    step: _66,
    minLength: _67,
    maxLength: _68,
    rows: _69,
    accept: _70,
    multiple: _71,
    mask: _71a,
    // 기타 noise
    columnName: _72,
    fieldKey: _73,
    fieldType: _74,
    inputType: _75,
    value: _76,
    ...domProps
  } = props as any;
  /* eslint-enable @typescript-eslint/no-unused-vars */

  // container 자체가 input box 역할 — border + radius + background.
  // 자식 element 는 자체 border 없이 transparent 로 가득 채움 (이중 박스 방지).
  // 단 radio/check 같은 list 형태는 외각 box 자체 불필요 (자체 visual element 가 표시).
  const selectFormat = (componentConfig as any).format;
  const isMultiSelect =
    type === "select" && (rawType === "multi" || !!(componentConfig as any).multiple);
  const selectMode =
    (componentConfig as any).mode ||
    (isMultiSelect && selectFormat === "list" ? "check" : "dropdown");
  const isListLikeMode = type === "select" && (selectMode === "radio" || selectMode === "check");
  const containerStyle: React.CSSProperties = {
    width: "100%",
    height: "100%",
    display: "flex",
    alignItems: "center",
    boxSizing: "border-box",
    border: isListLikeMode ? "none" : "1px solid hsl(var(--border))",
    borderRadius: isListLikeMode ? 0 : "4px",
    background: isListLikeMode ? "transparent" : (disabled ? "hsl(var(--muted))" : "hsl(var(--card))"),
    position: "relative",
    ...(component as any).style,
    ...style,
  };

  if (isDesignMode && isSelected) {
    containerStyle.outline = "2px solid hsl(var(--primary))";
    containerStyle.outlineOffset = "2px";
  }

  // 위젯 슬롯 — type 별 element 가 들어갈 통일 wrapper. 박스 가득.
  // overflow:hidden + borderRadius:inherit — numbering picker 의 prefix/suffix muted span
  // 이 container 의 라운드 모서리 안에서 깨끗하게 잘리도록. label 은 absolute top:-18
  // 로 container 바깥에 그려져서 영향 없음.
  const inputSlotStyle: React.CSSProperties = {
    flex: 1,
    display: "flex",
    width: "100%",
    height: "100%",
    minHeight: 0,
    overflow: "hidden",
    borderRadius: "inherit",
  };

  // 통일 element style — border/radius 없이 transparent. container 의 border 가 box 역할.
  const baseInputStyle: React.CSSProperties = {
    width: "100%",
    height: "100%",
    padding: "5px 8px",
    fontSize: "13px",
    border: 0,
    borderRadius: 0,
    background: "transparent",
    color: "hsl(var(--foreground))",
    outline: "none",
    boxSizing: "border-box",
  };

  const handleClick = (e: React.MouseEvent) => {
    e.stopPropagation();
    onClick?.();
  };

  // ─── 타입별 입력 위젯 ─────────────────────────────────────────────────
  const renderInput = () => {
    const common = {
      style: baseInputStyle,
      disabled: disabled || isDesignMode,
      readOnly: readonly,
      placeholder,
    };

    switch (type) {
      case "number": {
        const numFmt = (componentConfig as any).format;
        const isSlider =
          numFmt === "slider" || inputType === "slider" || webType === "slider";
        if (isSlider) {
          const minN = typeof componentConfig.min === "number" ? componentConfig.min : 0;
          const maxN = typeof componentConfig.max === "number" ? componentConfig.max : 100;
          const stepN = typeof componentConfig.step === "number" ? componentConfig.step : 1;
          const parsed =
            typeof value === "number"
              ? value
              : typeof value === "string" && value !== ""
                ? Number(value)
                : NaN;
          const sliderVal = Number.isFinite(parsed) ? parsed : minN;
          return (
            <div
              style={{
                display: "flex",
                alignItems: "center",
                gap: 8,
                width: "100%",
                padding: "0 8px",
                boxSizing: "border-box",
              }}
            >
              <input
                type="range"
                min={minN}
                max={maxN}
                step={stepN}
                value={sliderVal}
                onChange={(e) => propagate(e.target.valueAsNumber)}
                disabled={disabled || isDesignMode || readonly}
                style={{ flex: 1, accentColor: "hsl(var(--primary))" }}
              />
              <span
                style={{
                  fontSize: 12,
                  minWidth: 36,
                  textAlign: "right",
                  color: "hsl(var(--foreground))",
                }}
              >
                {sliderVal}
              </span>
            </div>
          );
        }
        return (
          <input
            type="number"
            value={typeof value === "number" || typeof value === "string" ? (value as any) : ""}
            onChange={(e) => propagate(e.target.valueAsNumber)}
            min={componentConfig.min}
            max={componentConfig.max}
            step={componentConfig.step}
            {...common}
          />
        );
      }
      case "date":
        return (
          <SingleDatePicker
            value={typeof value === "string" ? value : ""}
            onChange={(v) => propagate(v)}
            dateFormat={componentConfig.dateFormat || "YYYY-MM-DD"}
            showToday={componentConfig.showToday !== false}
            minDate={componentConfig.minDate}
            maxDate={componentConfig.maxDate}
            disabled={disabled}
            readonly={readonly}
            placeholder={placeholder}
            className="border-0 bg-transparent rounded-none"
          />
        );
      case "datetime":
        return (
          <DateTimePicker
            value={typeof value === "string" ? value : ""}
            onChange={(v) => propagate(v)}
            dateFormat={componentConfig.dateFormat || "YYYY-MM-DD HH:mm"}
            minDate={componentConfig.minDate}
            maxDate={componentConfig.maxDate}
            disabled={disabled}
            readonly={readonly}
            className="border-0 bg-transparent rounded-none"
          />
        );
      case "time":
        return (
          <TimePicker
            value={typeof value === "string" ? value : ""}
            onChange={(v) => propagate(v)}
            disabled={disabled}
            readonly={readonly}
            className="border-0 bg-transparent rounded-none"
          />
        );
      case "daterange":
        return (
          <RangeDatePicker
            value={
              Array.isArray(value) && value.length === 2
                ? (value as [string, string])
                : ["", ""]
            }
            onChange={(v) => propagate(v)}
            dateFormat={componentConfig.dateFormat || "YYYY-MM-DD"}
            minDate={componentConfig.minDate}
            maxDate={componentConfig.maxDate}
            disabled={disabled}
            readonly={readonly}
            className="border-0 bg-transparent rounded-none"
          />
        );
      case "textarea":
        return (
          <textarea
            value={typeof value === "string" ? value : ""}
            onChange={(e) => propagate(e.target.value)}
            rows={rows}
            minLength={componentConfig.minLength}
            maxLength={componentConfig.maxLength}
            {...common}
            style={{ ...baseInputStyle, resize: "vertical" }}
          />
        );
      case "select": {
        // 옵션은 useOptionLoader 가 결정. static / code / category / distinct / api 모두 처리.
        // 로딩 중 / fetch 미발동 (디자인 모드 등) 시에는 static 옵션을 우선 보여준다.
        const normalizedOptions = loadedOptions;
        const isMulti = rawType === "multi" || !!(componentConfig as any).multiple;
        // 기존 옵션 — config.mode (dropdown|combobox|radio|check|tag|swap|toggle)
        // multi + 고정 목록은 패널 설명처럼 체크박스 list 를 기본값으로 둔다.
        const mode = selectMode;
        const searchable = mode === "combobox" || !!(componentConfig as any).searchable;

        // multi 분기
        if (isMulti) {
          const arrValue = Array.isArray(value) ? (value as string[]) : value ? [String(value)] : [];
          // format=tags 또는 mode=tag → TagPicker (chip + 입력)
          const fmt = (componentConfig as any).format;
          if (fmt === "tags" || mode === "tag") {
            return (
              <TagPicker
                value={arrValue}
                onChange={(v) => propagate(v)}
                placeholder={placeholder && placeholder !== "입력하세요" ? placeholder : "태그 입력 후 Enter"}
                maxSelect={(componentConfig as any).maxSelect ?? (componentConfig as any).maxTags}
                separator={(componentConfig as any).tagSeparator || ","}
                disabled={disabled}
                readonly={readonly}
                className="border-0 bg-transparent rounded-none"
              />
            );
          }
          if (mode === "check") {
            return (
              <CheckboxListPicker
                value={arrValue}
                onChange={(v) => propagate(v)}
                options={normalizedOptions}
                maxSelect={(componentConfig as any).maxSelect}
                disabled={disabled}
                readonly={readonly}
                className="border-0 bg-transparent rounded-none"
              />
            );
          }
          if (mode === "swap") {
            return (
              <SwapPicker
                value={arrValue}
                onChange={(v) => propagate(v)}
                options={normalizedOptions}
                maxSelect={(componentConfig as any).maxSelect}
                disabled={disabled}
                readonly={readonly}
                className="border-0 bg-transparent rounded-none"
              />
            );
          }
          // dropdown / combobox (기본) — MultiSelectPicker
          return (
            <MultiSelectPicker
              value={arrValue}
              onChange={(v) => propagate(v)}
              options={normalizedOptions}
              placeholder={placeholder || "선택"}
              searchable={searchable}
              allowClear={!!(componentConfig as any).allowClear}
              maxSelect={(componentConfig as any).maxSelect}
              disabled={disabled}
              readonly={readonly}
              className="border-0 bg-transparent rounded-none"
            />
          );
        }

        // single 분기
        if (mode === "radio") {
          return (
            <RadioPicker
              value={typeof value === "string" ? value : ""}
              onChange={(v) => propagate(v)}
              options={normalizedOptions}
              disabled={disabled}
              readonly={readonly}
              className="border-0 bg-transparent rounded-none"
            />
          );
        }
        // dropdown / combobox (기본) — SingleSelectPicker
        return (
          <SingleSelectPicker
            value={typeof value === "string" ? value : ""}
            onChange={(v) => propagate(v)}
            options={normalizedOptions}
            placeholder={placeholder || "선택"}
            searchable={searchable}
            allowClear={!!(componentConfig as any).allowClear}
            disabled={disabled || isDesignMode}
            readonly={readonly}
            className="border-0 bg-transparent rounded-none"
          />
        );
      }
      case "checkbox": {
        // single.boolean 영역. canonical input 이 직접 switch / checkbox / yes-no 버튼을 렌더한다.
        const cbMode = (componentConfig as any).mode;
        const boolStyle = (componentConfig as any).boolStyle || (cbMode === "toggle" ? "switch" : "checkbox");
        const trueLabel = (componentConfig as any).trueLabel || "예";
        const falseLabel = (componentConfig as any).falseLabel || "아니오";
        const checked =
          value === true ||
          value === 1 ||
          (typeof value === "string" && ["true", "y", "yes", "1"].includes(value.toLowerCase()));
        const lockEdit = disabled || readonly;

        if (boolStyle === "switch" || cbMode === "toggle") {
          return (
            <TogglePicker
              value={value}
              onChange={(v) => propagate(v)}
              trueLabel={trueLabel}
              falseLabel={falseLabel}
              disabled={disabled}
              readonly={readonly}
              className="border-0 bg-transparent rounded-none"
            />
          );
        }
        if (boolStyle === "yesno") {
          const buttonBase: React.CSSProperties = {
            minWidth: "52px",
            height: "28px",
            borderRadius: "6px",
            border: "1px solid var(--input-border, rgba(148, 163, 184, 0.35))",
            fontSize: "13px",
            cursor: lockEdit ? "not-allowed" : "pointer",
            opacity: lockEdit ? 0.6 : 1,
          };
          return (
            <div style={{ display: "flex", alignItems: "center", gap: 6, padding: "0 8px", height: "100%" }}>
              <button
                type="button"
                disabled={lockEdit}
                onClick={() => !lockEdit && propagate(true)}
                style={{
                  ...buttonBase,
                  background: checked ? "var(--primary, #3b82f6)" : "transparent",
                  color: checked ? "var(--primary-foreground, #ffffff)" : "inherit",
                }}
              >
                {trueLabel}
              </button>
              <button
                type="button"
                disabled={lockEdit}
                onClick={() => !lockEdit && propagate(false)}
                style={{
                  ...buttonBase,
                  background: !checked ? "var(--primary, #3b82f6)" : "transparent",
                  color: !checked ? "var(--primary-foreground, #ffffff)" : "inherit",
                }}
              >
                {falseLabel}
              </button>
            </div>
          );
        }
        return (
          <label
            style={{
              display: "inline-flex",
              alignItems: "center",
              gap: "6px",
              fontSize: "13px",
              padding: "0 8px",
              cursor: lockEdit ? "not-allowed" : "pointer",
              opacity: lockEdit ? 0.7 : 1,
            }}
          >
            <input
              type="checkbox"
              checked={checked}
              onChange={(e) => propagate(e.target.checked)}
              disabled={lockEdit}
            />
            <span>{checked ? trueLabel : falseLabel}</span>
          </label>
        );
      }
      case "entity": {
        // entity 는 검색 모달이 아니라 참조 테이블의 value/label 컬럼을 읽는
        // code-name 선택형이다. 명시 검색 UI 는 entity-search-input 쪽 책임.
        const entityOptions = loadedOptions;
        const entityMode = (componentConfig as any).mode || "combobox";
        const entitySearchable =
          entityMode === "combobox" || (componentConfig as any).searchable === true;
        const isEntityMulti = !!(componentConfig as any).multiple;
        if (isEntityMulti) {
          const arrValue = Array.isArray(value) ? (value as string[]) : value ? [String(value)] : [];
          if (entityMode === "check") {
            return (
              <CheckboxListPicker
                value={arrValue}
                onChange={(v) => propagate(v)}
                options={entityOptions}
                maxSelect={(componentConfig as any).maxSelect}
                disabled={disabled}
                readonly={readonly}
                className="border-0 bg-transparent rounded-none"
              />
            );
          }
          if (entityMode === "swap") {
            return (
              <SwapPicker
                value={arrValue}
                onChange={(v) => propagate(v)}
                options={entityOptions}
                maxSelect={(componentConfig as any).maxSelect}
                disabled={disabled}
                readonly={readonly}
                className="border-0 bg-transparent rounded-none"
              />
            );
          }
          return (
            <MultiSelectPicker
              value={arrValue}
              onChange={(v) => propagate(v)}
              options={entityOptions}
              placeholder={placeholder || "선택"}
              searchable={entitySearchable}
              allowClear={!!(componentConfig as any).allowClear}
              maxSelect={(componentConfig as any).maxSelect}
              disabled={disabled}
              readonly={readonly}
              className="border-0 bg-transparent rounded-none"
            />
          );
        }
        return (
          <SingleSelectPicker
            value={typeof value === "string" ? value : value != null ? String(value) : ""}
            onChange={(v) => propagate(v)}
            options={entityOptions}
            placeholder={placeholder || "선택"}
            searchable={entitySearchable}
            allowClear={!!(componentConfig as any).allowClear}
            disabled={disabled || isDesignMode}
            readonly={readonly}
            className="border-0 bg-transparent rounded-none"
          />
        );
      }
      case "file": {
        // InvField format: "image" / "file" / "doc". image 이면 자동 preview + image/* accept.
        // accept 가 명시되었으면 우선, 아니면 format 으로 기본값 결정.
        const fmt = (componentConfig as any).format;
        const isImageFormat = fmt === "image";
        const defaultAccept = isImageFormat
          ? "image/*"
          : fmt === "doc"
            ? ".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
            : "*/*";
        const acceptValue = componentConfig.accept || defaultAccept;
        const showPreview =
          (componentConfig as any).showPreview ?? (isImageFormat || acceptValue.startsWith("image/"));
        return (
          <FilePicker
            value={value as any}
            onChange={(v) => propagate(v)}
            accept={acceptValue}
            multiple={!!componentConfig.multiple}
            maxFiles={(componentConfig as any).maxFiles}
            disabled={disabled}
            readonly={readonly}
            placeholder={placeholder}
            showPreview={!!showPreview}
            className="border-0 bg-transparent rounded-none"
          />
        );
      }
      case "code":
        return (
          <NumberingPicker
            value={value}
            onChange={(v) => propagate(v)}
            tableName={tableName}
            columnName={columnName}
            formData={formDataProp}
            numberingRuleId={autoGen?.options?.numberingRuleId}
            isEditMode={isEditMode}
            isDesignMode={isDesignMode}
            disabled={disabled}
            readonly={readonly}
            placeholder={placeholder}
            className="border-0 bg-transparent rounded-none"
            onRuleIdResolved={(ruleId) => {
              // EditModal / buttonActions 의 `${columnName}_numberingRuleId` 메타 키 호환
              if (typeof onFormDataChangeProp === "function" && columnName) {
                onFormDataChangeProp(`${columnName}_numberingRuleId`, ruleId);
              }
            }}
          />
        );
      case "text":
      default: {
        // InvField format 별 native input type 분기 (password/email/tel/url/color)
        const f = (componentConfig as any).format;
        const isColor =
          f === "color" || inputType === "color" || webType === "color";
        if (isColor) {
          const rawStr = typeof value === "string" ? value : "";
          const colorVal = normalizeHex(rawStr);
          return (
            <div
              style={{
                display: "flex",
                alignItems: "center",
                gap: 6,
                width: "100%",
                padding: "0 8px",
                boxSizing: "border-box",
              }}
            >
              <input
                type="color"
                value={colorVal}
                onChange={(e) => propagate(e.target.value)}
                disabled={disabled || isDesignMode || readonly}
                style={{
                  width: 28,
                  height: 22,
                  padding: 0,
                  border: 0,
                  background: "transparent",
                  cursor: disabled || readonly ? "not-allowed" : "pointer",
                }}
              />
              <input
                type="text"
                value={rawStr}
                onChange={(e) => propagate(sanitizeHexInput(e.target.value))}
                placeholder={placeholder || "#000000"}
                maxLength={7}
                disabled={disabled || isDesignMode}
                readOnly={readonly}
                style={{ ...baseInputStyle, flex: 1, padding: "0 4px" }}
              />
            </div>
          );
        }
        const maskPattern: string | undefined =
          typeof componentConfig.mask === "string" && componentConfig.mask
            ? componentConfig.mask
            : undefined;
        const inputHtmlType =
          f === "password" ? "password" :
          f === "email" ? "email" :
          f === "phone" || f === "tel" ? "tel" :
          f === "url" ? "url" :
          "text";
        return (
          <input
            type={inputHtmlType}
            value={typeof value === "string" ? value : ""}
            onChange={(e) => {
              const raw = e.target.value;
              const next = maskPattern ? applyInputMask(raw, maskPattern) : raw;
              propagate(next);
            }}
            minLength={componentConfig.minLength}
            maxLength={componentConfig.maxLength}
            {...common}
          />
        );
      }
    }
  };

  return (
    <div
      style={containerStyle}
      className={className}
      onClick={handleClick}
      onDragStart={onDragStart}
      onDragEnd={onDragEnd}
      {...filterDOMProps(domProps)}
    >
      {label && (
        <label
          style={{
            position: "absolute",
            top: "-18px",
            left: 0,
            fontSize: "11px",
            fontWeight: 600,
            color: "hsl(var(--foreground))",
            display: "flex",
            alignItems: "center",
            gap: "3px",
            whiteSpace: "nowrap",
            pointerEvents: "none",
          }}
        >
          {label}
          {required && <span style={{ color: "hsl(var(--destructive))" }}>*</span>}
        </label>
      )}
      <div style={inputSlotStyle}>{renderInput()}</div>
      {helperText && (
        <span
          style={{
            fontSize: "10px",
            color: "hsl(var(--muted-foreground))",
          }}
        >
          {helperText}
        </span>
      )}
    </div>
  );
};

export const InputWrapper: React.FC<InputComponentProps> = (props) => {
  return <InputComponent {...props} />;
};
