var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
import { findIndex } from 'ramda';
import React from 'react';
import { mergeRefs } from '../../effects/mergeRefs';
import { spacing } from '../../styles/spacing';
import { useNumberFormat, usePrevious } from './NumberInput.hooks';
import { addonRootPadding, addonRootFontFamily, addonRootFontSize, inputDefaultXPadding, StyledIncrementerButton, StyledIncrementerWrapper, StyledInput, StyledPrefix, StyledSuffix, StyledWrapper, } from './NumberInput.styles';
import { fillDecimalScaleVariant, } from './NumberInput.types';
import { minus, emptyString, backspaceKey, deleteKey, periodKey, maxDigitLength, numericKeypadKey, commaKey, metaKeyIdentifier, controlKey, highlightAllKey, copyKey, defaultLocale, getSeparators, toFixed, unformat, format, identifyPressedKey, getNewCursorPosition, prepareValueToFormatting, } from './NumberInput.utils';
function noop() { }
var PlusIcon = function () { return (React.createElement("svg", { width: "8", height: "8", viewBox: "0 0 8 8", xmlns: "http://www.w3.org/2000/svg" },
    React.createElement("path", { d: "M4.5 0H3.5V3.5H0V4.5H3.5V8H4.5V4.5H8V3.5H4.5V0Z" }))); };
var MinusIcon = function () { return (React.createElement("svg", { width: "8", height: "2", viewBox: "0 0 8 2", xmlns: "http://www.w3.org/2000/svg" },
    React.createElement("path", { d: "M4.5 0.5H3.5H0V1.5H3.5H4.5H8V0.5H4.5Z" }))); };
/**

 The number input allows users to add numbers to a given form or table.
 The currency input will be used for data with a monetary value.
 Both components allow characters 0-9, periods, and commas. In order to avoid
 the loss of precision, the number length is limited by 15 digits.

 @since 10.19.0

 @see [Storybook](https://procore.github.io/core/latest/?path=/story/demos-numberinput--demo-number-input)

 @see [Design Guidelines](https://design.procore.com/number-and-currency-input)

 */
export var NumberInput = React.forwardRef(function (_a, ref) {
    var _b = _a.decimalScale, decimalScale = _b === void 0 ? true : _b, defaultValue = _a.defaultValue, _c = _a.disabled, disabled = _c === void 0 ? false : _c, _d = _a.error, error = _d === void 0 ? false : _d, _e = _a.fillDecimalScale, fillDecimalScale = _e === void 0 ? fillDecimalScaleVariant.none : _e, _f = _a.locale, _locale = _f === void 0 ? defaultLocale : _f, _onChange = _a.onChange, _g = _a.onKeyDown, _onKeyDown = _g === void 0 ? noop : _g, _h = _a.onBlur, _onBlur = _h === void 0 ? noop : _h, _j = _a.prefix, prefix = _j === void 0 ? emptyString : _j, readOnly = _a.readOnly, _k = _a.step, step = _k === void 0 ? 0 : _k, style = _a.style, _l = _a.suffix, suffix = _l === void 0 ? emptyString : _l, value = _a.value, qa = _a.qa, props = __rest(_a, ["decimalScale", "defaultValue", "disabled", "error", "fillDecimalScale", "locale", "onChange", "onKeyDown", "onBlur", "prefix", "readOnly", "step", "style", "suffix", "value", "qa"]);
    var inputRef = React.useRef(null);
    var _m = useNumberFormat({
        decimalScale: decimalScale,
        fillDecimalScale: fillDecimalScale,
        locale: _locale,
    }), verifiedDecimalScale = _m.decimalScale, fillDecimalScaleOnBlur = _m.fillDecimalScaleOnBlur, fixedDecimalScale = _m.fixedDecimalScale, formatValue = _m.formatValue, getValueLengthInfo = _m.getValueLengthInfo, locale = _m.locale;
    var isControlled = value !== undefined;
    var isControlledWithoutOnChangeHandlerAndReadOnlyAttribute = isControlled && !_onChange && !readOnly;
    var hasIncrementer = step > 0 && !readOnly;
    var _o = React.useMemo(function () { return getSeparators(locale); }, [locale]), group = _o.group, decimal = _o.decimal;
    var decimalSeparatorKey = React.useMemo(function () {
        return decimal === periodKey ? periodKey : commaKey;
    }, [decimal]);
    var inputPadding = React.useMemo(function () {
        var fontContext = "".concat(addonRootFontSize, " \"").concat(addonRootFontFamily, "\"");
        if (prefix || suffix) {
            var element = document.createElement('canvas');
            var context = element.getContext('2d');
            if (context) {
                context.font = fontContext;
                var extraBasePadding = inputDefaultXPadding + addonRootPadding;
                var suffixPadding_1 = extraBasePadding + Math.ceil(context.measureText(suffix).width);
                var getRightPadding = function () {
                    if (hasIncrementer && suffix) {
                        return suffixPadding_1 + spacing.xl;
                    }
                    if (suffix) {
                        return suffixPadding_1;
                    }
                    return null;
                };
                return {
                    left: prefix
                        ? extraBasePadding + Math.ceil(context.measureText(prefix).width)
                        : inputDefaultXPadding,
                    right: getRightPadding(),
                };
            }
        }
        return {
            left: inputDefaultXPadding,
            right: null,
        };
    }, [prefix, suffix, hasIncrementer]);
    var getValueChange = React.useCallback(function (value) {
        var parsedNumber = unformat(locale, value).parsedNumber;
        return {
            value: value,
            parsedNumber: isNaN(parsedNumber) ? null : parsedNumber,
        };
    }, [locale]);
    var externalOnChange = React.useCallback(function (value) { return _onChange && _onChange(getValueChange(value)); }, [_onChange, getValueChange]);
    var _p = React.useState(formatValue(defaultValue)), inputValue = _p[0], setInputValue = _p[1];
    var _q = React.useState(isControlled ? formatValue(value) : inputValue), prevValue = _q[0], setPrevValue = _q[1];
    var _r = React.useState({
        selectionStart: 0,
        selectionEnd: 0,
        selection: emptyString,
    }), prevCursorPosition = _r[0], setPrevCursorPosition = _r[1];
    var _s = React.useState(), pressedKey = _s[0], setPressedKey = _s[1];
    var prevLocale = usePrevious(locale);
    // eslint-disable-next-line react-hooks/exhaustive-deps
    React.useLayoutEffect(function () {
        var formattedValue = formatValue(value);
        if (isControlled && inputValue !== formattedValue) {
            setInputValue(formattedValue);
        }
        if (isControlledWithoutOnChangeHandlerAndReadOnlyAttribute) {
            console.error("Warning: Failed prop type: You provided a `value` prop to a form field without an `onChange` handler.\n        This will render a read-only field. If the field should be mutable use `defaultValue`.\n        Otherwise, set either `onChange` or `readOnly`.");
        }
    });
    React.useEffect(function () {
        if (prevLocale) {
            setInputValue(function (prev) {
                var newValue = formatValue(unformat(prevLocale, prev).parsedNumber);
                externalOnChange(newValue);
                return newValue;
            });
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [prevLocale, formatValue]);
    var getNewValueAndCursorPosition = function (value, pressedKey) {
        var preparedValue = prepareValueToFormatting(decimal, decimalSeparatorKey, group, pressedKey, prevCursorPosition, prevValue, verifiedDecimalScale, value);
        var newValue = formatValue(preparedValue);
        return {
            newValue: newValue,
            newCursorPosition: getNewCursorPosition(decimal, decimalSeparatorKey, group, newValue, pressedKey, prevCursorPosition, prevValue, value, verifiedDecimalScale, locale, fillDecimalScale),
        };
    };
    var adjustCursor = function (newPosition) {
        var _a;
        (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.setSelectionRange(newPosition, newPosition);
        // Mobile Chrome adjustment
        setTimeout(function () { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.setSelectionRange(newPosition, newPosition); }, 0);
    };
    var makeStep = function (down) {
        if (down === void 0) { down = false; }
        var getSignificantPrecision = function (fraction) {
            var reversedFraction = fraction.split(emptyString).reverse();
            var indexOfLastSignificantFractionDigit = findIndex(function (i) { return i !== '0'; }, reversedFraction);
            return indexOfLastSignificantFractionDigit > -1
                ? reversedFraction.slice(indexOfLastSignificantFractionDigit).length
                : 0;
        };
        var _a = unformat(locale, inputValue), parsedNumber = _a.parsedNumber, fraction = _a.fraction;
        var stepFraction = unformat(locale, step).fraction;
        var prevValuePrecision = fraction.length || 0;
        var prevValueSignificantPrecision = getSignificantPrecision(fraction);
        var rawStepPrecision = stepFraction.length || 0;
        var rawStepPrecisionExceedsDecimalScale = rawStepPrecision > verifiedDecimalScale;
        var stepPrecision = rawStepPrecisionExceedsDecimalScale
            ? verifiedDecimalScale
            : rawStepPrecision;
        if (rawStepPrecisionExceedsDecimalScale) {
            console.warn("WARNING: Step's value was rounded since its scale (".concat(rawStepPrecision, ") exceeds the decimal scale (").concat(verifiedDecimalScale, ")."));
        }
        var diff = toFixed(step, stepPrecision) * (down ? -1 : 1);
        var precision = Math.max(stepPrecision, fixedDecimalScale ? verifiedDecimalScale : prevValuePrecision);
        var significantPrecision = Math.max(prevValueSignificantPrecision, stepPrecision);
        var calculated = inputValue
            ? ((parsedNumber || 0) + diff).toFixed(significantPrecision)
            : diff;
        var _b = getValueLengthInfo(calculated, true), isSafeLength = _b.isSafeLength, isSafeIntegerLength = _b.isSafeIntegerLength;
        var rejectStepByLengthLimit = !isSafeLength || !isSafeIntegerLength;
        if (rejectStepByLengthLimit) {
            console.warn("WARNING: Incrementer's step was not fulfilled since result oversteps input ".concat(maxDigitLength, "-digit limit and can't be presented without a loss of precision."));
            return;
        }
        var formatted = format(locale, precision, fillDecimalScaleVariant.always, calculated, true);
        setInputValue(formatted);
        externalOnChange(formatted);
    };
    var internalOnKeyDown = function (e) {
        var ctrlKey = e.ctrlKey, metaKey = e.metaKey, key = e.key, _a = e.target, value = _a.value, selectionStart = _a.selectionStart, selectionEnd = _a.selectionEnd;
        var selection = value.slice(selectionStart, selectionEnd);
        setPressedKey(key);
        setPrevCursorPosition({
            selectionStart: selectionStart,
            selectionEnd: selectionEnd,
            selection: selection,
        });
        setPrevValue(value);
        var preserveDecimalSeparator = function () {
            var decimalBackspaced = !selection &&
                [backspaceKey].includes(key) &&
                value[selectionEnd - 1] === decimal;
            var decimalDeleted = !selection &&
                [deleteKey, numericKeypadKey].includes(key) &&
                value[selectionEnd] === decimal;
            var nonReplacingKeys = [metaKeyIdentifier, controlKey, highlightAllKey, copyKey].includes(key) &&
                (ctrlKey || metaKey);
            var decimalReplacedAsSelection = selection &&
                selection.includes(decimal) &&
                selection.length < value.length &&
                !nonReplacingKeys;
            var preserveDecimal = decimalBackspaced || decimalDeleted || decimalReplacedAsSelection;
            if (preserveDecimal) {
                var base = decimalReplacedAsSelection
                    ? selectionStart
                    : selectionEnd;
                var shift = (decimalBackspaced && -1) || (decimalDeleted && 1) || 0;
                var cursorPosition = base + shift;
                e.preventDefault();
                adjustCursor(cursorPosition);
            }
        };
        var isDecimalSeparatorPreserved = function () {
            var _a = getValueLengthInfo(value), safeIntegerLength = _a.safeIntegerLength, integerLength = _a.integerLength, fractionLength = _a.fractionLength;
            var decimalSeparatorCanNotBeRemoved = safeIntegerLength - integerLength < fractionLength;
            return ((fillDecimalScaleOnBlur && decimalSeparatorCanNotBeRemoved) ||
                fixedDecimalScale);
        };
        if (isDecimalSeparatorPreserved()) {
            preserveDecimalSeparator();
        }
        var handleIncrementer = function () {
            switch (key) {
                case 'ArrowUp':
                case 'Up':
                    e.preventDefault();
                    makeStep();
                    break;
                case 'ArrowDown':
                case 'Down':
                    e.preventDefault();
                    makeStep(true);
                    break;
            }
        };
        if (hasIncrementer) {
            handleIncrementer();
        }
        _onKeyDown(e);
    };
    var internalOnChange = function (e) {
        if (isControlledWithoutOnChangeHandlerAndReadOnlyAttribute)
            return;
        var value = e.target.value, inputType = e.nativeEvent.inputType;
        var _a = getNewValueAndCursorPosition(value, identifyPressedKey(decimalSeparatorKey, inputType, pressedKey, prevValue, value)), newValue = _a.newValue, newCursorPosition = _a.newCursorPosition;
        // Prevent cursor flickering
        e.target.value = newValue;
        setInputValue(newValue);
        adjustCursor(newCursorPosition);
        if (newValue !== prevValue) {
            externalOnChange(newValue);
        }
    };
    var internalOnBlur = function (e) {
        var _a = unformat(locale, inputValue), integer = _a.integer, fraction = _a.fraction;
        var shouldValueBeCleared = inputValue === minus ||
            inputValue === decimal ||
            inputValue === "".concat(minus).concat(decimal);
        var shouldIntegerPartBeFilled = fraction.length > 0 && (integer === emptyString || integer === minus);
        var isDecimalSeparatorRedundant = verifiedDecimalScale > 0 &&
            inputValue[inputValue.length - 1] === decimal &&
            !fillDecimalScaleOnBlur;
        var isDecimalScaleNotFilled = verifiedDecimalScale - fraction.length > 0;
        var shouldDecimalScaleBeFilled = fillDecimalScaleOnBlur && isDecimalScaleNotFilled;
        if (shouldValueBeCleared ||
            shouldIntegerPartBeFilled ||
            isDecimalSeparatorRedundant ||
            shouldDecimalScaleBeFilled) {
            var getNewValue = function () {
                if (shouldValueBeCleared) {
                    return emptyString;
                }
                if (shouldIntegerPartBeFilled) {
                    var numberOfZerosToPad = verifiedDecimalScale - fraction.length;
                    return "".concat(integer, "0").concat(decimal).concat(shouldDecimalScaleBeFilled
                        ? fraction.concat(Array.from({ length: numberOfZerosToPad }, function (_) { return '0'; }).join(emptyString))
                        : fraction);
                }
                if (isDecimalSeparatorRedundant) {
                    return inputValue.replace(new RegExp("[".concat(decimal, "]"), 'g'), emptyString);
                }
                if (shouldDecimalScaleBeFilled) {
                    return format(locale, verifiedDecimalScale, fillDecimalScaleVariant.always, inputValue);
                }
                return inputValue;
            };
            var newValue = getNewValue();
            setInputValue(newValue);
            externalOnChange(newValue);
        }
        _onBlur(e);
    };
    return (React.createElement(StyledWrapper, { hasIncrementer: hasIncrementer, "data-qa": qa === null || qa === void 0 ? void 0 : qa.wrapper },
        React.createElement(StyledInput, __assign({ value: inputValue, disabled: disabled, error: error, onKeyDown: internalOnKeyDown, onChange: internalOnChange, onBlur: internalOnBlur, inputMode: "numeric", readOnly: readOnly, style: __assign(__assign({}, style), {
                paddingLeft: "".concat(inputPadding.left, "px"),
                paddingRight: "".concat(inputPadding.right, "px"),
            }) }, props, { ref: mergeRefs(ref, inputRef) })),
        prefix && React.createElement(StyledPrefix, { disabled: disabled }, prefix),
        suffix && (React.createElement(StyledSuffix, { withIncrementer: hasIncrementer, disabled: disabled }, suffix)),
        hasIncrementer && (React.createElement(StyledIncrementerWrapper, null,
            React.createElement(StyledIncrementerButton, { type: "button", disabled: disabled, tabIndex: -1, "data-qa": qa === null || qa === void 0 ? void 0 : qa.increment, onClick: function () { return makeStep(); } },
                React.createElement(PlusIcon, null)),
            React.createElement(StyledIncrementerButton, { type: "button", disabled: disabled, tabIndex: -1, "data-qa": qa === null || qa === void 0 ? void 0 : qa.decrement, onClick: function () { return makeStep(true); } },
                React.createElement(MinusIcon, null))))));
});
NumberInput.displayName = 'NumberInput';
export var defaultCurrencyDecimalScale = 2;
export var defaultCurrencyFillDecimalScale = fillDecimalScaleVariant.onBlur;
export var defaultCurrencyInputPrefix = '$';
export var CurrencyInput = React.forwardRef(function (_a, ref) {
    var _b = _a.decimalScale, decimalScale = _b === void 0 ? defaultCurrencyDecimalScale : _b, _c = _a.fillDecimalScale, fillDecimalScale = _c === void 0 ? defaultCurrencyFillDecimalScale : _c, _d = _a.prefix, prefix = _d === void 0 ? defaultCurrencyInputPrefix : _d, props = __rest(_a, ["decimalScale", "fillDecimalScale", "prefix"]);
    return (React.createElement(NumberInput, __assign({ decimalScale: decimalScale, fillDecimalScale: fillDecimalScale, prefix: prefix }, props, { ref: ref })));
});
CurrencyInput.displayName = 'CurrencyInput';
//# sourceMappingURL=NumberInput.js.map