import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
import _typeof from "@babel/runtime/helpers/esm/typeof";
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
// @ts-nocheck
import * as React from 'react';
import classNames from 'classnames';
import KeyCode from '../rc-util/KeyCode';
import { composeRef } from '../rc-util/ref';
import getMiniDecimal, { toFixed } from './utils/MiniDecimal';
import StepHandler from './StepHandler';
import { getNumberPrecision, num2str, validateNumber } from './utils/numberUtil';
import useCursor from './hooks/useCursor';
import useUpdateEffect from './hooks/useUpdateEffect';
import useFrame from './hooks/useFrame';
/**
 * We support `stringMode` which need handle correct type when user call in onChange
 */

var getDecimalValue = function getDecimalValue(stringMode, decimalValue) {
  if (stringMode || decimalValue.isEmpty()) {
    return decimalValue.toString();
  }

  return decimalValue.toNumber();
};

var getDecimalIfValidate = function getDecimalIfValidate(value) {
  var decimal = getMiniDecimal(value);
  return decimal.isInvalidate() ? null : decimal;
};

var InputNumber = /*#__PURE__*/React.forwardRef(function (props, ref) {
  var _classNames;

  var _props$prefixCls = props.prefixCls,
      prefixCls = _props$prefixCls === void 0 ? 'rc-input-number' : _props$prefixCls,
      className = props.className,
      style = props.style,
      min = props.min,
      max = props.max,
      _props$step = props.step,
      step = _props$step === void 0 ? 1 : _props$step,
      defaultValue = props.defaultValue,
      value = props.value,
      disabled = props.disabled,
      readOnly = props.readOnly,
      upHandler = props.upHandler,
      downHandler = props.downHandler,
      keyboard = props.keyboard,
      _props$controls = props.controls,
      controls = _props$controls === void 0 ? true : _props$controls,
      _props$floor = props.floor,
      floor = _props$floor === void 0 ? false : _props$floor,
      stringMode = props.stringMode,
      parser = props.parser,
      formatter = props.formatter,
      precision = props.precision,
      decimalSeparator = props.decimalSeparator,
      onChange = props.onChange,
      onInput = props.onInput,
      onPressEnter = props.onPressEnter,
      onStep = props.onStep,
      inputProps = _objectWithoutProperties(props, ["prefixCls", "className", "style", "min", "max", "step", "defaultValue", "value", "disabled", "readOnly", "upHandler", "downHandler", "keyboard", "controls", "floor", "stringMode", "parser", "formatter", "precision", "decimalSeparator", "onChange", "onInput", "onPressEnter", "onStep"]);

  var inputClassName = "".concat(prefixCls, "-input");
  var inputRef = React.useRef(null);

  var _React$useState = React.useState(false),
      _React$useState2 = _slicedToArray(_React$useState, 2),
      focus = _React$useState2[0],
      setFocus = _React$useState2[1];

  var userTypingRef = React.useRef(false);
  var compositionRef = React.useRef(false); // ============================ Value =============================
  // Real value control

  var _React$useState3 = React.useState(function () {
    return getMiniDecimal(value !== null && value !== void 0 ? value : defaultValue);
  }),
      _React$useState4 = _slicedToArray(_React$useState3, 2),
      decimalValue = _React$useState4[0],
      setDecimalValue = _React$useState4[1];

  function setUncontrolledDecimalValue(newDecimal) {
    if (value === undefined) {
      setDecimalValue(newDecimal);
    }
  } // ====================== Parser & Formatter ======================

  /**
   * `precision` is used for formatter & onChange.
   * It will auto generate by `value` & `step`.
   * But it will not block user typing.
   *
   * Note: Auto generate `precision` is used for legacy logic.
   * We should remove this since we already support high precision with BigInt.
   *
   * @param number  Provide which number should calculate precision
   * @param userTyping  Change by user typing
   */


  var getPrecision = React.useCallback(function (numStr, userTyping) {
    if (userTyping) {
      return undefined;
    }

    if (precision >= 0) {
      return precision;
    }

    return Math.max(getNumberPrecision(numStr), getNumberPrecision(step));
  }, [precision, step]); // >>> Parser

  var mergedParser = React.useCallback(function (num) {
    var numStr = String(num);

    if (parser) {
      return parser(numStr);
    }

    var parsedStr = numStr;

    if (decimalSeparator) {
      parsedStr = parsedStr.replace(decimalSeparator, '.');
    } // [Legacy] We still support auto convert `$ 123,456` to `123456`


    return parsedStr.replace(/[^\w.-]+/g, '');
  }, [parser, decimalSeparator]); // >>> Formatter

  var inputValueRef = React.useRef('');
  var mergedFormatter = React.useCallback(function (number, userTyping) {
    if (formatter) {
      return formatter(number, {
        userTyping: userTyping,
        input: String(inputValueRef.current)
      });
    }

    var str = typeof number === 'number' ? num2str(number) : number; // User typing will not auto format with precision directly

    if (!userTyping) {
      var mergedPrecision = getPrecision(str, userTyping);

      if (validateNumber(str) && (decimalSeparator || mergedPrecision >= 0)) {
        // Separator
        var separatorStr = decimalSeparator || '.';
        str = toFixed(str, separatorStr, mergedPrecision, floor);
      }
    }

    return str;
  }, [formatter, getPrecision, decimalSeparator]); // ========================== InputValue ==========================

  /**
   * Input text value control
   *
   * User can not update input content directly. It update with follow rules by priority:
   *  1. controlled `value` changed
   *    * [SPECIAL] Typing like `1.` should not immediately convert to `1`
   *  2. User typing with format (not precision)
   *  3. Blur or Enter trigger revalidate
   */

  var _React$useState5 = React.useState(function () {
    var initValue = defaultValue !== null && defaultValue !== void 0 ? defaultValue : value;

    if (decimalValue.isInvalidate() && ['string', 'number'].includes(_typeof(initValue))) {
      return Number.isNaN(initValue) ? '' : initValue;
    }

    return mergedFormatter(decimalValue.toString(), false);
  }),
      _React$useState6 = _slicedToArray(_React$useState5, 2),
      inputValue = _React$useState6[0],
      setInternalInputValue = _React$useState6[1];

  inputValueRef.current = inputValue; // Should always be string

  function setInputValue(newValue, userTyping) {
    setInternalInputValue(mergedFormatter( // Invalidate number is sometime passed by external control, we should let it go
    // Otherwise is controlled by internal interactive logic which check by userTyping
    // You can ref 'show limited value when input is not focused' test for more info.
    newValue.isInvalidate() ? newValue.toString(false) : newValue.toString(!userTyping), userTyping));
  } // >>> Max & Min limit


  var maxDecimal = React.useMemo(function () {
    return getDecimalIfValidate(max);
  }, [max]);
  var minDecimal = React.useMemo(function () {
    return getDecimalIfValidate(min);
  }, [min]);
  var upDisabled = React.useMemo(function () {
    if (!maxDecimal || !decimalValue || decimalValue.isInvalidate()) {
      return false;
    }

    return maxDecimal.lessEquals(decimalValue);
  }, [maxDecimal, decimalValue]);
  var downDisabled = React.useMemo(function () {
    if (!minDecimal || !decimalValue || decimalValue.isInvalidate()) {
      return false;
    }

    return decimalValue.lessEquals(minDecimal);
  }, [minDecimal, decimalValue]); // Cursor controller

  var _useCursor = useCursor(inputRef.current, focus),
      _useCursor2 = _slicedToArray(_useCursor, 2),
      recordCursor = _useCursor2[0],
      restoreCursor = _useCursor2[1]; // ============================= Data =============================

  /**
   * Find target value closet within range.
   * e.g. [11, 28]:
   *    3  => 11
   *    23 => 23
   *    99 => 28
   */


  var getRangeValue = function getRangeValue(target) {
    // target > max
    if (maxDecimal && !target.lessEquals(maxDecimal)) {
      return maxDecimal;
    } // target < min


    if (minDecimal && !minDecimal.lessEquals(target)) {
      return minDecimal;
    }

    return null;
  };
  /**
   * Check value is in [min, max] range
   */


  var isInRange = function isInRange(target) {
    return !getRangeValue(target);
  };
  /**
   * Trigger `onChange` if value validated and not equals of origin.
   * Return the value that re-align in range.
   */


  var triggerValueUpdate = function triggerValueUpdate(newValue, userTyping) {
    var updateValue = newValue;
    var isRangeValidate = isInRange(updateValue) || updateValue.isEmpty(); // Skip align value when trigger value is empty.
    // We just trigger onChange(null)
    // This should not block user typing

    if (!updateValue.isEmpty() && !userTyping) {
      // Revert value in range if needed
      updateValue = getRangeValue(updateValue) || updateValue;
      isRangeValidate = true;
    }

    if (!readOnly && !disabled && isRangeValidate) {
      var numStr = updateValue.toString();
      var mergedPrecision = getPrecision(numStr, userTyping);

      if (mergedPrecision >= 0) {
        updateValue = getMiniDecimal(toFixed(numStr, '.', mergedPrecision, floor));
      } // Trigger event


      if (!updateValue.equals(decimalValue)) {
        setUncontrolledDecimalValue(updateValue);
        onChange === null || onChange === void 0 ? void 0 : onChange(updateValue.isEmpty() ? null : getDecimalValue(stringMode, updateValue)); // Reformat input if value is not controlled

        if (value === undefined) {
          setInputValue(updateValue, userTyping);
        }
      }

      return updateValue;
    }

    return decimalValue;
  }; // ========================== User Input ==========================


  var onNextPromise = useFrame(); // >>> Collect input value

  var collectInputValue = function collectInputValue(inputStr) {
    recordCursor(); // Update inputValue incase input can not parse as number

    setInternalInputValue(inputStr); // Parse number

    if (!compositionRef.current) {
      var finalValue = mergedParser(inputStr);
      var finalDecimal = getMiniDecimal(finalValue);

      if (!finalDecimal.isNaN()) {
        triggerValueUpdate(finalDecimal, true);
      }
    } // Trigger onInput later to let user customize value if they want do handle something after onChange


    onInput === null || onInput === void 0 ? void 0 : onInput(inputStr); // optimize for chinese input experience
    // https://github.com/ant-design/ant-design/issues/8196

    onNextPromise(function () {
      var nextInputStr = inputStr;

      if (!parser) {
        nextInputStr = inputStr.replace(/。/g, '.');
      }

      if (nextInputStr !== inputStr) {
        collectInputValue(nextInputStr);
      }
    });
  }; // >>> Composition


  var onCompositionStart = function onCompositionStart() {
    compositionRef.current = true;
  };

  var onCompositionEnd = function onCompositionEnd() {
    compositionRef.current = false;
    collectInputValue(inputRef.current.value);
  }; // >>> Input


  var onInternalInput = function onInternalInput(e) {
    collectInputValue(e.target.value);
  }; // ============================= Step =============================


  var onInternalStep = function onInternalStep(up) {
    var _inputRef$current;

    // Ignore step since out of range
    if (up && upDisabled || !up && downDisabled) {
      return;
    } // Clear typing status since it may caused by up & down key.
    // We should sync with input value.


    userTypingRef.current = false;
    var stepDecimal = getMiniDecimal(step);

    if (!up) {
      stepDecimal = stepDecimal.negate();
    }

    var target = (decimalValue || getMiniDecimal(0)).add(stepDecimal.toString());
    var updatedValue = triggerValueUpdate(target, false);
    onStep === null || onStep === void 0 ? void 0 : onStep(getDecimalValue(stringMode, updatedValue), {
      offset: step,
      type: up ? 'up' : 'down'
    });
    (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
  }; // ============================ Flush =============================

  /**
   * Flush current input content to trigger value change & re-formatter input if needed
   */


  var flushInputValue = function flushInputValue(userTyping) {
    var parsedValue = getMiniDecimal(mergedParser(inputValue));
    var formatValue = parsedValue;

    if (!parsedValue.isNaN()) {
      // Only validate value or empty value can be re-fill to inputValue
      // Reassign the formatValue within ranged of trigger control
      formatValue = triggerValueUpdate(parsedValue, userTyping);
    } else {
      formatValue = decimalValue;
    }

    if (value !== undefined) {
      // Reset back with controlled value first
      setInputValue(decimalValue, false);
    } else if (!formatValue.isNaN()) {
      // Reset input back since no validate value
      setInputValue(formatValue, false);
    }
  };

  var onBeforeInput = function onBeforeInput() {
    userTypingRef.current = true;
  };

  var onKeyDown = function onKeyDown(event) {
    var which = event.which;
    userTypingRef.current = true;

    if (which === KeyCode.ENTER) {
      if (!compositionRef.current) {
        userTypingRef.current = false;
      }

      flushInputValue(false);
      onPressEnter === null || onPressEnter === void 0 ? void 0 : onPressEnter(event);
    }

    if (keyboard === false) {
      return;
    } // Do step


    if (!compositionRef.current && [KeyCode.UP, KeyCode.DOWN].includes(which)) {
      onInternalStep(KeyCode.UP === which);
      event.preventDefault();
    }
  };

  var onKeyUp = function onKeyUp() {
    userTypingRef.current = false;
  }; // >>> Focus & Blur


  var onBlur = function onBlur() {
    flushInputValue(false);
    setFocus(false);
    userTypingRef.current = false;
  }; // ========================== Controlled ==========================
  // Input by precision


  useUpdateEffect(function () {
    if (!decimalValue.isInvalidate()) {
      setInputValue(decimalValue, false);
    }
  }, [precision]); // Input by value

  useUpdateEffect(function () {
    var newValue = getMiniDecimal(value);
    setDecimalValue(newValue);
    var currentParsedValue = getMiniDecimal(mergedParser(inputValue)); // When user typing from `1.2` to `1.`, we should not convert to `1` immediately.
    // But let it go if user set `formatter`

    if (!newValue.equals(currentParsedValue) || !userTypingRef.current || formatter) {
      // Update value as effect
      setInputValue(newValue, userTypingRef.current);
    }
  }, [value]); // ============================ Cursor ============================

  useUpdateEffect(function () {
    if (formatter) {
      restoreCursor();
    }
  }, [inputValue]); // ============================ Render ============================

  return /*#__PURE__*/React.createElement("div", {
    className: classNames(prefixCls, className, (_classNames = {}, _defineProperty(_classNames, "".concat(prefixCls, "-focused"), focus), _defineProperty(_classNames, "".concat(prefixCls, "-disabled"), disabled), _defineProperty(_classNames, "".concat(prefixCls, "-readonly"), readOnly), _defineProperty(_classNames, "".concat(prefixCls, "-not-a-number"), decimalValue.isNaN()), _defineProperty(_classNames, "".concat(prefixCls, "-out-of-range"), !decimalValue.isInvalidate() && !isInRange(decimalValue)), _classNames)),
    style: style,
    onFocus: function onFocus() {
      setFocus(true);
    },
    onBlur: onBlur,
    onKeyDown: onKeyDown,
    onKeyUp: onKeyUp,
    onCompositionStart: onCompositionStart,
    onCompositionEnd: onCompositionEnd,
    onBeforeInput: onBeforeInput
  }, controls && /*#__PURE__*/React.createElement(StepHandler, {
    prefixCls: prefixCls,
    upNode: upHandler,
    downNode: downHandler,
    upDisabled: upDisabled,
    downDisabled: downDisabled,
    onStep: onInternalStep
  }), /*#__PURE__*/React.createElement("div", {
    className: "".concat(inputClassName, "-wrap")
  }, /*#__PURE__*/React.createElement("input", Object.assign({
    autoComplete: "off",
    role: "spinbutton",
    "aria-valuemin": min,
    "aria-valuemax": max,
    "aria-valuenow": decimalValue.isInvalidate() ? null : decimalValue.toString(),
    step: step
  }, inputProps, {
    ref: composeRef(inputRef, ref),
    className: inputClassName,
    value: inputValue,
    onChange: onInternalInput,
    disabled: disabled,
    readOnly: readOnly
  }))));
});
InputNumber.displayName = 'InputNumber';
export default InputNumber;