"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module QuantityFormatting
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuantityFormatter = exports.isCustomQuantityTypeDefinition = exports.getQuantityTypeKey = exports.AlternateUnitLabelsRegistry = exports.QuantityType = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_quantity_1 = require("@itwin/core-quantity");
const FrontendLoggerCategory_1 = require("../common/FrontendLoggerCategory");
const IModelApp_1 = require("../IModelApp");
const BasicUnitsProvider_1 = require("./BasicUnitsProvider");
// cSpell:ignore FORMATPROPS FORMATKEY ussurvey uscustomary USCUSTOM
/**
 * Defines standard format types for tools that need to display measurements to user.
 * @public
 */
var QuantityType;
(function (QuantityType) {
    /** Length which is stored in meters. Typically formatted to display in meters or feet-inches based on active unit system. */
    QuantityType[QuantityType["Length"] = 1] = "Length";
    /** Angular value which is stored in radians. Typically formatted to display degrees or Degrees-Minute-Seconds based on active unit system. */
    QuantityType[QuantityType["Angle"] = 2] = "Angle";
    /** Area value store in meters squared. Typically formatted to display in meters squared or feet squared based on active unit system. */
    QuantityType[QuantityType["Area"] = 3] = "Area";
    /** Volume value which is stored in meters cubed. Typically formatted to display in meters cubed or feet cubed based on active unit system. */
    QuantityType[QuantityType["Volume"] = 4] = "Volume";
    /** LatLong is an angular value which is stored in radians. Typically formatted to display degrees or Degrees-Minute-Seconds based on active unit system. */
    QuantityType[QuantityType["LatLong"] = 5] = "LatLong";
    /** Coordinate/Location value which is stored in meters. Typically formatted to display in meters or feet based on active unit system. */
    QuantityType[QuantityType["Coordinate"] = 6] = "Coordinate";
    /** Stationing is a distance value stored in meters. Typically formatted to display `xxx+xx` or `xx+xxx` based on active unit system. */
    QuantityType[QuantityType["Stationing"] = 7] = "Stationing";
    /** LengthSurvey is a distance value stored in meters. Typically formatted to display in meters or US Survey Feet based on active unit system.. */
    QuantityType[QuantityType["LengthSurvey"] = 8] = "LengthSurvey";
    /** LengthEngineering is a distance value stored in meters. Typically formatted to display either meters or feet based on active unit system. */
    QuantityType[QuantityType["LengthEngineering"] = 9] = "LengthEngineering";
})(QuantityType || (exports.QuantityType = QuantityType = {}));
/**
 * Class that contains alternate Unit Labels. These labels are used when parsing strings to quantities.
 * One use case is to allow a "^", which is easily input, to be used to specify "°".
 * @internal
 */
class AlternateUnitLabelsRegistry {
    addAlternateLabels(key, ...labels) {
        [...labels].forEach((value) => this._alternateLabelRegistry.get(key)?.add(value));
    }
    constructor(defaultAlternates) {
        this._alternateLabelRegistry = new Map();
        if (defaultAlternates) {
            this._alternateLabelRegistry = defaultAlternates;
        }
    }
    getAlternateUnitLabels(unit) {
        const key = unit.name;
        const labels = this._alternateLabelRegistry.get(key);
        if (labels)
            return [...labels.values()];
        return undefined;
    }
}
exports.AlternateUnitLabelsRegistry = AlternateUnitLabelsRegistry;
/**
 * Function to return a QuantityTypeKey given either a QuantityType enum value or a string. This allows caching and
 * retrieving standard and custom quantity types.
 * @public
 */
function getQuantityTypeKey(type) {
    // For QuantityType enum values, build a string that shouldn't collide with anything a user may come up with
    if (typeof type === "number")
        return `QuantityTypeEnumValue-${type.toString()}`;
    return type;
}
exports.getQuantityTypeKey = getQuantityTypeKey;
/** CustomQuantityTypeDefinition type guard.
 * @public
*/
function isCustomQuantityTypeDefinition(item) {
    return !!item.isCompatibleFormatProps;
}
exports.isCustomQuantityTypeDefinition = isCustomQuantityTypeDefinition;
/** private class to hold standard quantity definitions as defined by QuantityType enum and implement QuantityTypeDefinition interface */
class StandardQuantityTypeDefinition {
    constructor(type, persistenceUnit, _labelKey, _descriptionKey) {
        this.type = type;
        this.persistenceUnit = persistenceUnit;
        this._labelKey = _labelKey;
        this._descriptionKey = _descriptionKey;
        this._key = getQuantityTypeKey(type);
    }
    get key() { return this._key; }
    get label() {
        if (!this._label) {
            this._label = IModelApp_1.IModelApp.localization.getLocalizedString(this._labelKey);
        }
        return this._label ?? "";
    }
    get description() {
        if (!this._description) {
            this._description = IModelApp_1.IModelApp.localization.getLocalizedString(this._descriptionKey);
        }
        return this._description ?? this.label;
    }
    /** Get a default format to show quantity in persistence unit with precision or 6 decimal places.  */
    getDefaultFormatPropsBySystem(requestedSystem) {
        // Fallback same as Format "DefaultRealU" in Formats ecschema
        const fallbackProps = {
            formatTraits: ["keepSingleZero", "keepDecimalPoint", "showUnitLabel"],
            precision: 6,
            type: "Decimal",
            uomSeparator: " ",
            decimalSeparator: ".",
        };
        const defaultUnitSystemData = DEFAULT_FORMATKEY_BY_UNIT_SYSTEM.find((value) => value.system === requestedSystem);
        if (defaultUnitSystemData) {
            const defaultFormatEntry = defaultUnitSystemData.entries.find((value) => value.type === this.key);
            if (defaultFormatEntry) {
                const defaultFormatPropsEntry = DEFAULT_FORMATPROPS.find((props) => props.key === defaultFormatEntry.formatKey);
                if (defaultFormatPropsEntry)
                    return defaultFormatPropsEntry.format;
            }
        }
        return fallbackProps;
    }
    async generateFormatterSpec(formatProps, unitsProvider) {
        const format = await core_quantity_1.Format.createFromJSON(this.key, unitsProvider, formatProps);
        return core_quantity_1.FormatterSpec.create(format.name, format, unitsProvider, this.persistenceUnit);
    }
    async generateParserSpec(formatProps, unitsProvider, alternateUnitLabelsProvider) {
        const format = await core_quantity_1.Format.createFromJSON(this.key, unitsProvider, formatProps);
        return core_quantity_1.ParserSpec.create(format, unitsProvider, this.persistenceUnit, alternateUnitLabelsProvider);
    }
}
/** Class that supports formatting quantity values into strings and parsing strings into quantity values. This class also maintains
 * the "active" unit system and caches FormatterSpecs and ParserSpecs for the "active" unit system to allow synchronous access to
 * parsing and formatting values. The support unit systems are defined by [[UnitSystemKey]] and is kept in synch with the unit systems
 * provided by the Presentation Manager on the backend. The QuantityFormatter contains a registry of quantity type definitions. These definitions implement
 * the [[QuantityTypeDefinition]] interface, which among other things, provide default [FormatProps]$(core-quantity), and provide methods
 * to generate both a [FormatterSpec]$(core-quantity) and a [ParserSpec]$(core-quantity). There are built-in quantity types that are
 * identified by the [[QuantityType]] enum. [[CustomQuantityTypeDefinition]] can be registered to extend the available quantity types available
 * by frontend tools. The QuantityFormatter also allows the default formats to be overriden.
 *
 * @public
 */
class QuantityFormatter {
    /** Set the settings provider and if not iModel specific initialize setting for user. */
    async setUnitFormattingSettingsProvider(provider) {
        this._unitFormattingSettingsProvider = provider;
        if (!provider.maintainOverridesPerIModel)
            await provider.loadOverrides(undefined);
    }
    /**
     * constructor
     * @param showMetricOrUnitSystem - Pass in `true` to show Metric formatted quantity values. Defaults to Imperial. To explicitly
     * set it to a specific unit system pass a UnitSystemKey.
     */
    constructor(showMetricOrUnitSystem) {
        this._unitsProvider = new BasicUnitsProvider_1.BasicUnitsProvider();
        this._alternateUnitLabelsRegistry = new AlternateUnitLabelsRegistry((0, BasicUnitsProvider_1.getDefaultAlternateUnitLabels)());
        /** Registry containing available quantity type definitions. */
        this._quantityTypeRegistry = new Map();
        /** Active UnitSystem key - must be one of "imperial", "metric", "usCustomary", or "usSurvey". */
        this._activeUnitSystem = "imperial";
        /** Map of FormatSpecs for all available QuantityTypes and the active Unit System */
        this._activeFormatSpecsByType = new Map();
        /** Map of ParserSpecs for all available QuantityTypes and the active Unit System */
        this._activeParserSpecsByType = new Map();
        /** Map of FormatSpecs that have been overriden from the default. */
        this._overrideFormatPropsByUnitSystem = new Map();
        /** Called after the active unit system is changed.
        * The system will report the UnitSystemKey/name of the the system that was activated.
        */
        this.onActiveFormattingUnitSystemChanged = new core_bentley_1.BeUiEvent();
        /** Called when the format of a QuantityType is overriden or the override is cleared. The string returned will
         * be a QuantityTypeKey generated by method `getQuantityTypeKey`.
         */
        this.onQuantityFormatsChanged = new core_bentley_1.BeUiEvent();
        /** Fired when the active UnitsProvider is updated. This will allow cached Formatter and Parser specs to be updated if necessary. */
        this.onUnitsProviderChanged = new core_bentley_1.BeUiEvent();
        if (undefined !== showMetricOrUnitSystem) {
            if (typeof showMetricOrUnitSystem === "boolean")
                this._activeUnitSystem = showMetricOrUnitSystem ? "metric" : "imperial";
            else
                this._activeUnitSystem = showMetricOrUnitSystem;
        }
    }
    getOverrideFormatPropsByQuantityType(quantityTypeKey, unitSystem) {
        const requestedUnitSystem = unitSystem ?? this.activeUnitSystem;
        const overrideMap = this._overrideFormatPropsByUnitSystem.get(requestedUnitSystem);
        if (!overrideMap)
            return undefined;
        return overrideMap.get(quantityTypeKey);
    }
    /** Method used to register all QuantityTypes defined in QuantityType enum. */
    async initializeQuantityTypesRegistry() {
        // QuantityType.Length
        const lengthUnit = await this.findUnitByName("Units.M");
        const lengthDefinition = new StandardQuantityTypeDefinition(QuantityType.Length, lengthUnit, "iModelJs:QuantityType.Length.label", "iModelJs:QuantityType.Length.description");
        this._quantityTypeRegistry.set(lengthDefinition.key, lengthDefinition);
        // QuantityType.LengthEngineering
        const lengthEngineeringDefinition = new StandardQuantityTypeDefinition(QuantityType.LengthEngineering, lengthUnit, "iModelJs:QuantityType.LengthEngineering.label", "iModelJs:QuantityType.LengthEngineering.description");
        this._quantityTypeRegistry.set(lengthEngineeringDefinition.key, lengthEngineeringDefinition);
        // QuantityType.Coordinate
        const coordinateDefinition = new StandardQuantityTypeDefinition(QuantityType.Coordinate, lengthUnit, "iModelJs:QuantityType.Coordinate.label", "iModelJs:QuantityType.Coordinate.description");
        this._quantityTypeRegistry.set(coordinateDefinition.key, coordinateDefinition);
        // QuantityType.Stationing
        const stationingDefinition = new StandardQuantityTypeDefinition(QuantityType.Stationing, lengthUnit, "iModelJs:QuantityType.Stationing.label", "iModelJs:QuantityType.Stationing.description");
        this._quantityTypeRegistry.set(stationingDefinition.key, stationingDefinition);
        // QuantityType.LengthSurvey
        const lengthSurveyDefinition = new StandardQuantityTypeDefinition(QuantityType.LengthSurvey, lengthUnit, "iModelJs:QuantityType.LengthSurvey.label", "iModelJs:QuantityType.LengthSurvey.description");
        this._quantityTypeRegistry.set(lengthSurveyDefinition.key, lengthSurveyDefinition);
        // QuantityType.Angle
        const radUnit = await this.findUnitByName("Units.RAD");
        const angleDefinition = new StandardQuantityTypeDefinition(QuantityType.Angle, radUnit, "iModelJs:QuantityType.Angle.label", "iModelJs:QuantityType.Angle.description");
        this._quantityTypeRegistry.set(angleDefinition.key, angleDefinition);
        // QuantityType.LatLong
        const latLongDefinition = new StandardQuantityTypeDefinition(QuantityType.LatLong, radUnit, "iModelJs:QuantityType.LatLong.label", "iModelJs:QuantityType.LatLong.description");
        this._quantityTypeRegistry.set(latLongDefinition.key, latLongDefinition);
        // QuantityType.Area
        const sqMetersUnit = await this.findUnitByName("Units.SQ_M");
        const areaDefinition = new StandardQuantityTypeDefinition(QuantityType.Area, sqMetersUnit, "iModelJs:QuantityType.Area.label", "iModelJs:QuantityType.Area.description");
        this._quantityTypeRegistry.set(areaDefinition.key, areaDefinition);
        // QuantityType.Volume
        const cubicMetersUnit = await this.findUnitByName("Units.CUB_M");
        const volumeDefinition = new StandardQuantityTypeDefinition(QuantityType.Volume, cubicMetersUnit, "iModelJs:QuantityType.Volume.label", "iModelJs:QuantityType.Volume.description");
        this._quantityTypeRegistry.set(volumeDefinition.key, volumeDefinition);
    }
    /** Asynchronous call to load Formatting and ParsingSpecs for a unit system. This method ends up caching FormatterSpecs and ParserSpecs
     *  so they can be quickly accessed.
     * @internal public for unit test usage
     */
    async loadFormatAndParsingMapsForSystem(systemType) {
        const systemKey = (undefined !== systemType) ? systemType : this._activeUnitSystem;
        const formatPropsByType = new Map();
        // load cache for every registered QuantityType
        [...this.quantityTypesRegistry.keys()].forEach((key) => {
            const entry = this.quantityTypesRegistry.get(key);
            formatPropsByType.set(entry, this.getFormatPropsByQuantityTypeEntryAndSystem(entry, systemKey));
        });
        for (const [entry, formatProps] of formatPropsByType) {
            await this.loadFormatAndParserSpec(entry, formatProps);
        }
    }
    getFormatPropsByQuantityTypeEntryAndSystem(quantityEntry, requestedSystem, ignoreOverrides) {
        if (!ignoreOverrides) {
            const overrideProps = this.getOverrideFormatPropsByQuantityType(quantityEntry.key, requestedSystem);
            if (overrideProps)
                return overrideProps;
        }
        return quantityEntry.getDefaultFormatPropsBySystem(requestedSystem);
    }
    async loadFormatAndParserSpec(quantityTypeDefinition, formatProps) {
        const formatterSpec = await quantityTypeDefinition.generateFormatterSpec(formatProps, this.unitsProvider);
        const parserSpec = await quantityTypeDefinition.generateParserSpec(formatProps, this.unitsProvider, this.alternateUnitLabelsProvider);
        this._activeFormatSpecsByType.set(quantityTypeDefinition.key, formatterSpec);
        this._activeParserSpecsByType.set(quantityTypeDefinition.key, parserSpec);
    }
    // repopulate formatSpec and parserSpec entries using only default format
    async loadDefaultFormatAndParserSpecForQuantity(typeKey) {
        const quantityTypeDefinition = this.quantityTypesRegistry.get(typeKey);
        if (!quantityTypeDefinition)
            throw new Error(`Unable to locate QuantityType by key ${typeKey}`);
        const defaultFormat = quantityTypeDefinition.getDefaultFormatPropsBySystem(this.activeUnitSystem);
        await this.loadFormatAndParserSpec(quantityTypeDefinition, defaultFormat);
    }
    async setOverrideFormatsByQuantityTypeKey(typeKey, overrideEntry) {
        // extract overrides and insert into appropriate override map entry
        Object.keys(overrideEntry).forEach((systemKey) => {
            const unitSystemKey = systemKey;
            const props = overrideEntry[unitSystemKey];
            if (props) {
                if (this._overrideFormatPropsByUnitSystem.has(unitSystemKey)) {
                    this._overrideFormatPropsByUnitSystem.get(unitSystemKey).set(typeKey, props);
                }
                else {
                    const newMap = new Map();
                    newMap.set(typeKey, props);
                    this._overrideFormatPropsByUnitSystem.set(unitSystemKey, newMap);
                }
            }
        });
        await this._unitFormattingSettingsProvider?.storeFormatOverrides({ typeKey, overrideEntry });
        const formatProps = this.getOverrideFormatPropsByQuantityType(typeKey, this.activeUnitSystem);
        if (formatProps) {
            const typeEntry = this.quantityTypesRegistry.get(typeKey);
            if (typeEntry) {
                await this.loadFormatAndParserSpec(typeEntry, formatProps);
                // trigger a message to let callers know the format has changed.
                this.onQuantityFormatsChanged.emit({ quantityType: typeKey });
            }
        }
    }
    /** Method called to clear override and restore defaults formatter and parser spec */
    async clearOverrideFormatsByQuantityTypeKey(type) {
        const unitSystem = this.activeUnitSystem;
        if (this.getOverrideFormatPropsByQuantityType(type, unitSystem)) {
            const overrideMap = this._overrideFormatPropsByUnitSystem.get(unitSystem);
            if (overrideMap && overrideMap.has(type)) {
                overrideMap.delete(type);
                await this._unitFormattingSettingsProvider?.storeFormatOverrides({ typeKey: type, unitSystem });
                await this.loadDefaultFormatAndParserSpecForQuantity(type);
                // trigger a message to let callers know the format has changed.
                this.onQuantityFormatsChanged.emit({ quantityType: type });
            }
        }
    }
    /** This method is called during IModelApp initialization to load the standard quantity types into the registry and to initialize the cache.
     * @internal
     */
    async onInitialized() {
        await this.initializeQuantityTypesRegistry();
        // initialize default format and parsing specs
        await this.loadFormatAndParsingMapsForSystem();
    }
    /** Return a map that serves as a registry of all standard and custom quantity types. */
    get quantityTypesRegistry() {
        return this._quantityTypeRegistry;
    }
    /** Return the class the contain map of all alternate labels for units. These alternate labels are used when parsing strings in quantity values. */
    get alternateUnitLabelsProvider() {
        return this._alternateUnitLabelsRegistry;
    }
    /**
     * Add one or more alternate labels for a unit - these labels are used during string parsing.
     * @param key UnitNameKey which comes from `UnitProps.name`
     * @param labels one or more unit labels
     */
    addAlternateLabels(key, ...labels) {
        this._alternateUnitLabelsRegistry.addAlternateLabels(key, ...labels);
        this.onUnitsProviderChanged.emit();
    }
    /** Get/Set the active UnitsProvider class. */
    get unitsProvider() {
        return this._unitsProvider;
    }
    set unitsProvider(unitsProvider) {
        this.setUnitsProvider(unitsProvider); // eslint-disable-line @typescript-eslint/no-floating-promises
    }
    /** async method to set a units provider and reload caches */
    async setUnitsProvider(unitsProvider) {
        this._unitsProvider = unitsProvider;
        try {
            // force all cached data to be reinitialized
            await IModelApp_1.IModelApp.quantityFormatter.onInitialized();
        }
        catch (err) {
            core_bentley_1.Logger.logWarning(`${FrontendLoggerCategory_1.FrontendLoggerCategory.Package}.quantityFormatter`, core_bentley_1.BentleyError.getErrorMessage(err), core_bentley_1.BentleyError.getErrorMetadata(err));
            core_bentley_1.Logger.logWarning(`${FrontendLoggerCategory_1.FrontendLoggerCategory.Package}.quantityFormatter`, "An exception occurred initializing the iModelApp.quantityFormatter with the given UnitsProvider. Defaulting back to the internal units provider.");
            // If there is a problem initializing with the given provider, default back to the internal provider
            await IModelApp_1.IModelApp.quantityFormatter.resetToUseInternalUnitsProvider();
            return;
        }
        // force default tool to start so any tool that may be using cached data will not be using bad data.
        if (IModelApp_1.IModelApp.toolAdmin)
            await IModelApp_1.IModelApp.toolAdmin.startDefaultTool();
        this.onUnitsProviderChanged.emit();
    }
    /** Async call typically used after IModel is closed to reset UnitsProvider to default one that does not require an Units schema. */
    async resetToUseInternalUnitsProvider() {
        if (this._unitsProvider instanceof BasicUnitsProvider_1.BasicUnitsProvider)
            return;
        await this.setUnitsProvider(new BasicUnitsProvider_1.BasicUnitsProvider());
    }
    /** Async call to register a CustomQuantityType and load the FormatSpec and ParserSpec for the new type. */
    async registerQuantityType(entry, replace) {
        if (!replace && this._quantityTypeRegistry.has(entry.key))
            return false;
        this._quantityTypeRegistry.set(entry.key, entry);
        // load any overrides so any saved overrides for the type being registered are applied
        if (this._unitFormattingSettingsProvider)
            await this._unitFormattingSettingsProvider.loadOverrides(undefined);
        if (entry.getDefaultFormatPropsBySystem) {
            const formatProps = entry.getDefaultFormatPropsBySystem(this.activeUnitSystem);
            await this.loadFormatAndParserSpec(entry, formatProps);
            return true;
        }
        return false;
    }
    /** Reinitialize caches. Typically called by active UnitFormattingSettingsProvider.
     * startDefaultTool - set to true to start the Default to instead of leaving any active tool pointing to cached unit data that is no longer valid
     * @public
     */
    async reinitializeFormatAndParsingsMaps(overrideFormatPropsByUnitSystem, unitSystemKey, fireUnitSystemChanged, startDefaultTool) {
        this._overrideFormatPropsByUnitSystem.clear();
        if (overrideFormatPropsByUnitSystem.size) {
            this._overrideFormatPropsByUnitSystem = overrideFormatPropsByUnitSystem;
        }
        unitSystemKey && (this._activeUnitSystem = unitSystemKey);
        await this.loadFormatAndParsingMapsForSystem(this._activeUnitSystem);
        fireUnitSystemChanged && this.onActiveFormattingUnitSystemChanged.emit({ system: this._activeUnitSystem });
        IModelApp_1.IModelApp.toolAdmin && startDefaultTool && await IModelApp_1.IModelApp.toolAdmin.startDefaultTool();
    }
    /** Set the Active unit system to one of the supported types. This will asynchronously load the formatter and parser specs for the activated system. */
    async setActiveUnitSystem(isImperialOrUnitSystem, restartActiveTool) {
        let systemType;
        if (typeof isImperialOrUnitSystem === "boolean")
            systemType = isImperialOrUnitSystem ? "imperial" : "metric";
        else
            systemType = isImperialOrUnitSystem;
        if (this._activeUnitSystem === systemType)
            return;
        this._activeUnitSystem = systemType;
        await this.loadFormatAndParsingMapsForSystem(systemType);
        // allow settings provider to store the change
        await this._unitFormattingSettingsProvider?.storeUnitSystemSetting({ system: systemType });
        // fire current event
        this.onActiveFormattingUnitSystemChanged.emit({ system: systemType });
        if (IModelApp_1.IModelApp.toolAdmin && restartActiveTool)
            return IModelApp_1.IModelApp.toolAdmin.startDefaultTool();
    }
    /** Retrieve the active [[UnitSystemKey]] which is used to determine what formats are to be used to display quantities */
    get activeUnitSystem() { return this._activeUnitSystem; }
    /** Clear any formatting override for specified quantity type, but only for the "active" Unit System. */
    async clearOverrideFormats(type) {
        await this.clearOverrideFormatsByQuantityTypeKey(this.getQuantityTypeKey(type));
    }
    /** Set formatting override for specified quantity type, but only for the "active" Unit System. */
    async setOverrideFormats(type, overrideEntry) {
        await this.setOverrideFormatsByQuantityTypeKey(this.getQuantityTypeKey(type), overrideEntry);
    }
    /** Set Override Format for a quantity type, but only in the "active" Unit System. */
    async setOverrideFormat(type, overrideFormat) {
        const typeKey = this.getQuantityTypeKey(type);
        let overrideEntry = {};
        if (this.activeUnitSystem === "imperial")
            overrideEntry = { imperial: overrideFormat };
        else if (this.activeUnitSystem === "metric")
            overrideEntry = { metric: overrideFormat };
        else if (this.activeUnitSystem === "usCustomary")
            overrideEntry = { usCustomary: overrideFormat };
        else
            overrideEntry = { usSurvey: overrideFormat };
        await this.setOverrideFormatsByQuantityTypeKey(typeKey, overrideEntry);
    }
    /** Clear formatting override for all quantity types, but only for the "active" Unit System. */
    async clearAllOverrideFormats() {
        if (0 === this._overrideFormatPropsByUnitSystem.size)
            return;
        if (this._overrideFormatPropsByUnitSystem.has(this.activeUnitSystem)) {
            const overrides = this._overrideFormatPropsByUnitSystem.get(this.activeUnitSystem);
            const typesRemoved = [];
            if (overrides && overrides.size) {
                const promises = new Array();
                overrides.forEach((_props, typeKey) => {
                    typesRemoved.push(typeKey);
                    promises.push(this._unitFormattingSettingsProvider?.storeFormatOverrides({ typeKey, unitSystem: this.activeUnitSystem }));
                });
                await Promise.all(promises);
            }
            if (typesRemoved.length) {
                const promises = new Array();
                typesRemoved.forEach((typeRemoved) => promises.push(this.loadDefaultFormatAndParserSpecForQuantity(typeRemoved)));
                await Promise.all(promises);
                // trigger a message to let callers know the format has changed.
                this.onQuantityFormatsChanged.emit({ quantityType: typesRemoved.join("|") });
            }
        }
    }
    /** Converts a QuantityTypeArg into a QuantityTypeKey/string value that can be used to lookup custom and standard quantity types. */
    getQuantityTypeKey(type) {
        return getQuantityTypeKey(type);
    }
    /** Return [[QuantityTypeDefinition]] if type has been registered. Standard QuantityTypes are automatically registered. */
    getQuantityDefinition(type) {
        return this.quantityTypesRegistry.get(this.getQuantityTypeKey(type));
    }
    /** Synchronous call to get a FormatterSpec of a QuantityType. If the FormatterSpec is not yet cached an undefined object is returned. The
     * cache is populated by the async call loadFormatAndParsingMapsForSystem.
     */
    findFormatterSpecByQuantityType(type, _unused) {
        return this._activeFormatSpecsByType.get(this.getQuantityTypeKey(type));
    }
    /** Asynchronous Call to get a FormatterSpec for a QuantityType. This formatter spec can be used to synchronously format quantities. */
    async generateFormatterSpecByType(type, formatProps) {
        const quantityTypeDefinition = this.quantityTypesRegistry.get(this.getQuantityTypeKey(type));
        if (quantityTypeDefinition)
            return quantityTypeDefinition.generateFormatterSpec(formatProps, this.unitsProvider);
        throw new Error(`Unable to generate FormatSpec for QuantityType ${type}`);
    }
    /** Asynchronous Call to get a FormatterSpec for a QuantityType and a Unit System. This formatter spec can be used to synchronously format quantities.
     * @param type        One of the built-in quantity types supported.
     * @param system      Requested unit system key. Note it is more efficient to use setActiveUnitSystem to set up formatters for all
     * quantity types of a unit system.
     * @return A FormatterSpec Promise.
     */
    async getFormatterSpecByQuantityTypeAndSystem(type, system) {
        const quantityKey = this.getQuantityTypeKey(type);
        const requestedSystem = system ?? this.activeUnitSystem;
        if (requestedSystem === this.activeUnitSystem) {
            const formatterSpec = this._activeFormatSpecsByType.get(quantityKey);
            if (formatterSpec)
                return formatterSpec;
        }
        const entry = this.quantityTypesRegistry.get(quantityKey);
        if (!entry)
            throw new Error(`Unable to find registered quantity type with key ${quantityKey}`);
        return entry.generateFormatterSpec(this.getFormatPropsByQuantityTypeEntryAndSystem(entry, requestedSystem), this.unitsProvider);
    }
    /** Asynchronous Call to get a FormatterSpec for a QuantityType.
     * @param type        One of the built-in quantity types supported.
     * @param isImperial  Argument to specify use of imperial or metric unit system. If left undefined the active unit system is used.
     * @return A FormatterSpec Promise.
     */
    async getFormatterSpecByQuantityType(type, isImperial) {
        let requestedSystem = this.activeUnitSystem;
        if (undefined !== isImperial)
            requestedSystem = isImperial ? "imperial" : "metric";
        return this.getFormatterSpecByQuantityTypeAndSystem(type, requestedSystem);
    }
    /** Synchronous call to get a ParserSpec for a QuantityType. If the ParserSpec is not yet cached an undefined object is returned. The
     * cache is populated when the active units system is set.
     */
    findParserSpecByQuantityType(type) {
        return this._activeParserSpecsByType.get(this.getQuantityTypeKey(type));
    }
    /** Asynchronous Call to get a ParserSpec for a QuantityType. If the UnitSystemKey is not specified the active Unit System is used. **/
    async getParserSpecByQuantityTypeAndSystem(type, system) {
        const quantityKey = this.getQuantityTypeKey(type);
        const requestedSystem = system ?? this.activeUnitSystem;
        if (requestedSystem === this.activeUnitSystem) {
            const parserSpec = this._activeParserSpecsByType.get(quantityKey);
            if (parserSpec)
                return parserSpec;
        }
        const entry = this.quantityTypesRegistry.get(quantityKey);
        if (!entry)
            throw new Error(`Unable to find registered quantity type with key ${quantityKey}`);
        return entry.generateParserSpec(this.getFormatPropsByQuantityTypeEntryAndSystem(entry, requestedSystem), this.unitsProvider);
    }
    /** Asynchronous Call to get a ParserSpec for a QuantityType.
     * @param type        One of the built-in quantity types supported.
     * @param isImperial  Argument to specify use of imperial or metric unit system. If left undefined the active unit system is used.
     * @return A FormatterSpec Promise.
     */
    async getParserSpecByQuantityType(type, isImperial) {
        let requestedSystem = this.activeUnitSystem;
        if (undefined !== isImperial)
            requestedSystem = isImperial ? "imperial" : "metric";
        return this.getParserSpecByQuantityTypeAndSystem(type, requestedSystem);
    }
    /** Generates a formatted string for a quantity given its format spec.
     * @param magnitude       The magnitude of the quantity.
     * @param formatSpec      The format specification. See methods getFormatterSpecByQuantityType and findFormatterSpecByQuantityType.
     * @return the formatted string.
     */
    formatQuantity(magnitude, formatSpec) {
        /** Format a quantity value. Default FormatterSpec implementation uses Formatter.formatQuantity. */
        if (formatSpec)
            return formatSpec.applyFormatting(magnitude);
        return magnitude.toString();
    }
    /** Parse input string into quantity given the ParserSpec
     * @param inString       The magnitude of the quantity.
     * @param parserSpec     The parse specification the defines the expected format of the string and the conversion to the output unit.
     * @return QuantityParseResult object containing either the parsed value or an error value if unsuccessful.
     */
    parseToQuantityValue(inString, parserSpec) {
        if (parserSpec)
            return parserSpec.parseToQuantityValue(inString);
        return { ok: false, error: core_quantity_1.ParseError.InvalidParserSpec };
    }
    /**
     * Get a UnitSystemKey from a string that may have been entered via a key-in. Supports different variation of
     * unit system names that have been used in the past.
     */
    getUnitSystemFromString(inputSystem, fallback) {
        switch (inputSystem.toLowerCase()) {
            case "metric":
            case "si":
                return "metric";
            case "imperial":
            case "british-imperial":
                return "imperial";
            case "uscustomary":
            case "us-customary":
            case "us":
                return "usCustomary";
            case "ussurvey":
            case "us-survey":
            case "survey":
                return "usSurvey";
            default:
                if (undefined !== fallback)
                    return fallback;
                break;
        }
        return "imperial";
    }
    /** Return true if the QuantityType is using an override format. */
    hasActiveOverride(type, checkOnlyActiveUnitSystem) {
        const quantityTypeKey = this.getQuantityTypeKey(type);
        if (checkOnlyActiveUnitSystem) {
            const overrides = this._overrideFormatPropsByUnitSystem.get(this.activeUnitSystem);
            if (overrides && overrides.has(quantityTypeKey))
                return true;
            return false;
        }
        for (const [_key, overrideMap] of this._overrideFormatPropsByUnitSystem) {
            if (overrideMap.has(quantityTypeKey))
                return true;
        }
        return false;
    }
    /** Get the cached FormatProps give a quantity type. If ignoreOverrides is false then if the format has been overridden
     * the overridden format is returned, else the standard format is returned.
     */
    getFormatPropsByQuantityType(quantityType, requestedSystem, ignoreOverrides) {
        const quantityEntry = this.quantityTypesRegistry.get(this.getQuantityTypeKey(quantityType));
        if (quantityEntry)
            return this.getFormatPropsByQuantityTypeEntryAndSystem(quantityEntry, requestedSystem ?? this.activeUnitSystem, ignoreOverrides);
        return undefined;
    }
    // keep following to maintain existing API of implementing UnitsProvider
    /** Find [UnitProp] for a specific unit label. */
    async findUnit(unitLabel, schemaName, phenomenon, unitSystem) {
        return this._unitsProvider.findUnit(unitLabel, schemaName, phenomenon, unitSystem);
    }
    /** Returns all defined units for the specified Unit Family/Phenomenon. */
    async getUnitsByFamily(phenomenon) {
        return this._unitsProvider.getUnitsByFamily(phenomenon);
    }
    /** Find [UnitProp] for a specific unit name. */
    async findUnitByName(unitName) {
        return this._unitsProvider.findUnitByName(unitName);
    }
    /** Returns data needed to convert from one Unit to another in the same Unit Family/Phenomenon. */
    async getConversion(fromUnit, toUnit) {
        return this._unitsProvider.getConversion(fromUnit, toUnit);
    }
}
exports.QuantityFormatter = QuantityFormatter;
// ========================================================================================================================================
// Default Data
// ========================================================================================================================================
const DEFAULT_FORMATKEY_BY_UNIT_SYSTEM = [
    {
        system: "metric", // PresentationUnitSystem.Metric,
        entries: [
            { type: getQuantityTypeKey(QuantityType.Length), formatKey: "[units:length]meter4" },
            { type: getQuantityTypeKey(QuantityType.Angle), formatKey: "[units:angle]degree2" },
            { type: getQuantityTypeKey(QuantityType.Area), formatKey: "[units:area]mSquared4" },
            { type: getQuantityTypeKey(QuantityType.Volume), formatKey: "[units:volume]mCubed4" },
            { type: getQuantityTypeKey(QuantityType.LatLong), formatKey: "[units:angle]dms" },
            { type: getQuantityTypeKey(QuantityType.Coordinate), formatKey: "[units:length]meter2" },
            { type: getQuantityTypeKey(QuantityType.Stationing), formatKey: "[units:length]m-sta2" },
            { type: getQuantityTypeKey(QuantityType.LengthSurvey), formatKey: "[units:length]meter4" },
            { type: getQuantityTypeKey(QuantityType.LengthEngineering), formatKey: "[units:length]meter4" },
        ],
    },
    {
        system: "imperial", // PresentationUnitSystem.BritishImperial,
        entries: [
            { type: getQuantityTypeKey(QuantityType.Length), formatKey: "[units:length]fi8" },
            { type: getQuantityTypeKey(QuantityType.Angle), formatKey: "[units:angle]dms2" },
            { type: getQuantityTypeKey(QuantityType.Area), formatKey: "[units:area]fSquared4" },
            { type: getQuantityTypeKey(QuantityType.Volume), formatKey: "[units:volume]fCubed4" },
            { type: getQuantityTypeKey(QuantityType.LatLong), formatKey: "[units:angle]dms" },
            { type: getQuantityTypeKey(QuantityType.Coordinate), formatKey: "[units:length]feet2" },
            { type: getQuantityTypeKey(QuantityType.Stationing), formatKey: "[units:length]f-sta2" },
            { type: getQuantityTypeKey(QuantityType.LengthSurvey), formatKey: "[units:length]f-survey-4-labeled" },
            { type: getQuantityTypeKey(QuantityType.LengthEngineering), formatKey: "[units:length]feet4" },
        ],
    },
    {
        system: "usCustomary", // PresentationUnitSystem.UsCustomary
        entries: [
            { type: getQuantityTypeKey(QuantityType.Length), formatKey: "[units:length]fi8" },
            { type: getQuantityTypeKey(QuantityType.Angle), formatKey: "[units:angle]dms2" },
            { type: getQuantityTypeKey(QuantityType.Area), formatKey: "[units:area]fSquared4" },
            { type: getQuantityTypeKey(QuantityType.Volume), formatKey: "[units:volume]fCubed4" },
            { type: getQuantityTypeKey(QuantityType.LatLong), formatKey: "[units:angle]dms" },
            { type: getQuantityTypeKey(QuantityType.Coordinate), formatKey: "[units:length]feet2" },
            { type: getQuantityTypeKey(QuantityType.Stationing), formatKey: "[units:length]f-sta2" },
            { type: getQuantityTypeKey(QuantityType.LengthSurvey), formatKey: "[units:length]f-survey-4" },
            { type: getQuantityTypeKey(QuantityType.LengthEngineering), formatKey: "[units:length]feet4" },
        ],
    },
    {
        system: "usSurvey", // PresentationUnitSystem.UsSurvey
        entries: [
            { type: getQuantityTypeKey(QuantityType.Length), formatKey: "[units:length]f-survey-4" },
            { type: getQuantityTypeKey(QuantityType.Angle), formatKey: "[units:angle]dms2" },
            { type: getQuantityTypeKey(QuantityType.Area), formatKey: "[units:area]usSurveyFtSquared4" },
            { type: getQuantityTypeKey(QuantityType.Volume), formatKey: "[units:volume]usSurveyFtCubed4" },
            { type: getQuantityTypeKey(QuantityType.LatLong), formatKey: "[units:angle]dms" },
            { type: getQuantityTypeKey(QuantityType.Coordinate), formatKey: "[units:length]f-survey-2" },
            { type: getQuantityTypeKey(QuantityType.Stationing), formatKey: "[units:length]f-survey-sta2" },
            { type: getQuantityTypeKey(QuantityType.LengthSurvey), formatKey: "[units:length]f-survey-4" },
            { type: getQuantityTypeKey(QuantityType.LengthEngineering), formatKey: "[units:length]f-survey-4" },
        ],
    },
];
/** List of default format definitions used by the Standard QuantityTypes. */
const DEFAULT_FORMATPROPS = [
    {
        key: "[units:length]meter4",
        description: "meters (labeled) 4 decimal places",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "m", name: "Units.M" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 4,
            type: "Decimal",
        },
    },
    {
        key: "[units:length]meter2",
        description: "meters (labeled) 2 decimal places",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "m", name: "Units.M" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 2,
            type: "Decimal",
        },
    },
    {
        key: "[units:length]feet4",
        description: "feet (labeled) 4 decimal places",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "ft", name: "Units.FT" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 4,
            type: "Decimal",
        },
    },
    {
        key: "[units:length]feet2",
        description: "feet (labeled) 2 decimal places",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "ft", name: "Units.FT" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 2,
            type: "Decimal",
        },
    },
    {
        key: "[units:length]fi8",
        description: "feet-inch 1/8 (labeled)",
        format: {
            composite: {
                includeZero: true,
                spacer: "-",
                units: [{ label: "'", name: "Units.FT" }, { label: "\"", name: "Units.IN" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 8,
            type: "Fractional",
            uomSeparator: "",
        },
    },
    {
        key: "[units:length]f-sta2",
        description: "stationing feet-2 decimal places ",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "ft", name: "Units.FT" }],
            },
            formatTraits: ["trailZeroes", "keepSingleZero"],
            stationOffsetSize: 2,
            precision: 2,
            type: "Station",
        },
    },
    {
        key: "[units:length]f-survey-sta2",
        description: "stationing feet-2 decimal places ",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "ft", name: "Units.US_SURVEY_FT" }],
            },
            formatTraits: ["trailZeroes", "keepSingleZero"],
            stationOffsetSize: 2,
            precision: 2,
            type: "Station",
        },
    },
    {
        key: "[units:length]m-sta2",
        description: "stationing meters-2 decimal places ",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "m", name: "Units.M" }],
            },
            formatTraits: ["trailZeroes", "keepSingleZero"],
            stationOffsetSize: 3,
            precision: 2,
            type: "Station",
        },
    },
    {
        key: "[units:length]f-survey-2",
        description: "survey feet (labeled)-2 decimal places ",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "ft", name: "Units.US_SURVEY_FT" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 2,
            type: "Decimal",
        },
    },
    {
        key: "[units:length]f-survey-4-labeled",
        description: "survey feet (labeled)-4 decimal places ",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "ft (US Survey)", name: "Units.US_SURVEY_FT" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 4,
            type: "Decimal",
        },
    },
    {
        key: "[units:length]f-survey-4",
        description: "survey feet (labeled)-4 decimal places ",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "ft", name: "Units.US_SURVEY_FT" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 4,
            type: "Decimal",
        },
    },
    {
        key: "[units:angle]degree2",
        description: "degrees (labeled) 2 decimal places",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "°", name: "Units.ARC_DEG" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 2,
            type: "Decimal",
            uomSeparator: "",
        },
    },
    {
        key: "[units:angle]dms",
        description: "degrees minutes seconds (labeled) 0 decimal places",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "°", name: "Units.ARC_DEG" }, { label: "'", name: "Units.ARC_MINUTE" }, { label: "\"", name: "Units.ARC_SECOND" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 4,
            type: "Decimal",
            uomSeparator: "",
        },
    },
    {
        key: "[units:angle]dms2",
        description: "degrees minutes seconds (labeled) 2 decimal places",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "°", name: "Units.ARC_DEG" }, { label: "'", name: "Units.ARC_MINUTE" }, { label: "\"", name: "Units.ARC_SECOND" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 2,
            type: "Decimal",
            uomSeparator: "",
        },
    },
    {
        key: "[units:area]mSquared4",
        description: "square meters (labeled) 4 decimal places",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "m²", name: "Units.SQ_M" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 4,
            type: "Decimal",
        },
    },
    {
        key: "[units:area]fSquared4",
        description: "square feet (labeled) 4 decimal places",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "ft²", name: "Units.SQ_FT" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 4,
            type: "Decimal",
        },
    },
    {
        key: "[units:area]usSurveyFtSquared4",
        description: "square survey feet (labeled) 4 decimal places",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "ft²", name: "Units.SQ_US_SURVEY_FT" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 4,
            type: "Decimal",
        },
    },
    {
        key: "[units:volume]mCubed4",
        description: "cubic meters (labeled) 4 decimal places",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "m³", name: "Units.CUB_M" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 4,
            type: "Decimal",
        },
    },
    {
        key: "[units:volume]fCubed4",
        description: "cubic feet (labeled) 4 decimal places",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "ft³", name: "Units.CUB_FT" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 4,
            type: "Decimal",
        },
    },
    {
        key: "[units:volume]usSurveyFtCubed4",
        description: "cubic survey feet (labeled) 4 decimal places",
        format: {
            composite: {
                includeZero: true,
                spacer: "",
                units: [{ label: "ft³", name: "Units.CUB_US_SURVEY_FT" }],
            },
            formatTraits: ["keepSingleZero", "showUnitLabel"],
            precision: 4,
            type: "Decimal",
        },
    },
];
//# sourceMappingURL=QuantityFormatter.js.map