/**
 * InputNumber — 数字输入框
 * @prop {'default'|'error'} [status='default'] — 状态
 * @prop {boolean} [disabled=false] — 是否禁用
 * @prop {boolean} [innerButtons=false] — 步进按钮是否内嵌到输入框右侧
 * @prop {boolean} [controls=true] — 是否显示步进按钮
 * @prop {number} [min] — 最小值
 * @prop {number} [max] — 最大值
 * @prop {number} [step=1] — 步长
 * @prop {number} [precision] — 小数位
 * @prop {string} [placeholder='请输入'] — 占位
 * @prop {number|string} value — 当前值（受控）
 * @prop {number|string} [defaultValue] — 非受控初值
 * @prop {Function} [onChange=null] — 变更回调，参数为 nextValue
 * @prop {Function} [onStep=null] — 点击步进按钮回调
 * @prop {string} [className=''] — 额外类名
 */

import { useCallback, useMemo, useState } from 'react';

const WRAPPER_BASE = [
  'group/input-number relative inline-flex w-full min-w-0 max-w-full items-center',
  '[font-family:inherit]',
].join(' ');

const FIELD_BASE = [
  'relative inline-flex h-[36px] min-w-0 flex-1 items-center overflow-hidden',
  'rounded-md border border-solid transition-colors tfds-motion-ui-fast',
].join(' ');

const STATUS_CLASS = {
  default: [
    'border-border bg-surface',
    'hover:border-blueGrey-400',
    'focus-within:border-primary focus-within:hover:border-primary',
  ].join(' '),
  error: [
    'border-border bg-red-50',
    'hover:bg-red-100',
    'focus-within:border-red-500 focus-within:hover:border-red-500 focus-within:hover:bg-red-50',
  ].join(' '),
};

const FIELD_DISABLED = 'border-border bg-disabled opacity-60 cursor-not-allowed';

const FIELD_SIZE_CLASS = 'h-[36px] pl-3 pr-2 text-sm';

const INPUT_BASE = [
  'min-w-0 flex-1 border-none bg-transparent p-0 m-0 outline-none',
  'text-foreground placeholder:text-foreground-muted',
  'focus:placeholder:text-foreground-disabled',
  '[font-family:inherit] [font-size:inherit] leading-[20px]',
  'disabled:cursor-not-allowed disabled:text-foreground-disabled disabled:placeholder:text-foreground-disabled',
  '[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none',
].join(' ');

const ARROW_STACK_BASE = [
  'shrink-0 overflow-hidden rounded-md border border-solid border-border bg-surface',
  'text-foreground-muted transition-colors tfds-motion-ui-fast',
].join(' ');

const ARROW_STACK_DISABLED = 'bg-disabled text-foreground-disabled opacity-60';
const OUTER_ARROWS = 'ml-1 flex h-[36px] w-[16px] flex-col';
const INNER_ARROWS = [
  'absolute bottom-[2px] right-px top-[2px] flex w-[16px] flex-col',
  'opacity-0 group-hover/input-number:opacity-100 group-focus-within/input-number:opacity-100',
].join(' ');
const ARROW_BUTTON = [
  'flex min-h-0 flex-1 items-center justify-center border-none bg-transparent p-0',
  'text-current hover:bg-fill disabled:cursor-not-allowed disabled:hover:bg-transparent',
].join(' ');

function normalizeNumber(raw) {
  if (raw === '' || raw === null || raw === undefined) return null;
  const next = Number(raw);
  return Number.isFinite(next) ? next : null;
}

function clampNumber(next, min, max) {
  if (next === null) return null;
  let value = next;
  if (typeof min === 'number') value = Math.max(min, value);
  if (typeof max === 'number') value = Math.min(max, value);
  return value;
}

function getPrecision(step, precision) {
  if (typeof precision === 'number') return precision;
  const decimal = String(step).split('.')[1];
  return decimal ? decimal.length : 0;
}

function formatNumber(next, precision) {
  if (next === null) return '';
  if (precision > 0) return Number(next.toFixed(precision));
  return next;
}

function ChevronUpIcon() {
  return (
    <svg viewBox="0 0 8 8" fill="none" className="size-[8px]" aria-hidden="true">
      <path d="M1.5 5L4 2.5L6.5 5" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

function ChevronDownIcon() {
  return (
    <svg viewBox="0 0 8 8" fill="none" className="size-[8px]" aria-hidden="true">
      <path d="M1.5 3L4 5.5L6.5 3" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

function StepControls({ disabled, placement, onStep }) {
  const stackClass = [
    ARROW_STACK_BASE,
    placement === 'inner' ? INNER_ARROWS : OUTER_ARROWS,
    disabled ? ARROW_STACK_DISABLED : '',
  ].filter(Boolean).join(' ');

  return (
    <span className={stackClass} aria-hidden={disabled}>
      <button
        type="button"
        className={ARROW_BUTTON}
        disabled={disabled}
        onMouseDown={(e) => e.preventDefault()}
        onClick={() => onStep(1)}
        tabIndex={-1}
        aria-label="增加"
      >
        <ChevronUpIcon />
      </button>
      <button
        type="button"
        className={ARROW_BUTTON}
        disabled={disabled}
        onMouseDown={(e) => e.preventDefault()}
        onClick={() => onStep(-1)}
        tabIndex={-1}
        aria-label="减少"
      >
        <ChevronDownIcon />
      </button>
    </span>
  );
}

export default function InputNumber({
  size: _size = 'md',
  status = 'default',
  disabled = false,
  innerButtons = false,
  controls = true,
  min,
  max,
  step = 1,
  precision,
  placeholder = '请输入',
  value,
  defaultValue,
  onChange,
  onStep,
  className = '',
  ...rest
}) {
  const isControlled = value !== undefined;
  const [innerValue, setInnerValue] = useState(defaultValue ?? '');
  const currentValue = isControlled ? value : innerValue;
  const resolvedPrecision = useMemo(() => getPrecision(step, precision), [step, precision]);

  const emitChange = useCallback((nextValue, meta) => {
    if (!isControlled) setInnerValue(nextValue);
    onChange?.(nextValue, meta);
  }, [isControlled, onChange]);

  const stepBy = useCallback((direction) => {
    if (disabled) return;
    const currentNumber = normalizeNumber(currentValue) ?? 0;
    const stepped = currentNumber + direction * Number(step || 1);
    const clamped = clampNumber(stepped, min, max);
    const nextValue = formatNumber(clamped, resolvedPrecision);
    emitChange(nextValue, { type: direction > 0 ? 'up' : 'down' });
    onStep?.(nextValue, { type: direction > 0 ? 'up' : 'down' });
  }, [currentValue, disabled, emitChange, max, min, onStep, resolvedPrecision, step]);

  const handleInputChange = useCallback((event) => {
    const raw = event.target.value;
    if (raw === '' || raw === '-' || raw === '.' || raw === '-.') {
      emitChange(raw, { event });
      return;
    }
    const parsed = normalizeNumber(raw);
    if (parsed === null) return;
    const nextValue = formatNumber(clampNumber(parsed, min, max), resolvedPrecision);
    emitChange(nextValue, { event });
  }, [emitChange, max, min, resolvedPrecision]);

  const wrapperClass = [
    WRAPPER_BASE,
    className,
  ].filter(Boolean).join(' ');

  const fieldClass = [
    FIELD_BASE,
    disabled ? FIELD_DISABLED : STATUS_CLASS[status] || STATUS_CLASS.default,
    FIELD_SIZE_CLASS,
  ].filter(Boolean).join(' ');

  const inputClass = [
    INPUT_BASE,
    controls && innerButtons ? 'pr-6' : '',
  ].filter(Boolean).join(' ');

  return (
    <div className={wrapperClass}>
      <div className={[`tfds-input-number`, fieldClass].filter(Boolean).join(' ')} data-tfds-component="InputNumber">
        <input
          type="text"
          inputMode="decimal"
          role="spinbutton"
          aria-valuemin={min}
          aria-valuemax={max}
          aria-valuenow={normalizeNumber(currentValue) ?? undefined}
          disabled={disabled}
          placeholder={placeholder}
          className={inputClass}
          value={currentValue ?? ''}
          onChange={handleInputChange}
          {...rest}
        />
        {controls && innerButtons ? (
          <StepControls disabled={disabled} placement="inner" onStep={stepBy} />
        ) : null}
      </div>
      {controls && !innerButtons ? (
        <StepControls disabled={disabled} placement="outer" onStep={stepBy} />
      ) : null}
    </div>
  );
}
