import {NumberFormatter as $1dfb119a85e764e5$export$cc77c4ff7e8673c5} from "./NumberFormatter.mjs";

/*
 * Copyright 2020 Adobe. All rights reserved.
 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. You may obtain a copy
 * of the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under
 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
 * OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */ 
const $eb76cf4feb040f77$var$CURRENCY_SIGN_REGEX = new RegExp('^.*\\(.*\\).*$');
const $eb76cf4feb040f77$var$NUMBERING_SYSTEMS = [
    'latn',
    'arab',
    'hanidec',
    'deva',
    'beng',
    'fullwide'
];
class $eb76cf4feb040f77$export$cd11ab140839f11d {
    constructor(locale, options = {}){
        this.locale = locale;
        this.options = options;
    }
    /**
   * Parses the given string to a number. Returns NaN if a valid number could not be parsed.
   */ parse(value) {
        return $eb76cf4feb040f77$var$getNumberParserImpl(this.locale, this.options, value).parse(value);
    }
    /**
   * Returns whether the given string could potentially be a valid number. This should be used to
   * validate user input as the user types. If a `minValue` or `maxValue` is provided, the validity
   * of the minus/plus sign characters can be checked.
   */ isValidPartialNumber(value, minValue, maxValue) {
        return $eb76cf4feb040f77$var$getNumberParserImpl(this.locale, this.options, value).isValidPartialNumber(value, minValue, maxValue);
    }
    /**
   * Returns a numbering system for which the given string is valid in the current locale.
   * If no numbering system could be detected, the default numbering system for the current
   * locale is returned.
   */ getNumberingSystem(value) {
        return $eb76cf4feb040f77$var$getNumberParserImpl(this.locale, this.options, value).options.numberingSystem;
    }
}
const $eb76cf4feb040f77$var$numberParserCache = new Map();
function $eb76cf4feb040f77$var$getNumberParserImpl(locale, options, value) {
    // First try the default numbering system for the provided locale
    let defaultParser = $eb76cf4feb040f77$var$getCachedNumberParser(locale, options);
    // If that doesn't match, and the locale doesn't include a hard coded numbering system,
    // try each of the other supported numbering systems until we find one that matches.
    if (!locale.includes('-nu-') && !defaultParser.isValidPartialNumber(value)) {
        for (let numberingSystem of $eb76cf4feb040f77$var$NUMBERING_SYSTEMS)if (numberingSystem !== defaultParser.options.numberingSystem) {
            let parser = $eb76cf4feb040f77$var$getCachedNumberParser(locale + (locale.includes('-u-') ? '-nu-' : '-u-nu-') + numberingSystem, options);
            if (parser.isValidPartialNumber(value)) return parser;
        }
    }
    return defaultParser;
}
function $eb76cf4feb040f77$var$getCachedNumberParser(locale, options) {
    let cacheKey = locale + (options ? Object.entries(options).sort((a, b)=>a[0] < b[0] ? -1 : 1).join() : '');
    let parser = $eb76cf4feb040f77$var$numberParserCache.get(cacheKey);
    if (!parser) {
        parser = new $eb76cf4feb040f77$var$NumberParserImpl(locale, options);
        $eb76cf4feb040f77$var$numberParserCache.set(cacheKey, parser);
    }
    return parser;
}
// The actual number parser implementation. Instances of this class are cached
// based on the locale, options, and detected numbering system.
class $eb76cf4feb040f77$var$NumberParserImpl {
    constructor(locale, options = {}){
        this.locale = locale;
        // see https://tc39.es/ecma402/#sec-setnfdigitoptions, when using roundingIncrement, the maximumFractionDigits and minimumFractionDigits must be equal
        // by default, they are 0 and 3 respectively, so we set them to 0 if neither are set
        if (options.roundingIncrement !== 1 && options.roundingIncrement != null) {
            if (options.maximumFractionDigits == null && options.minimumFractionDigits == null) {
                options.maximumFractionDigits = 0;
                options.minimumFractionDigits = 0;
            } else if (options.maximumFractionDigits == null) options.maximumFractionDigits = options.minimumFractionDigits;
            else if (options.minimumFractionDigits == null) options.minimumFractionDigits = options.maximumFractionDigits;
        // if both are specified, let the normal Range Error be thrown
        }
        this.formatter = new Intl.NumberFormat(locale, options);
        this.options = this.formatter.resolvedOptions();
        this.symbols = $eb76cf4feb040f77$var$getSymbols(locale, this.formatter, this.options, options);
        if (this.options.style === 'percent' && ((this.options.minimumFractionDigits ?? 0) > 18 || (this.options.maximumFractionDigits ?? 0) > 18)) console.warn('NumberParser cannot handle percentages with greater than 18 decimal places, please reduce the number in your options.');
    }
    parse(value) {
        let isGroupSymbolAllowed = this.formatter.resolvedOptions().useGrouping;
        // to parse the number, we need to remove anything that isn't actually part of the number, for example we want '-10.40' not '-10.40 USD'
        let fullySanitizedValue = this.sanitize(value);
        // Return NaN if there is a group symbol but useGrouping is false
        if (!isGroupSymbolAllowed && this.symbols.group && fullySanitizedValue.includes(this.symbols.group)) return NaN;
        else if (this.symbols.group) fullySanitizedValue = fullySanitizedValue.replaceAll(this.symbols.group, '');
        if (this.symbols.decimal) fullySanitizedValue = fullySanitizedValue.replace(this.symbols.decimal, '.');
        if (this.symbols.minusSign) fullySanitizedValue = fullySanitizedValue.replace(this.symbols.minusSign, '-');
        fullySanitizedValue = fullySanitizedValue.replace(this.symbols.numeral, this.symbols.index);
        if (this.options.style === 'percent') {
            // javascript is bad at dividing by 100 and maintaining the same significant figures, so perform it on the string before parsing
            let isNegative = fullySanitizedValue.indexOf('-');
            fullySanitizedValue = fullySanitizedValue.replace('-', '');
            fullySanitizedValue = fullySanitizedValue.replace('+', '');
            let index = fullySanitizedValue.indexOf('.');
            if (index === -1) index = fullySanitizedValue.length;
            fullySanitizedValue = fullySanitizedValue.replace('.', '');
            if (index - 2 === 0) fullySanitizedValue = `0.${fullySanitizedValue}`;
            else if (index - 2 === -1) fullySanitizedValue = `0.0${fullySanitizedValue}`;
            else if (index - 2 === -2) fullySanitizedValue = '0.00';
            else fullySanitizedValue = `${fullySanitizedValue.slice(0, index - 2)}.${fullySanitizedValue.slice(index - 2)}`;
            if (isNegative > -1) fullySanitizedValue = `-${fullySanitizedValue}`;
        }
        let newValue = fullySanitizedValue ? +fullySanitizedValue : NaN;
        if (isNaN(newValue)) return NaN;
        if (this.options.style === 'percent') {
            // extra step for rounding percents to what our formatter would output
            let options = {
                ...this.options,
                style: 'decimal',
                minimumFractionDigits: Math.min((this.options.minimumFractionDigits ?? 0) + 2, 20),
                maximumFractionDigits: Math.min((this.options.maximumFractionDigits ?? 0) + 2, 20)
            };
            return new $eb76cf4feb040f77$export$cd11ab140839f11d(this.locale, options).parse(new (0, $1dfb119a85e764e5$export$cc77c4ff7e8673c5)(this.locale, options).format(newValue));
        }
        // accounting will always be stripped to a positive number, so if it's accounting and has a () around everything, then we need to make it negative again
        if (this.options.currencySign === 'accounting' && $eb76cf4feb040f77$var$CURRENCY_SIGN_REGEX.test(value)) newValue = -1 * newValue;
        return newValue;
    }
    sanitize(value) {
        let isGroupSymbolAllowed = this.formatter.resolvedOptions().useGrouping;
        // If the value is only a unit and it matches one of the formatted numbers where the value is part of the unit and doesn't have any numerals, then
        // return the known value for that case.
        if (this.symbols.noNumeralUnits.length > 0 && this.symbols.noNumeralUnits.find((obj)=>obj.unit === value)) return this.symbols.noNumeralUnits.find((obj)=>obj.unit === value).value.toString();
        value = value.replace(this.symbols.literals, '');
        // Replace the ASCII minus sign with the minus sign used in the current locale
        // so that both are allowed in case the user's keyboard doesn't have the locale's minus sign.
        if (this.symbols.minusSign) value = value.replace('-', this.symbols.minusSign);
        // In arab numeral system, their decimal character is 1643, but most keyboards don't type that
        // instead they use the , (44) character or apparently the (1548) character.
        if (this.options.numberingSystem === 'arab') {
            if (this.symbols.decimal) {
                value = $eb76cf4feb040f77$var$replaceAll(value, ',', this.symbols.decimal);
                value = $eb76cf4feb040f77$var$replaceAll(value, String.fromCharCode(1548), this.symbols.decimal);
            }
            if (this.symbols.group && isGroupSymbolAllowed) value = $eb76cf4feb040f77$var$replaceAll(value, '.', this.symbols.group);
        }
        // In some locale styles, such as swiss currency, the group character can be a special single quote
        // that keyboards don't typically have. This expands the character to include the easier to type single quote.
        if (this.symbols.group === "\u2019" && value.includes("'") && isGroupSymbolAllowed) value = $eb76cf4feb040f77$var$replaceAll(value, "'", this.symbols.group);
        // On newer ICU versions, the special single quote has been normalized, so we need to backport.
        if (this.symbols.group === "'" && value.includes("\u2019") && isGroupSymbolAllowed) value = $eb76cf4feb040f77$var$replaceAll(value, "\u2019", this.symbols.group);
        // fr-FR group character is narrow non-breaking space, char code 8239 (U+202F), but that's not a key on the french keyboard,
        // so allow space and non-breaking space as a group char as well
        if (this.options.locale === 'fr-FR' && this.symbols.group && isGroupSymbolAllowed) {
            value = $eb76cf4feb040f77$var$replaceAll(value, ' ', this.symbols.group);
            value = $eb76cf4feb040f77$var$replaceAll(value, /\u00A0/g, this.symbols.group);
        }
        return value;
    }
    isValidPartialNumber(value, minValue = -Infinity, maxValue = Infinity) {
        let isGroupSymbolAllowed = this.formatter.resolvedOptions().useGrouping;
        value = this.sanitize(value);
        // Remove minus or plus sign, which must be at the start of the string.
        if (this.symbols.minusSign && value.startsWith(this.symbols.minusSign) && minValue < 0) value = value.slice(this.symbols.minusSign.length);
        else if (this.symbols.plusSign && value.startsWith(this.symbols.plusSign) && maxValue > 0) value = value.slice(this.symbols.plusSign.length);
        // Numbers that can't have any decimal values fail if a decimal character is typed
        if (this.symbols.decimal && value.indexOf(this.symbols.decimal) > -1 && this.options.maximumFractionDigits === 0) return false;
        // Remove numerals, groups, and decimals
        if (this.symbols.group && isGroupSymbolAllowed) value = $eb76cf4feb040f77$var$replaceAll(value, this.symbols.group, '');
        value = value.replace(this.symbols.numeral, '');
        if (this.symbols.decimal) value = value.replace(this.symbols.decimal, '');
        // The number is valid if there are no remaining characters
        return value.length === 0;
    }
}
const $eb76cf4feb040f77$var$nonLiteralParts = new Set([
    'decimal',
    'fraction',
    'integer',
    'minusSign',
    'plusSign',
    'group'
]);
// This list is derived from https://www.unicode.org/cldr/charts/49/supplemental/language_plural_rules.html#comparison and includes
// all unique numbers which we need to check in order to determine all the plural forms for a given locale.
// Run scripts/generateAllPlurals.mjs to generate this list.
const $eb76cf4feb040f77$var$pluralNumbers = [
    0,
    4,
    2,
    1,
    11,
    20,
    3,
    7,
    100,
    21,
    0.1,
    1.1
];
function $eb76cf4feb040f77$var$getSymbols(locale, formatter, intlOptions, originalOptions) {
    // formatter needs access to all decimal places in order to generate the correct literal strings for the plural set
    let symbolFormatter = new Intl.NumberFormat(locale, {
        ...intlOptions,
        // Resets so we get the full range of symbols
        minimumSignificantDigits: 1,
        maximumSignificantDigits: 21,
        roundingIncrement: 1,
        roundingPriority: 'auto',
        roundingMode: 'halfExpand',
        useGrouping: true
    });
    // Note: some locale's don't add a group symbol until there is a ten thousands place
    let allParts = symbolFormatter.formatToParts(-10000.111);
    let posAllParts = symbolFormatter.formatToParts(10000.111);
    let pluralParts = $eb76cf4feb040f77$var$pluralNumbers.map((n)=>symbolFormatter.formatToParts(n));
    // if the plural parts include a unit but no integer or fraction, then we need to add the unit to the special set
    let noNumeralUnits = pluralParts.map((p, i)=>{
        let unit = p.find((p)=>p.type === 'unit');
        if (unit && !p.some((p)=>p.type === 'integer' || p.type === 'fraction')) return {
            unit: unit.value,
            value: $eb76cf4feb040f77$var$pluralNumbers[i]
        };
        return null;
    }).filter((p)=>!!p);
    let minusSign = allParts.find((p)=>p.type === 'minusSign')?.value ?? '-';
    let plusSign = posAllParts.find((p)=>p.type === 'plusSign')?.value;
    // Safari does not support the signDisplay option, but our number parser polyfills it.
    // If no plus sign was returned, but the original options contained signDisplay, default to the '+' character.
    if (!plusSign && (originalOptions?.signDisplay === 'exceptZero' || originalOptions?.signDisplay === 'always')) plusSign = '+';
    // If maximumSignificantDigits is 1 (the minimum) then we won't get decimal characters out of the above formatters
    // Percent also defaults to 0 fractionDigits, so we need to make a new one that isn't percent to get an accurate decimal
    let decimalParts = new Intl.NumberFormat(locale, {
        ...intlOptions,
        minimumFractionDigits: 2,
        maximumFractionDigits: 2
    }).formatToParts(0.001);
    let decimal = decimalParts.find((p)=>p.type === 'decimal')?.value;
    let group = allParts.find((p)=>p.type === 'group')?.value;
    // this set is also for a regex, it's all literals that might be in the string we want to eventually parse that
    // don't contribute to the numerical value
    let allPartsLiterals = allParts.filter((p)=>!$eb76cf4feb040f77$var$nonLiteralParts.has(p.type)).map((p)=>$eb76cf4feb040f77$var$escapeRegex(p.value));
    let pluralPartsLiterals = pluralParts.flatMap((p)=>p.filter((p)=>!$eb76cf4feb040f77$var$nonLiteralParts.has(p.type)).map((p)=>$eb76cf4feb040f77$var$escapeRegex(p.value)));
    let sortedLiterals = [
        ...new Set([
            ...allPartsLiterals,
            ...pluralPartsLiterals
        ])
    ].sort((a, b)=>b.length - a.length);
    // Match both whitespace and formatting characters
    let literals = sortedLiterals.length === 0 ? new RegExp('\\p{White_Space}|\\p{Cf}', 'gu') : new RegExp(`${sortedLiterals.join('|')}|\\p{White_Space}|\\p{Cf}`, 'gu');
    // These are for replacing non-latn characters with the latn equivalent
    let numerals = [
        ...new Intl.NumberFormat(intlOptions.locale, {
            useGrouping: false
        }).format(9876543210)
    ].reverse();
    let indexes = new Map(numerals.map((d, i)=>[
            d,
            i
        ]));
    let numeral = new RegExp(`[${numerals.join('')}]`, 'g');
    let index = (d)=>String(indexes.get(d));
    return {
        minusSign: minusSign,
        plusSign: plusSign,
        decimal: decimal,
        group: group,
        literals: literals,
        numeral: numeral,
        numerals: numerals,
        index: index,
        noNumeralUnits: noNumeralUnits
    };
}
function $eb76cf4feb040f77$var$replaceAll(str, find, replace) {
    if (str.replaceAll) return str.replaceAll(find, replace);
    return str.split(find).join(replace);
}
function $eb76cf4feb040f77$var$escapeRegex(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}


export {$eb76cf4feb040f77$export$cd11ab140839f11d as NumberParser};
//# sourceMappingURL=NumberParser.mjs.map
