"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.FormatsProviderManager = exports.QuantityTypeFormatsProvider = exports.AlternateUnitLabelsRegistry = exports.QuantityType = void 0;
exports.getQuantityTypeKey = getQuantityTypeKey;
exports.isCustomQuantityTypeDefinition = isCustomQuantityTypeDefinition;
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 AlternateUnitLabels_1 = require("./AlternateUnitLabels");
// 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 {
    _alternateLabelRegistry = new Map();
    addAlternateLabels(key, ...labels) {
        [...labels].forEach((value) => this._alternateLabelRegistry.get(key)?.add(value));
    }
    constructor(defaultAlternates) {
        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;
}
/** CustomQuantityTypeDefinition type guard.
 * @public
*/
function isCustomQuantityTypeDefinition(item) {
    return !!item.isCompatibleFormatProps;
}
/** private class to hold standard quantity definitions as defined by QuantityType enum and implement QuantityTypeDefinition interface */
class StandardQuantityTypeDefinition {
    type;
    persistenceUnit;
    _labelKey;
    _descriptionKey;
    _label;
    _description;
    _key;
    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);
    }
}
/**
 * A default formatsProvider, that provides a limited set of [[FormatDefinition]], associated to a few [[KindOfQuantity]].
 * Maps each KindOfQuantity to a [[QuantityType]].
 * When retrieving a valid [[KindOfQuantity]], returns the [[FormatProps]] for the associated [[QuantityType]].
 * @internal
 */
class QuantityTypeFormatsProvider {
    onFormatsChanged = new core_bentley_1.BeEvent();
    _removeListeners = [];
    constructor() {
        this._removeListeners.push(IModelApp_1.IModelApp.quantityFormatter.onActiveFormattingUnitSystemChanged.addListener(() => {
            this.onFormatsChanged.raiseEvent({ formatsChanged: "all" });
        }));
    }
    [Symbol.dispose]() {
        this._removeListeners.forEach(listener => listener());
    }
    _kindOfQuantityMap = new Map([
        ["DefaultToolsUnits.LENGTH", QuantityType.Length],
        ["DefaultToolsUnits.ANGLE", QuantityType.Angle],
        ["DefaultToolsUnits.AREA", QuantityType.Area],
        ["DefaultToolsUnits.VOLUME", QuantityType.Volume],
        ["DefaultToolsUnits.LENGTH_COORDINATE", QuantityType.Coordinate],
        ["CivilUnits.STATION", QuantityType.Stationing],
        ["CivilUnits.LENGTH", QuantityType.LengthEngineering],
        ["AecUnits.LENGTH", QuantityType.LengthEngineering]
    ]);
    async getFormat(name, _system) {
        const quantityType = this._kindOfQuantityMap.get(name);
        if (!quantityType)
            return undefined;
        return IModelApp_1.IModelApp.quantityFormatter.getFormatPropsByQuantityType(quantityType);
    }
}
exports.QuantityTypeFormatsProvider = QuantityTypeFormatsProvider;
/**
 * An implementation of the [[FormatsProvider]] interface that forwards calls to getFormats to the underlying FormatsProvider.
 * Also fires the onFormatsChanged event when the underlying FormatsProvider fires its own onFormatsChanged event.
 * @internal
 */
class FormatsProviderManager {
    _formatsProvider;
    onFormatsChanged = new core_bentley_1.BeEvent();
    _removeProviderListener;
    constructor(_formatsProvider) {
        this._formatsProvider = _formatsProvider;
        this._removeProviderListener = this._formatsProvider.onFormatsChanged.addListener((args) => {
            this.onFormatsChanged.raiseEvent(args);
        });
    }
    async getFormat(name, system) {
        return this._formatsProvider.getFormat(name, system);
    }
    get formatsProvider() { return this; }
    set formatsProvider(formatsProvider) {
        this._removeProviderListener?.();
        this._formatsProvider = formatsProvider;
        this._removeProviderListener = this._formatsProvider.onFormatsChanged.addListener((args) => {
            this.onFormatsChanged.raiseEvent(args);
        });
        this.onFormatsChanged.raiseEvent({ formatsChanged: "all" });
    }
}
exports.FormatsProviderManager = FormatsProviderManager;
/** The QuantityFormatter class provides methods for formatting and parsing quantities. There are a set of standard quantity types
 * 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 {
    static _allUnitSystems = ["metric", "imperial", "usCustomary", "usSurvey"];
    _unitsProvider = new core_quantity_1.BasicUnitsProvider();
    _alternateUnitLabelsRegistry = new AlternateUnitLabelsRegistry((0, AlternateUnitLabels_1.getDefaultAlternateUnitLabels)());
    /** Registry containing available quantity type definitions. */
    _quantityTypeRegistry = new Map();
    /** Registry containing available FormatterSpec and ParserSpec, mapped by keys.
     * @beta
     */
    _formatSpecsRegistry = new Map();
    /** Active UnitSystem key - must be one of "imperial", "metric", "usCustomary", or "usSurvey". */
    _activeUnitSystem = "imperial";
    /** Map of FormatSpecs for all available QuantityTypes, keyed by quantity type */
    _activeFormatSpecsByType = new Map();
    /** Map of ParserSpecs for all available QuantityTypes, keyed by quantity type */
    _activeParserSpecsByType = new Map();
    /** Map of FormatSpecs that have been overriden from the default. */
    _overrideFormatPropsByUnitSystem = new Map();
    /** Optional object that gets called to store and retrieve format overrides. */
    _unitFormattingSettingsProvider;
    /** 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);
    }
    /** Called after the active unit system is changed.
    * The system will report the UnitSystemKey/name of the the system that was activated.
    */
    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`.
     */
    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. */
    onUnitsProviderChanged = new core_bentley_1.BeUiEvent();
    /** Fired after every reload path completes (initialization, unit system change, provider change, reinitialize).
     * This is the terminal "ready" signal — it fires once the QuantityFormatter has fully rebuilt its caches and is
     * ready to format/parse values. Subscribe to this event to know when formatting specs are available.
     *
     * Uses `BeUnorderedUiEvent` (Set-backed) so listeners can safely add/remove themselves during emit —
     * this is critical for `FormatSpecHandle` instances that subscribe/dispose at volume.
     * @beta
     */
    onFormattingReady = new core_bentley_1.BeUnorderedUiEvent();
    /** Event for formatting providers to register async work before the formatter signals ready.
     * Fires synchronously after each reload completes. Providers should call
     * `collector.addPendingWork(promise)` to register work. The formatter awaits all
     * registered work (with a 20-second default timeout) before emitting [[onFormattingReady]].
     *
     * Use this for **providers** that need async loading. Use [[onFormattingReady]] for
     * **consumers** that read specs.
     * @beta
     */
    onBeforeFormattingReady = new core_bentley_1.BeEvent();
    /** Whether the QuantityFormatter has completed at least one successful reload and is ready to format/parse.
     * @beta
     */
    get isReady() { return this._isReady; }
    /** A promise that resolves after the first successful initialization. This is one-shot — it resolves once
     * and stays resolved forever. For subsequent reloads, subscribe to [[onFormattingReady]].
     *
     * This promise never rejects. If the first initialization attempt fails, it stays pending until a
     * subsequent reload succeeds (e.g., triggered by `setUnitsProvider` or a format change). There is no
     * finite retry limit, so rejection would prematurely close the door on recovery.
     * @beta
     */
    get whenInitialized() { return this._initializedPromise; }
    _isReady = false;
    _hasEverBeenReady = false;
    _initializedPromise;
    _resolveInitialized;
    _reloadInFlight = false;
    _pendingReload;
    _deferredSystemChangedEmit;
    _removeFormatsProviderListener;
    /**
     * 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._initializedPromise = new Promise((resolve) => {
            this._resolveInitialized = resolve;
        });
        if (undefined !== showMetricOrUnitSystem) {
            if (typeof showMetricOrUnitSystem === "boolean")
                this._activeUnitSystem = showMetricOrUnitSystem ? "metric" : "imperial";
            else
                this._activeUnitSystem = showMetricOrUnitSystem;
        }
    }
    [Symbol.dispose]() {
        if (this._removeFormatsProviderListener) {
            this._removeFormatsProviderListener();
            this._removeFormatsProviderListener = undefined;
        }
    }
    /** Schedule an async reload. If no reload is in flight, runs immediately. If a reload is
     * already in flight, stores the intent as pending (latest-wins: only the last scheduled reload
     * is kept). `finalizeReload()` fires only when the queue is fully drained.
     *
     * **Await semantics:** When a reload is already in flight, the returned promise resolves
     * immediately *without* the requested reload having run. Callers that need to know when
     * the reload has actually completed should listen for `onFormattingReady` instead.
     *
     * Reload intents:
     * - `"full"` — rebuild entire registry + re-register provider listener (onInitialized, setUnitsProvider)
     * - `"formatsChanged"` — patch registry from provider + load maps (formatsChanged listener)
     * - `"activeSystem"` — reload format/parsing maps for current unit system (setActiveUnitSystem, reinitializeFormatAndParsingsMaps)
     * @internal
     */
    async scheduleReload(intent) {
        if (this._reloadInFlight) {
            // A reload is already running — queue this one (latest-wins)
            this._pendingReload = intent;
            return;
        }
        this._reloadInFlight = true;
        this._isReady = false;
        this._deferredSystemChangedEmit = undefined; // Clear stale deferred from prior cycle
        try {
            await this._executeReload(intent);
        }
        catch (err) {
            core_bentley_1.Logger.logError(`${FrontendLoggerCategory_1.FrontendLoggerCategory.Package}.QuantityFormatter`, core_bentley_1.BentleyError.getErrorMessage(err));
            this._reloadInFlight = false;
            // If there's a pending reload, still try to run it
            if (this._pendingReload) {
                const next = this._pendingReload;
                this._pendingReload = undefined;
                return this.scheduleReload(next);
            }
            // Restore prior ready state so stale-but-usable specs remain accessible
            if (this._hasEverBeenReady) {
                core_bentley_1.Logger.logWarning(`${FrontendLoggerCategory_1.FrontendLoggerCategory.Package}.QuantityFormatter`, "Reload failed — restoring previous ready state. Cached specs may be stale.");
                this._isReady = true;
            }
            return;
        }
        // Current reload succeeded — check if another was queued
        if (this._pendingReload) {
            const next = this._pendingReload;
            this._pendingReload = undefined;
            this._reloadInFlight = false;
            return this.scheduleReload(next);
        }
        // Queue is drained — finalize
        await this.finalizeReload();
        // A new reload may have been queued during the async finalizeReload window
        if (this._pendingReload) {
            const next = this._pendingReload;
            this._pendingReload = undefined;
            this._reloadInFlight = false;
            return this.scheduleReload(next);
        }
        this._reloadInFlight = false;
    }
    /** Execute the reload work for a given intent. All reload logic is centralized here.
     * @internal
     */
    async _executeReload(intent) {
        switch (intent.scope) {
            case "full":
                await this._reloadCore();
                break;
            case "formatsChanged": {
                const { args } = intent;
                await this._rebuildRegistryFromProvider(args);
                if (args.impliedUnitSystem && args.impliedUnitSystem !== this._activeUnitSystem) {
                    this._activeUnitSystem = args.impliedUnitSystem;
                }
                await this.loadFormatAndParsingMapsForSystem(this._activeUnitSystem);
                if (args.impliedUnitSystem) {
                    this._deferredSystemChangedEmit = { system: this._activeUnitSystem };
                }
                break;
            }
            case "activeSystem":
                await this.loadFormatAndParsingMapsForSystem(this._activeUnitSystem);
                if (intent.emitSystemChanged) {
                    this._deferredSystemChangedEmit = { system: this._activeUnitSystem };
                }
                break;
        }
    }
    /** Called when the reload queue is fully drained after a successful reload.
     * Sets `isReady` to true, resolves `whenInitialized` (one-shot), and emits ready events.
     * @internal
     */
    async finalizeReload() {
        // Phase 1: Let providers register async work
        const collector = new core_quantity_1.FormattingReadyCollector();
        this.onBeforeFormattingReady.raiseEvent(collector);
        await collector.awaitAll();
        // Phase 2: Signal ready to consumers
        this._isReady = true;
        this._hasEverBeenReady = true;
        this._resolveInitialized();
        this.onFormattingReady.emit();
        // Phase 3: Emit deferred unit-system-changed if the winning reload set one.
        // This fires after isReady === true so listeners can safely use the formatter.
        if (this._deferredSystemChangedEmit) {
            const args = this._deferredSystemChangedEmit;
            this._deferredSystemChangedEmit = undefined;
            this.onActiveFormattingUnitSystemChanged.emit(args);
        }
    }
    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
        for (const [_, entry] of this.quantityTypesRegistry) {
            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)) {
                    // We just verified that unitSystemKey is present.
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    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.scheduleReload({ scope: "full" });
    }
    /** Core reload logic — does all async I/O and cache rebuilding without events or state management.
     * @internal
     */
    async _reloadCore() {
        // Remove any existing listener before re-registering to avoid duplicates when called via setUnitsProvider.
        if (this._removeFormatsProviderListener) {
            this._removeFormatsProviderListener();
            this._removeFormatsProviderListener = undefined;
        }
        await this.initializeQuantityTypesRegistry();
        const initialKoQs = [["DefaultToolsUnits.LENGTH", "Units.M"], ["DefaultToolsUnits.ANGLE", "Units.RAD"], ["DefaultToolsUnits.AREA", "Units.SQ_M"], ["DefaultToolsUnits.VOLUME", "Units.CUB_M"], ["DefaultToolsUnits.LENGTH_COORDINATE", "Units.M"], ["CivilUnits.STATION", "Units.M"], ["CivilUnits.LENGTH", "Units.M"], ["AecUnits.LENGTH", "Units.M"]];
        for (const entry of initialKoQs) {
            for (const system of QuantityFormatter._allUnitSystems) {
                try {
                    await this.addFormattingSpecsToRegistry({ name: entry[0], persistenceUnitName: entry[1], system });
                }
                catch (err) {
                    core_bentley_1.Logger.logWarning(`${FrontendLoggerCategory_1.FrontendLoggerCategory.Package}.QuantityFormatter`, err.toString());
                }
            }
        }
        // Register formatsProvider listener that triggers a queued reload when formats change
        this._removeFormatsProviderListener = IModelApp_1.IModelApp.formatsProvider.onFormatsChanged.addListener((args) => {
            void this.scheduleReload({ scope: "formatsChanged", args });
        });
        // initialize default format and parsing specs
        await this.loadFormatAndParsingMapsForSystem();
    }
    /** Rebuild the formatting specs registry from the current formatsProvider based on changed args.
     * @internal
     */
    async _rebuildRegistryFromProvider(args) {
        if (args.formatsChanged === "all") {
            for (const [name, unitMap] of this._formatSpecsRegistry.entries()) {
                await this._rebuildRegistryForName(name, unitMap);
            }
        }
        else {
            for (const name of args.formatsChanged) {
                const unitMap = this._formatSpecsRegistry.get(name);
                if (unitMap) {
                    await this._rebuildRegistryForName(name, unitMap);
                }
            }
        }
    }
    /** Rebuild all system entries for a single KoQ name in the registry. */
    async _rebuildRegistryForName(name, unitMap) {
        let anySystemHadFormat = false;
        for (const system of QuantityFormatter._allUnitSystems) {
            const formatProps = await IModelApp_1.IModelApp.formatsProvider.getFormat(name, system);
            if (formatProps) {
                anySystemHadFormat = true;
                for (const [persistenceUnitName] of unitMap.entries()) {
                    await this.addFormattingSpecsToRegistry({ name, persistenceUnitName, formatProps, system });
                }
            }
            else {
                // Remove stale entries for this system
                for (const [, systemMap] of unitMap.entries()) {
                    systemMap.delete(system);
                }
            }
        }
        if (!anySystemHadFormat) {
            this._formatSpecsRegistry.delete(name);
        }
    }
    /** 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.
     * @note If the active primitive tool may be holding stale unit data, callers should follow this with
     * `IModelApp.toolAdmin.restartPrimitiveTool()` to allow the tool to reinitialize itself.
     */
    async setUnitsProvider(unitsProvider) {
        this._unitsProvider = unitsProvider;
        try {
            // force all cached data to be reinitialized
            await this.scheduleReload({ scope: "full" });
        }
        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;
        }
        this.onUnitsProviderChanged.emit();
    }
    /**
     * Async call typically used after an iModel is closed to reset the UnitsProvider to the default one
     * that does not require a Units schema.
     * @note If the active primitive tool may be holding stale unit data, callers should follow this with
     * `IModelApp.toolAdmin.restartPrimitiveTool()` to allow the tool to reinitialize itself.
     */
    async resetToUseInternalUnitsProvider() {
        // Coupled to createUnitsProvider() returning BasicUnitsProvider directly when no primary is set.
        if (this._unitsProvider instanceof core_quantity_1.BasicUnitsProvider)
            return;
        await this.setUnitsProvider(new core_quantity_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.scheduleReload({ scope: "activeSystem", emitSystemChanged: fireUnitSystemChanged });
        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.scheduleReload({ scope: "activeSystem", emitSystemChanged: true });
        // allow settings provider to store the change
        await this._unitFormattingSettingsProvider?.storeUnitSystemSetting({ 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);
                    const promise = this._unitFormattingSettingsProvider?.storeFormatOverrides({ typeKey, unitSystem: this.activeUnitSystem });
                    if (promise)
                        promises.push(promise);
                });
                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);
    }
    formatQuantity(args, spec) {
        if (typeof args === "number") {
            /** Format a quantity value. Default FormatterSpec implementation uses Formatter.formatQuantity. */
            const magnitude = args;
            if (spec)
                return spec.applyFormatting(magnitude);
            return magnitude.toString();
        }
        return this.formatQuantityAsync(args);
    }
    async formatQuantityAsync(args) {
        const { value, valueUnitName, kindOfQuantityName } = args;
        const formatProps = await IModelApp_1.IModelApp.formatsProvider.getFormat(kindOfQuantityName);
        if (!formatProps)
            return value.toString();
        const formatSpec = await this.createFormatterSpec({
            persistenceUnitName: valueUnitName,
            formatProps,
            formatName: kindOfQuantityName,
        });
        return formatSpec.applyFormatting(value);
    }
    parseToQuantityValue(args, parserSpec) {
        if (typeof args === "string") {
            /** Parse a quantity value. Default ParserSpec implementation uses ParserSpec.parseToQuantityValue. */
            const inString = args;
            if (parserSpec)
                return parserSpec.parseToQuantityValue(inString);
            return { ok: false, error: core_quantity_1.ParseError.InvalidParserSpec };
        }
        return this.parseToQuantityValueAsync(args);
    }
    async parseToQuantityValueAsync(args) {
        const { value, valueUnitName, kindOfQuantityName } = args;
        const formatProps = await IModelApp_1.IModelApp.formatsProvider.getFormat(kindOfQuantityName);
        if (!formatProps)
            return { ok: false, error: core_quantity_1.ParseError.InvalidParserSpec };
        const parserSpec = await this.createParserSpec({
            persistenceUnitName: valueUnitName,
            formatProps,
            formatName: kindOfQuantityName,
        });
        return parserSpec.parseToQuantityValue(value);
    }
    /**
     * 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) {
        const result = await this._unitsProvider.getConversion(fromUnit, toUnit);
        if (result.error)
            core_bentley_1.Logger.logWarning(`${FrontendLoggerCategory_1.FrontendLoggerCategory.Package}.quantityFormatter`, `Unit conversion from "${fromUnit.name}" to "${toUnit.name}" could not be resolved.`);
        return result;
    }
    /**
     * Creates a [[FormatterSpec]] for a given persistence unit name and format properties, using the [[UnitsProvider]] to resolve the persistence unit.
     * @beta
     * @param props - A [[CreateFormattingSpecProps]] interface.
     */
    async createFormatterSpec(props) {
        const { persistenceUnitName, formatProps, formatName } = props;
        const persistenceUnitProps = await this._unitsProvider.findUnitByName(persistenceUnitName);
        const format = await core_quantity_1.Format.createFromJSON(formatName ?? "temp", this._unitsProvider, formatProps);
        return core_quantity_1.FormatterSpec.create(`${format.name}_format_spec`, format, this._unitsProvider, persistenceUnitProps);
    }
    /**
     * Creates a [[ParserSpec]] for a given persistence unit name and format properties, using the [[UnitsProvider]] to resolve the persistence unit.
     * @beta
     * @param props - A [[CreateFormattingSpecProps]] object.
     */
    async createParserSpec(props) {
        const { persistenceUnitName, formatProps, formatName } = props;
        const persistenceUnitProps = await this._unitsProvider.findUnitByName(persistenceUnitName);
        const format = await core_quantity_1.Format.createFromJSON(formatName ?? "temp", this._unitsProvider, formatProps);
        return core_quantity_1.ParserSpec.create(format, this._unitsProvider, persistenceUnitProps);
    }
    /**
     * @beta
     * Returns a map of [[FormattingSpecEntry]] keyed by persistence unit for a given name, typically a KindOfQuantity full name.
     */
    getSpecsByName(name) {
        const unitMap = this._formatSpecsRegistry.get(name);
        if (!unitMap)
            return undefined;
        // Return active-system projection
        const result = new Map();
        for (const [persistenceUnit, systemMap] of unitMap) {
            const entry = systemMap.get(this._activeUnitSystem);
            if (entry)
                result.set(persistenceUnit, entry);
        }
        return result.size > 0 ? result : undefined;
    }
    /**
     * Returns a [[FormattingSpecEntry]] for a given name and persistence unit.
     * @beta
     */
    getSpecsByNameAndUnit(args) {
        const effectiveSystem = args.system ?? this._activeUnitSystem;
        return this._formatSpecsRegistry.get(args.name)?.get(args.persistenceUnitName)?.get(effectiveSystem);
    }
    /** Create a cacheable handle to formatting specs for a specific KoQ and persistence unit.
     * The handle auto-refreshes when the QuantityFormatter reloads. Call `dispose()` when done.
     *
     * @param koqName - The KindOfQuantity name (e.g., "DefaultToolsUnits.LENGTH")
     * @param persistenceUnit - The persistence unit name (e.g., "Units.M")
     * @returns A FormatSpecHandle that auto-updates on reload
     * @beta
     */
    getFormatSpecHandle(koqName, persistenceUnit, system) {
        return new core_quantity_1.FormatSpecHandle({ provider: this, name: koqName, persistenceUnitName: persistenceUnit, system });
    }
    /**
   * Populates the registry with a new FormatterSpec and ParserSpec entry for the given format name.
   * @beta
   */
    async addFormattingSpecsToRegistry(args) {
        const { name, persistenceUnitName } = args;
        const effectiveSystem = args.system ?? this._activeUnitSystem;
        let formatProps = args.formatProps;
        if (!formatProps) {
            formatProps = await IModelApp_1.IModelApp.formatsProvider.getFormat(name, effectiveSystem);
        }
        if (formatProps) {
            const formatterSpec = await this.createFormatterSpec({
                persistenceUnitName,
                formatProps,
                formatName: name,
            });
            const parserSpec = await this.createParserSpec({
                persistenceUnitName,
                formatProps,
                formatName: name,
            });
            if (!this._formatSpecsRegistry.has(name))
                this._formatSpecsRegistry.set(name, new Map());
            const unitMap = this._formatSpecsRegistry.get(name);
            if (!unitMap.has(persistenceUnitName))
                unitMap.set(persistenceUnitName, new Map());
            unitMap.get(persistenceUnitName).set(effectiveSystem, { formatterSpec, parserSpec });
        }
        else {
            throw new Error(`Unable to find format properties for ${name} with persistence unit ${persistenceUnitName}`);
        }
    }
}
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]feet2" },
        ],
    },
    {
        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]feet2" },
        ],
    },
    {
        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-2" },
        ],
    },
];
/** 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