/**
 * DevExtreme (esm/localization/number.js)
 * Version: 24.1.7-build-24272-1937
 * Build date: Sat Sep 28 2024
 *
 * Copyright (c) 2012 - 2024 Developer Express Inc. ALL RIGHTS RESERVED
 * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
 */
import dependencyInjector from "../core/utils/dependency_injector";
import {
    escapeRegExp
} from "../core/utils/common";
import {
    each
} from "../core/utils/iterator";
import {
    isPlainObject
} from "../core/utils/type";
import {
    getFormatter
} from "./ldml/number";
import config from "../core/config";
import errors from "../core/errors";
import {
    toFixed
} from "./utils";
import currencyLocalization from "./currency";
import intlNumberLocalization from "./intl/number";
const hasIntl = "undefined" !== typeof Intl;
const MAX_LARGE_NUMBER_POWER = 4;
const DECIMAL_BASE = 10;
const NUMERIC_FORMATS = ["currency", "fixedpoint", "exponential", "percent", "decimal"];
const LargeNumberFormatPostfixes = {
    1: "K",
    2: "M",
    3: "B",
    4: "T"
};
const LargeNumberFormatPowers = {
    largenumber: "auto",
    thousands: 1,
    millions: 2,
    billions: 3,
    trillions: 4
};
const numberLocalization = dependencyInjector({
    engine: function() {
        return "base"
    },
    numericFormats: NUMERIC_FORMATS,
    defaultLargeNumberFormatPostfixes: LargeNumberFormatPostfixes,
    _parseNumberFormatString: function(formatType) {
        const formatObject = {};
        if (!formatType || "string" !== typeof formatType) {
            return
        }
        const formatList = formatType.toLowerCase().split(" ");
        each(formatList, ((index, value) => {
            if (NUMERIC_FORMATS.includes(value)) {
                formatObject.formatType = value
            } else if (value in LargeNumberFormatPowers) {
                formatObject.power = LargeNumberFormatPowers[value]
            }
        }));
        if (formatObject.power && !formatObject.formatType) {
            formatObject.formatType = "fixedpoint"
        }
        if (formatObject.formatType) {
            return formatObject
        }
    },
    _calculateNumberPower: function(value, base, minPower, maxPower) {
        let number = Math.abs(value);
        let power = 0;
        if (number > 1) {
            while (number && number >= base && (void 0 === maxPower || power < maxPower)) {
                power++;
                number /= base
            }
        } else if (number > 0 && number < 1) {
            while (number < 1 && (void 0 === minPower || power > minPower)) {
                power--;
                number *= base
            }
        }
        return power
    },
    _getNumberByPower: function(number, power, base) {
        let result = number;
        while (power > 0) {
            result /= base;
            power--
        }
        while (power < 0) {
            result *= base;
            power++
        }
        return result
    },
    _formatNumber: function(value, formatObject, formatConfig) {
        if ("auto" === formatObject.power) {
            formatObject.power = this._calculateNumberPower(value, 1e3, 0, 4)
        }
        if (formatObject.power) {
            value = this._getNumberByPower(value, formatObject.power, 1e3)
        }
        const powerPostfix = this.defaultLargeNumberFormatPostfixes[formatObject.power] || "";
        let result = this._formatNumberCore(value, formatObject.formatType, formatConfig);
        result = result.replace(/(\d|.$)(\D*)$/, "$1" + powerPostfix + "$2");
        return result
    },
    _formatNumberExponential: function(value, formatConfig) {
        let power = this._calculateNumberPower(value, 10);
        let number = this._getNumberByPower(value, power, 10);
        if (void 0 === formatConfig.precision) {
            formatConfig.precision = 1
        }
        if (number.toFixed(formatConfig.precision || 0) >= 10) {
            power++;
            number /= 10
        }
        const powString = (power >= 0 ? "+" : "") + power.toString();
        return this._formatNumberCore(number, "fixedpoint", formatConfig) + "E" + powString
    },
    _addZeroes: function(value, precision) {
        const multiplier = Math.pow(10, precision);
        const sign = value < 0 ? "-" : "";
        value = (Math.abs(value) * multiplier >>> 0) / multiplier;
        let result = value.toString();
        while (result.length < precision) {
            result = "0" + result
        }
        return sign + result
    },
    _addGroupSeparators: function(value) {
        const parts = value.toString().split(".");
        return parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, config().thousandsSeparator) + (parts[1] ? config().decimalSeparator + parts[1] : "")
    },
    _formatNumberCore: function(value, format, formatConfig) {
        if ("exponential" === format) {
            return this._formatNumberExponential(value, formatConfig)
        }
        if ("decimal" !== format && null !== formatConfig.precision) {
            formatConfig.precision = formatConfig.precision || 0
        }
        if ("percent" === format) {
            value *= 100
        }
        if (void 0 !== formatConfig.precision) {
            if ("decimal" === format) {
                value = this._addZeroes(value, formatConfig.precision)
            } else {
                value = null === formatConfig.precision ? value.toPrecision() : toFixed(value, formatConfig.precision)
            }
        }
        if ("decimal" !== format) {
            value = this._addGroupSeparators(value)
        } else {
            value = value.toString().replace(".", config().decimalSeparator)
        }
        if ("percent" === format) {
            value += "%"
        }
        return value
    },
    _normalizeFormat: function(format) {
        if (!format) {
            return {}
        }
        if ("function" === typeof format) {
            return format
        }
        if (!isPlainObject(format)) {
            format = {
                type: format
            }
        }
        return format
    },
    _getSeparators: function() {
        return {
            decimalSeparator: this.getDecimalSeparator(),
            thousandsSeparator: this.getThousandsSeparator()
        }
    },
    getThousandsSeparator: function() {
        return this.format(1e4, "fixedPoint")[2]
    },
    getDecimalSeparator: function() {
        return this.format(1.2, {
            type: "fixedPoint",
            precision: 1
        })[1]
    },
    convertDigits: function(value, toStandard) {
        const digits = this.format(90, "decimal");
        if ("string" !== typeof value || "0" === digits[1]) {
            return value
        }
        const fromFirstDigit = toStandard ? digits[1] : "0";
        const toFirstDigit = toStandard ? "0" : digits[1];
        const fromLastDigit = toStandard ? digits[0] : "9";
        const regExp = new RegExp("[" + fromFirstDigit + "-" + fromLastDigit + "]", "g");
        return value.replace(regExp, (char => String.fromCharCode(char.charCodeAt(0) + (toFirstDigit.charCodeAt(0) - fromFirstDigit.charCodeAt(0)))))
    },
    getNegativeEtalonRegExp: function(format) {
        const separators = this._getSeparators();
        const digitalRegExp = new RegExp("[0-9" + escapeRegExp(separators.decimalSeparator + separators.thousandsSeparator) + "]+", "g");
        let negativeEtalon = this.format(-1, format).replace(digitalRegExp, "1");
        ["\\", "(", ")", "[", "]", "*", "+", "$", "^", "?", "|", "{", "}"].forEach((char => {
            negativeEtalon = negativeEtalon.replace(new RegExp(`\\${char}`, "g"), `\\${char}`)
        }));
        negativeEtalon = negativeEtalon.replace(/ /g, "\\s");
        negativeEtalon = negativeEtalon.replace(/1/g, ".*");
        return new RegExp(negativeEtalon, "g")
    },
    getSign: function(text, format) {
        if (!format) {
            if ("-" === text.replace(/[^0-9-]/g, "").charAt(0)) {
                return -1
            }
            return 1
        }
        const negativeEtalon = this.getNegativeEtalonRegExp(format);
        return text.match(negativeEtalon) ? -1 : 1
    },
    format: function(value, format) {
        if ("number" !== typeof value) {
            return value
        }
        if ("number" === typeof format) {
            return value
        }
        format = format && format.formatter || format;
        if ("function" === typeof format) {
            return format(value)
        }
        format = this._normalizeFormat(format);
        if (!format.type) {
            format.type = "decimal"
        }
        const numberConfig = this._parseNumberFormatString(format.type);
        if (!numberConfig) {
            const formatterConfig = this._getSeparators();
            formatterConfig.unlimitedIntegerDigits = format.unlimitedIntegerDigits;
            return this.convertDigits(getFormatter(format.type, formatterConfig)(value))
        }
        return this._formatNumber(value, numberConfig, format)
    },
    parse: function(text, format) {
        if (!text) {
            return
        }
        if (format && format.parser) {
            return format.parser(text)
        }
        text = this.convertDigits(text, true);
        if (format && "string" !== typeof format) {
            errors.log("W0011")
        }
        const decimalSeparator = this.getDecimalSeparator();
        const regExp = new RegExp("[^0-9" + escapeRegExp(decimalSeparator) + "]", "g");
        const cleanedText = text.replace(regExp, "").replace(decimalSeparator, ".").replace(/\.$/g, "");
        if ("." === cleanedText || "" === cleanedText) {
            return null
        }
        if (this._calcSignificantDigits(cleanedText) > 15) {
            return NaN
        }
        let parsed = +cleanedText * this.getSign(text, format);
        format = this._normalizeFormat(format);
        const formatConfig = this._parseNumberFormatString(format.type);
        let power = null === formatConfig || void 0 === formatConfig ? void 0 : formatConfig.power;
        if (power) {
            if ("auto" === power) {
                const match = text.match(/\d(K|M|B|T)/);
                if (match) {
                    power = Object.keys(LargeNumberFormatPostfixes).find((power => LargeNumberFormatPostfixes[power] === match[1]))
                }
            }
            parsed *= Math.pow(10, 3 * power)
        }
        if ("percent" === (null === formatConfig || void 0 === formatConfig ? void 0 : formatConfig.formatType)) {
            parsed /= 100
        }
        return parsed
    },
    _calcSignificantDigits: function(text) {
        const [integer, fractional] = text.split(".");
        const calcDigitsAfterLeadingZeros = digits => {
            let index = -1;
            for (let i = 0; i < digits.length; i++) {
                if ("0" !== digits[i]) {
                    index = i;
                    break
                }
            }
            return index > -1 ? digits.length - index : 0
        };
        let result = 0;
        if (integer) {
            result += calcDigitsAfterLeadingZeros(integer.split(""))
        }
        if (fractional) {
            result += calcDigitsAfterLeadingZeros(fractional.split("").reverse())
        }
        return result
    }
});
numberLocalization.inject(currencyLocalization);
if (hasIntl) {
    numberLocalization.inject(intlNumberLocalization)
}
export default numberLocalization;
