import React, { useState, useEffect, useRef, useCallback } from 'react';
import type { NumericParamSpec } from '../utils/indicatorParamSpec';

interface NumericParamInputProps {
  value: number;
  spec: NumericParamSpec;
  disabled?: boolean;
  className?: string;
  onChange: (v: number) => void;
}

function formatOption(v: number, decimal: boolean): string {
  return decimal
    ? (Number.isInteger(v) ? String(v) : v.toFixed(2).replace(/\.?0+$/, ''))
    : String(Math.round(v));
}

/** 입력 문자열을 숫자만(선택적 -, 소수점) 허용하도록 필터 */
function filterTyping(raw: string, allowNegative: boolean, decimal: boolean): string {
  let s = raw.replace(/[^\d.\-]/g, '');
  if (!allowNegative) {
    s = s.replace(/-/g, '');
  } else {
    const first = s.startsWith('-') ? '-' : '';
    s = first + s.slice(first ? 1 : 0).replace(/-/g, '');
  }
  if (!decimal) {
    s = s.replace(/\./g, '');
  } else {
    const parts = s.replace('-', '').split('.');
    if (parts.length > 2) {
      s = (s.startsWith('-') ? '-' : '') + parts[0] + '.' + parts.slice(1).join('');
    }
  }
  return s;
}

function isPartialNumber(s: string, allowNegative: boolean, decimal: boolean): boolean {
  if (s === '' || s === '-') return true;
  if (decimal && (s === '.' || s === '-.')) return true;
  if (decimal) {
    return allowNegative ? /^-?\d*\.?\d*$/.test(s) : /^\d*\.?\d*$/.test(s);
  }
  return allowNegative ? /^-?\d*$/.test(s) : /^\d*$/.test(s);
}

const NumericParamInput: React.FC<NumericParamInputProps> = ({
  value,
  spec,
  disabled = false,
  className = 'ism-input',
  onChange,
}) => {
  const wrapRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);
  const [str, setStr] = useState(() => formatOption(value, spec.decimal));
  const [open, setOpen] = useState(false);
  const prevValueRef = useRef(value);
  const editingRef = useRef(false);

  useEffect(() => {
    if (editingRef.current) return;
    if (value !== prevValueRef.current) {
      prevValueRef.current = value;
      setStr(formatOption(value, spec.decimal));
    }
  }, [value, spec.decimal]);

  useEffect(() => {
    if (!open) return;
    const onDoc = (e: MouseEvent) => {
      if (wrapRef.current?.contains(e.target as Node)) return;
      setOpen(false);
    };
    const tid = window.setTimeout(() => {
      document.addEventListener('mousedown', onDoc, true);
    }, 0);
    return () => {
      window.clearTimeout(tid);
      document.removeEventListener('mousedown', onDoc, true);
    };
  }, [open]);

  const commit = useCallback((raw: string) => {
    const trimmed = raw.trim();
    if (trimmed === '' || trimmed === '-' || trimmed === '-.') {
      setStr(formatOption(prevValueRef.current, spec.decimal));
      return;
    }
    const v = parseFloat(trimmed);
    if (isNaN(v)) {
      setStr(formatOption(prevValueRef.current, spec.decimal));
      return;
    }
    let clamped = Math.max(spec.min, Math.min(spec.max, v));
    if (!spec.decimal) clamped = Math.round(clamped);
    else clamped = roundTo(clamped, spec.step < 1 ? 2 : 1);
    prevValueRef.current = clamped;
    onChange(clamped);
    setStr(formatOption(clamped, spec.decimal));
  }, [spec, onChange]);

  const handleChange = (raw: string) => {
    const filtered = filterTyping(raw, spec.allowNegative, spec.decimal);
    if (!isPartialNumber(filtered, spec.allowNegative, spec.decimal)) return;
    setStr(filtered);
  };

  const selectOption = (opt: number) => {
    prevValueRef.current = opt;
    onChange(opt);
    setStr(formatOption(opt, spec.decimal));
    setOpen(false);
    inputRef.current?.focus();
  };

  const toggleList = () => {
    if (disabled) return;
    setOpen(prev => !prev);
    inputRef.current?.focus();
  };

  return (
    <div
      className="ism-num-combo"
      ref={wrapRef}
      onMouseDown={e => e.stopPropagation()}
      onPointerDown={e => e.stopPropagation()}
    >
      <input
        ref={inputRef}
        type="text"
        inputMode={spec.decimal ? 'decimal' : 'numeric'}
        className={className}
        value={str}
        disabled={disabled}
        autoComplete="off"
        spellCheck={false}
        onChange={e => handleChange(e.target.value)}
        onFocus={() => {
          editingRef.current = true;
        }}
        onBlur={e => {
          const related = e.relatedTarget as Node | null;
          window.setTimeout(() => {
            if (document.activeElement === inputRef.current) return;
            if (related && wrapRef.current?.contains(related)) return;
            editingRef.current = false;
            commit(e.target.value);
            setOpen(false);
          }, 0);
        }}
        onKeyDown={e => {
          if (e.key === 'ArrowDown' && !open) {
            e.preventDefault();
            if (!disabled) setOpen(true);
            return;
          }
          if (e.key === 'Enter') {
            commit((e.target as HTMLInputElement).value);
            setOpen(false);
          }
          if (e.key === 'Escape') setOpen(false);
        }}
      />
      <button
        type="button"
        className="ism-num-combo-toggle"
        tabIndex={-1}
        disabled={disabled}
        aria-label="기간 목록"
        aria-expanded={open}
        onMouseDown={e => {
          e.preventDefault();
          toggleList();
        }}
      >
        ▾
      </button>
      {open && !disabled && (
        <ul className="ism-num-combo-list" role="listbox">
          {spec.options.map(opt => (
            <li key={opt}>
              <button
                type="button"
                className={`ism-num-combo-opt${opt === value ? ' selected' : ''}`}
                onMouseDown={e => {
                  e.preventDefault();
                  selectOption(opt);
                }}
              >
                {formatOption(opt, spec.decimal)}
              </button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
};

function roundTo(v: number, decimals: number): number {
  const f = 10 ** decimals;
  return Math.round(v * f) / f;
}

export default NumericParamInput;
