import React, { ComponentProps, forwardRef, useRef } from 'react';

import { getNumberInRange } from '@/modules/@common/helpers/number';

import TextField from '@/mui/components/TextField/TextField';

type Props = ComponentProps<typeof TextField> & {
  value: number | string;
  min?: number;
  max?: number;
  step?: number;
  allowFloat?: boolean;
  controls?: boolean;
  /**
   * Maximum number of decimal places allowed.
   * Limited to 10 due to JavaScript's floating-point precision limitations.
   */
  maxDecimals?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
};

const NumberField = forwardRef<HTMLInputElement, Props>(
  (
    {
      value,
      onChange,
      min = Number.MIN_SAFE_INTEGER,
      max = Number.MAX_SAFE_INTEGER,
      step = 1,
      allowFloat = false,
      maxDecimals = 10,
      disabled = false,
      ...props
    },
    ref,
  ) => {
    const innerRef = useRef<HTMLInputElement | null>(null);

    const validStep = allowFloat ? Math.max(10 ** -maxDecimals, step) : Math.max(1, Math.round(step));

    const enforceMaxDecimals = (inputValue: string) => {
      if (allowFloat) {
        const [integerPart, decimalPart] = inputValue.split('.');

        if (decimalPart && decimalPart.length > maxDecimals) {
          return `${integerPart}.${decimalPart.substring(0, maxDecimals)}`;
        }
      }
      return inputValue;
    };

    const handleChangeByDelta = (delta: number) => {
      if (!innerRef.current) {
        return;
      }

      const numericValue = parseFloat(value as string) || 0;
      const newValue = parseFloat((numericValue + delta * validStep).toFixed(maxDecimals));

      const clampedValue = getNumberInRange(newValue, min, max);
      const enforcedValue = enforceMaxDecimals(clampedValue.toString());

      const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;

      if (nativeInputValueSetter) {
        nativeInputValueSetter.call(innerRef.current, enforcedValue);
      }

      innerRef.current.dispatchEvent(new Event('input', { bubbles: true }));
    };

    return (
      <TextField
        {...props}
        disabled={disabled}
        value={value}
        multiline={false}
        inputRef={(node) => {
          if (typeof ref === 'function') {
            ref(node);
          } else if (ref && 'current' in ref) {
            ref.current = node;
          }

          innerRef.current = node;
        }}
        inputProps={{
          ...props.inputProps,
          inputMode: allowFloat ? 'decimal' : 'numeric',
          pattern: allowFloat ? '[0-9]*\\.?[0-9]*' : '[0-9]*',
          min,
          max,
          step: validStep,
          autoCorrect: 'off',
          autoComplete: 'off',
          spellCheck: 'false',
        }}
        onKeyDown={(event: React.KeyboardEvent<HTMLInputElement>) => {
          const allowedKeys = [
            'Backspace',
            'Delete',
            'Tab',
            'Enter',
            'Escape',
            'ArrowLeft',
            'ArrowRight',
            'ArrowUp',
            'ArrowDown',
          ];

          if (allowFloat) {
            allowedKeys.push('.');
          }

          if (event.ctrlKey || event.metaKey) {
            return;
          }

          if (event.key === '-') {
            if (innerRef.current?.selectionStart !== 0 || min >= 0) {
              event.preventDefault();
            }
            return;
          }

          if (event.key === 'ArrowUp') {
            handleChangeByDelta(1);
          }

          if (event.key === 'ArrowDown') {
            handleChangeByDelta(-1);
          }

          if (!allowedKeys.includes(event.key) && !/^[0-9]$/.test(event.key)) {
            event.preventDefault();
          }
        }}
        onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
          let inputValue = event.target.value;

          if (inputValue.startsWith('-') && min < 0) {
            inputValue = `-${inputValue.slice(1).replace(/[^0-9.]/g, '')}`;
          } else {
            inputValue = inputValue.replace(/[^0-9.]/g, '');
          }

          if (inputValue) {
            const [mainPart, ...decimalParts] = inputValue.split('.');
            inputValue = allowFloat && decimalParts.length > 0 ? `${mainPart}.${decimalParts.join('')}` : mainPart;

            const numericValue = allowFloat ? parseFloat(inputValue) : parseInt(inputValue, 10);

            if (!Number.isNaN(numericValue)) {
              if (numericValue < min) {
                inputValue = min.toString();
              } else if (numericValue > max) {
                inputValue = max.toString();
              } else {
                inputValue = enforceMaxDecimals(inputValue);
              }
            }
          }

          if (event.target.value !== inputValue) {
            event.target.value = inputValue;
          }

          if (onChange) {
            onChange(event);
          }
        }}
      />
    );
  },
);

export default NumberField;
