import { ReactNode, forwardRef, useCallback, useEffect, useRef } from 'react';

import { assertEmptyObject } from '../../utils/assertEmptyObject';
import { setForwarderRef } from '../../utils/setForwarderRef';
import { BaseTextFieldProps } from '../BaseTextField/BaseTextField';
import { BaseTextFieldType } from '../BaseTextField/constants';

import { TunedBaseTextField } from './styled';

export interface NumberFieldProps extends Omit<BaseTextFieldProps<number | null>, 'type' | 'suffix'> {
  /**
   * Element that will be appended to {@link NumberField} as prefix.
   * Should be only {@link FieldPrefixIcon}.
   */
  prefix?: ReactNode;
}

/**
 * Field that allow typing some numbers.
 *
 * ```tsx
 * import { NumberField } from 'ui-kit';
 *
 * <NumberField value="any text" onChange={console.log} />
 * ```
 */
export const NumberField = forwardRef<HTMLInputElement, NumberFieldProps>((props, forwardedRef) => {
  const {
    value,
    onChange,
    className,
    ariaInvalid,
    ariaDescribedBy,
    ariaLabel,
    ariaErrorMessage,
    disabled,
    placeholder,
    onBlur,
    id,
    prefix,
    ariaRequired,
    required,
    testId,
    ...rest
  } = props;
  assertEmptyObject(rest);

  const inputRef = useRef<HTMLInputElement | null>();

  const setRef = useCallback(
    (ref: HTMLInputElement) => {
      inputRef.current = ref;
      setForwarderRef(forwardedRef, ref);
    },
    [inputRef, forwardedRef],
  );

  const getStep = (shift: boolean) => (shift ? 10 : 1);

  const handleChange = useCallback(
    (changedValue: string) => {
      onChange?.(changedValue !== '' ? Number(changedValue) : null);
    },
    [onChange],
  );

  const handleStep = useCallback(
    (method: 'stepUp' | 'stepDown') => (steps: number) => {
      const input = inputRef.current;
      /* istanbul ignore else */
      if (input) {
        input[method](steps);
        handleChange(input.value);
      }
    },
    [inputRef, handleChange],
  );

  const handleStepUp = useCallback((steps: number) => handleStep('stepUp')(steps), [handleStep]);
  const handleStepDown = useCallback((steps: number) => handleStep('stepDown')(steps), [handleStep]);

  useEffect(() => {
    const input = inputRef.current;

    /* istanbul ignore next */
    if (!input) {
      return;
    }

    const handleKeyDown = (event: KeyboardEvent) => {
      const step = getStep(event.shiftKey);

      switch (event.key) {
        case 'ArrowUp':
          event.preventDefault();
          handleStepUp(step);
          break;
        case 'ArrowDown':
          event.preventDefault();
          handleStepDown(step);
          break;
        default:
      }
    };

    input.addEventListener('keydown', handleKeyDown);
    return () => {
      input.removeEventListener('keydown', handleKeyDown);
    };
  }, [inputRef, handleStepUp, handleStepDown]);

  return (
    <TunedBaseTextField
      ref={setRef}
      ariaDescribedBy={ariaDescribedBy}
      ariaErrorMessage={ariaErrorMessage}
      ariaInvalid={ariaInvalid}
      ariaLabel={ariaLabel}
      ariaRequired={ariaRequired}
      className={className}
      disabled={disabled}
      id={id}
      onBlur={onBlur}
      onChange={handleChange}
      placeholder={placeholder}
      prefix={prefix}
      required={required}
      testId={testId}
      type={BaseTextFieldType.Number}
      value={Number.isFinite(value) ? (value as number).toString() : ''}
    />
  );
});
