/*---------------------------------------------------------------------------------------------
 * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
 * See LICENSE.md in the project root for license terms and full copyright notice.
 *--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Frontstage
 */
import { BeUiEvent, Logger } from "@itwin/core-bentley";
import { IModelApp, InteractiveTool } from "@itwin/core-frontend";
import { UiFramework } from "../UiFramework";
import { ToolInformation } from "../toolsettings/ToolInformation";
import { FrontstageDef } from "./FrontstageDef";
import { FrontstageProvider } from "./FrontstageProvider";
import { TimeTracker } from "../configurableui/TimeTracker";
import { UiItemsManager } from "../ui-items-provider/UiItemsManager";
/** Frontstage Manager class.
 * @internal
 */
export class InternalFrontstageManager {
    /** This should only be called within InternalFrontstageManager and its tests.
     *  @internal
     */
    static ensureToolInformationIsSet(toolId) {
        if (!InternalFrontstageManager._toolInformationMap.get(toolId))
            InternalFrontstageManager._toolInformationMap.set(toolId, 
            // eslint-disable-next-line deprecation/deprecation
            new ToolInformation(toolId));
    }
    /** Initializes the InternalFrontstageManager
     * @internal
     */
    static initialize() {
        if (this._initialized)
            return;
        IModelApp.toolAdmin.activeToolChanged.addListener((tool) => {
            // make sure tool settings properties are cached before creating ToolInformation
            UiFramework.toolSettings.clearToolSettingsData();
            if (tool instanceof InteractiveTool)
                UiFramework.toolSettings.initializeDataForTool(tool);
            // if the tool data is not already cached then see if there is data to cache
            InternalFrontstageManager.ensureToolInformationIsSet(tool.toolId);
            InternalFrontstageManager.setActiveTool(tool);
        });
        UiFramework.toolSettings.onSyncToolSettingsProperties.addListener(InternalFrontstageManager._handleSyncToolSettingsPropertiesEvent);
        UiFramework.toolSettings.onReloadToolSettingsProperties.addListener(InternalFrontstageManager._handleReloadToolSettingsEvent);
        IModelApp.viewManager.onSelectedViewportChanged.addListener(InternalFrontstageManager._handleSelectedViewportChanged);
        UiItemsManager.onUiProviderRegisteredEvent.addListener(() => {
            const frontstageDef = UiFramework.frontstages.activeFrontstageDef;
            if (!frontstageDef)
                return;
            frontstageDef.updateWidgetDefs();
        });
        this._initialized = true;
    }
    /** @internal */
    static get isInitialized() {
        return InternalFrontstageManager._initialized;
    }
    static set isInitialized(v) {
        InternalFrontstageManager._initialized = v;
    }
    /** Returns true if Frontstage is loading its controls. If false the Frontstage content and controls have been created. */
    static get isLoading() {
        return InternalFrontstageManager._isLoading;
    }
    /** @internal */
    static get nineZoneSize() {
        return InternalFrontstageManager._nineZoneSize;
    }
    static set nineZoneSize(size) {
        InternalFrontstageManager._nineZoneSize = size;
    }
    /** @internal */
    static get frontstageDefs() {
        return this._frontstageDefs;
    }
    /** Clears the Frontstage map.
     */
    static clearFrontstageDefs() {
        InternalFrontstageManager._frontstageDefs.clear();
        InternalFrontstageManager._activeFrontstageDef = undefined;
    }
    /** Clears the Frontstage Providers and the defs that may have been created from them.
     */
    static clearFrontstageProviders() {
        InternalFrontstageManager._frontstageProviders.clear();
        InternalFrontstageManager.clearFrontstageDefs();
    }
    static getFrontstageKey(frontstageId) {
        return frontstageId;
    }
    /** @internal */
    static clearFrontstageDefsForIModelId(iModelId) {
        if (!iModelId)
            return;
        const keysToRemove = [];
        InternalFrontstageManager._frontstageDefs.forEach((_, key) => {
            if (key.startsWith(`[${iModelId}]`))
                keysToRemove.push(key);
        });
        keysToRemove.forEach((keyValue) => {
            InternalFrontstageManager._frontstageDefs.delete(keyValue);
        });
    }
    static addFrontstageProvider(
    // eslint-disable-next-line deprecation/deprecation
    frontstageProvider) {
        const key = InternalFrontstageManager.getFrontstageKey(frontstageProvider.id);
        key && InternalFrontstageManager._frontstageDefs.delete(key);
        InternalFrontstageManager._frontstageProviders.set(frontstageProvider.id, frontstageProvider);
    }
    static addFrontstage(frontstage) {
        const key = InternalFrontstageManager.getFrontstageKey(frontstage.id);
        key && InternalFrontstageManager._frontstageDefs.delete(key);
        InternalFrontstageManager._frontstages.set(frontstage.id, frontstage);
    }
    /** Find a loaded Frontstage with a given id. If the id is not provided, the active Frontstage is returned.
     * If the FrontstageDef has not been cached by FrontstageDef then it will not be found. See async function
     * `getFrontstageDef` to get a FrontstageDef.
     * @param id  Id of the Frontstage to find
     * @returns  FrontstageDef with a given id if found, or undefined if not found.
     */
    static findFrontstageDef(id) {
        const key = InternalFrontstageManager.getFrontstageKey(id);
        const frontstageDef = InternalFrontstageManager._frontstageDefs.get(key);
        if (frontstageDef instanceof FrontstageDef)
            return frontstageDef;
        return undefined;
    }
    static findFrontstageProvider(id
    // eslint-disable-next-line deprecation/deprecation
    ) {
        if (!id) {
            return undefined;
        }
        const provider = InternalFrontstageManager._frontstageProviders.get(id);
        if (provider) {
            return provider;
        }
        const frontstage = InternalFrontstageManager._frontstages.get(id);
        if (frontstage) {
            // eslint-disable-next-line deprecation/deprecation
            return new (class extends FrontstageProvider {
                get id() {
                    return frontstage?.id ?? "";
                }
                frontstageConfig() {
                    return frontstage;
                }
            })();
        }
        return undefined;
    }
    /** Find a loaded Frontstage with a given id. If the id is not provided, the active Frontstage is returned. If
     * no cached FrontstageDef is found but a FrontstageProvider is registered a FrontstageDef will be created, cached, and
     * returned.
     * @param id  Id of the Frontstage to find
     * @returns  FrontstageDef with a given id if found, or undefined if not found.
     */
    static async getFrontstageDef(id) {
        if (!id)
            return InternalFrontstageManager.activeFrontstageDef;
        let frontstageDef = InternalFrontstageManager.findFrontstageDef(id);
        if (frontstageDef)
            return frontstageDef;
        const frontstageProvider = InternalFrontstageManager.findFrontstageProvider(id);
        if (frontstageProvider) {
            frontstageDef = await FrontstageDef.create(frontstageProvider);
            if (frontstageDef) {
                const key = InternalFrontstageManager.getFrontstageKey(frontstageDef.id);
                InternalFrontstageManager._frontstageDefs.set(key, frontstageDef);
            }
            return frontstageDef;
        }
        return undefined;
    }
    /** Gets the active FrontstageDef. If a Frontstage is not active, undefined is returned.
     * @return  Active FrontstageDef, or undefined if one is not active.
     */
    static get activeFrontstageDef() {
        return InternalFrontstageManager._activeFrontstageDef;
    }
    /** Gets the Id of the active FrontstageDef. If a Frontstage is not active, blank is returned.
     * @return  Id of the active FrontstageDef, or blank if one is not active.
     */
    static get activeFrontstageId() {
        const activeFrontstage = InternalFrontstageManager._activeFrontstageDef;
        return activeFrontstage ? activeFrontstage.id : "";
    }
    static hasFrontstage(frontstageId) {
        if (InternalFrontstageManager.findFrontstageDef(frontstageId))
            return true;
        if (InternalFrontstageManager.findFrontstageProvider(frontstageId))
            return true;
        return false;
    }
    /** Sets the active FrontstageDef give the stageId.
     * @param  frontstageId  Id of the Frontstage to set active.
     * @returns A Promise that is fulfilled when the Frontstage is ready.
     */
    static async setActiveFrontstage(frontstageId) {
        const frontstageDef = await InternalFrontstageManager.getFrontstageDef(frontstageId);
        if (!frontstageDef) {
            Logger.logError(UiFramework.loggerCategory(this), `setActiveFrontstage: Could not load a FrontstageDef with id of '${frontstageId}'`);
            return;
        }
        return InternalFrontstageManager.setActiveFrontstageDef(frontstageDef);
    }
    /** Sets the active FrontstageDef.
     * @param  frontstageDef  FrontstageDef to set active.
     * @returns A Promise that is fulfilled when the [[FrontstageDef]] is ready.
     */
    static async setActiveFrontstageDef(frontstageDef) {
        if (InternalFrontstageManager._activeFrontstageDef === frontstageDef)
            return;
        InternalFrontstageManager._isLoading = true;
        const deactivatedFrontstageDef = InternalFrontstageManager._activeFrontstageDef;
        if (deactivatedFrontstageDef) {
            await deactivatedFrontstageDef.onDeactivated();
            const timeTracker = deactivatedFrontstageDef.timeTracker;
            InternalFrontstageManager.onFrontstageDeactivatedEvent.emit({
                deactivatedFrontstageDef,
                activatedFrontstageDef: frontstageDef,
                totalTime: timeTracker.getTotalTimeSeconds(),
                engagementTime: timeTracker.getEngagementTimeSeconds(),
                idleTime: timeTracker.getIdleTimeSeconds(),
            });
        }
        InternalFrontstageManager._activeFrontstageDef = frontstageDef;
        if (frontstageDef) {
            await frontstageDef.onActivated();
            InternalFrontstageManager.onFrontstageActivatedEvent.emit({
                activatedFrontstageDef: frontstageDef,
                deactivatedFrontstageDef,
            });
            await frontstageDef.waitUntilReady();
            InternalFrontstageManager._isLoading = false;
            frontstageDef.onFrontstageReady();
            InternalFrontstageManager.onFrontstageReadyEvent.emit({ frontstageDef });
            UiFramework.visibility.handleFrontstageReady();
            await frontstageDef.setActiveContent();
        }
        InternalFrontstageManager._isLoading = false;
    }
    /** Deactivates the active FrontstageDef.
     */
    static async deactivateFrontstageDef() {
        await this.setActiveFrontstageDef(undefined);
    }
    /** Gets the Id of the active tool. If a tool is not active, blank is returned.
     * @return  Id of the active tool, or blank if one is not active.
     */
    static get activeToolId() {
        return InternalFrontstageManager._activeToolId;
    }
    /** Sets the active tool id */
    static setActiveToolId(toolId) {
        InternalFrontstageManager._activeToolId = toolId;
        const toolSettingsProvider = InternalFrontstageManager.activeToolSettingsProvider;
        // ensure the toolSettingsProvider is initialized before emitting onToolActivatedEvent
        if (toolSettingsProvider)
            toolSettingsProvider.initialize();
        InternalFrontstageManager.onToolActivatedEvent.emit({ toolId });
    }
    /** Sets the active tool */
    static setActiveTool(tool) {
        InternalFrontstageManager.setActiveToolId(tool.toolId);
        InternalFrontstageManager.onToolIconChangedEvent.emit({
            iconSpec: tool.iconSpec,
        });
    }
    /** Gets the active tool's [[ToolInformation]] */
    // eslint-disable-next-line deprecation/deprecation
    static get activeToolInformation() {
        return InternalFrontstageManager._toolInformationMap.get(InternalFrontstageManager.activeToolId);
    }
    /** Gets the Tool Setting React node of the active tool.
     * @return  Tool Setting React node of the active tool, or undefined if there is no active tool or Tool Settings for the active tool.
     * @internal
     */
    // eslint-disable-next-line deprecation/deprecation
    static get activeToolSettingsProvider() {
        const activeToolInformation = InternalFrontstageManager.activeToolInformation;
        return activeToolInformation?.toolUiProvider;
    }
    /** Sets the active layout, content group and active content.
     * @param contentLayoutDef  Content layout to make active
     * @param contentGroup  Content Group to make active
     */
    static async setActiveLayout(contentLayoutDef, contentGroup) {
        const activeFrontstageDef = InternalFrontstageManager.activeFrontstageDef;
        if (activeFrontstageDef) {
            InternalFrontstageManager._isLoading = false;
            activeFrontstageDef.setContentLayoutAndGroup(contentLayoutDef, contentGroup);
            InternalFrontstageManager.onContentLayoutActivatedEvent.emit({
                contentLayout: contentLayoutDef,
                contentGroup,
            });
            await activeFrontstageDef.waitUntilReady();
            InternalFrontstageManager._isLoading = false;
            await activeFrontstageDef.setActiveContent();
        }
    }
    /** Sets the active layout, content group and active content.
     * @param contentGroup  Content Group to make active
     */
    static async setActiveContentGroup(contentGroup) {
        const contentLayoutDef = UiFramework.content.layouts.getForGroup(contentGroup);
        await InternalFrontstageManager.setActiveLayout(contentLayoutDef, contentGroup);
    }
    /** Opens a modal Frontstage. Modal Frontstages can be stacked.
     * @param modalFrontstage  Information about the modal Frontstage
     */
    static openModalFrontstage(modalFrontstage) {
        InternalFrontstageManager.pushModalFrontstage(modalFrontstage);
    }
    static pushModalFrontstage(modalFrontstage) {
        const timeTracker = new TimeTracker();
        timeTracker.startTiming();
        const frontstageItem = {
            modalFrontstage,
            timeTracker,
        };
        InternalFrontstageManager._modalFrontstages.push(frontstageItem);
        InternalFrontstageManager.emitModalFrontstageChangedEvent();
    }
    /** Closes the top-most modal Frontstage.
     */
    static closeModalFrontstage() {
        if (InternalFrontstageManager._modalFrontstages.length > 0) {
            const topMostStageItem = InternalFrontstageManager._modalFrontstages[InternalFrontstageManager._modalFrontstages.length - 1];
            if (topMostStageItem.modalFrontstage.notifyCloseRequest)
                InternalFrontstageManager.onCloseModalFrontstageRequestedEvent.emit({
                    modalFrontstage: topMostStageItem.modalFrontstage,
                    stageCloseFunc: InternalFrontstageManager.popModalFrontstage,
                });
            else
                InternalFrontstageManager.popModalFrontstage();
        }
    }
    static popModalFrontstage() {
        const frontstageItem = InternalFrontstageManager._modalFrontstages.pop();
        if (frontstageItem) {
            const modalFrontstage = frontstageItem.modalFrontstage;
            const timeTracker = frontstageItem.timeTracker;
            timeTracker.stopTiming();
            InternalFrontstageManager.onModalFrontstageClosedEvent.emit({
                modalFrontstage,
                totalTime: timeTracker.getTotalTimeSeconds(),
                engagementTime: timeTracker.getEngagementTimeSeconds(),
                idleTime: timeTracker.getIdleTimeSeconds(),
            });
        }
        InternalFrontstageManager.emitModalFrontstageChangedEvent();
        UiFramework.visibility.handleFrontstageReady();
    }
    static emitModalFrontstageChangedEvent() {
        InternalFrontstageManager.onModalFrontstageChangedEvent.emit({
            modalFrontstageCount: InternalFrontstageManager.modalFrontstageCount,
        });
    }
    /** Updates the top-most modal Frontstage.
     */
    static updateModalFrontstage() {
        InternalFrontstageManager.emitModalFrontstageChangedEvent();
    }
    /** Gets the top-most modal Frontstage.
     * @returns Top-most modal Frontstage, or undefined if there is none.
     */
    static get activeModalFrontstage() {
        if (InternalFrontstageManager._modalFrontstages.length > 0) {
            const frontstageItem = InternalFrontstageManager._modalFrontstages[InternalFrontstageManager._modalFrontstages.length - 1];
            const modalFrontstage = frontstageItem.modalFrontstage;
            return modalFrontstage;
        }
        else {
            return undefined;
        }
    }
    /** Gets the number of modal Frontstages.
     * @returns Modal Frontstage count
     */
    static get modalFrontstageCount() {
        return InternalFrontstageManager._modalFrontstages.length;
    }
    /** Sets the active Navigation Aid via its Id.
     * @param navigationAidId  Id of the Navigation Aid to set as active
     * @param iModelConnection IModelConnection to query for view data
     */
    static setActiveNavigationAid(navigationAidId, iModelConnection) {
        InternalFrontstageManager.onNavigationAidActivatedEvent.emit({
            navigationAidId,
            iModelConnection,
        });
    }
    /** Sets the state of the widget with the given id
     * @param widgetId  Id of the Widget for which to set the state
     * @param state     New state of the widget
     * @returns true if the widget state was set successfully, or false if not.
     */
    static setWidgetState(widgetId, state) {
        const widgetDef = InternalFrontstageManager.findWidget(widgetId);
        if (widgetDef) {
            widgetDef.setWidgetState(state);
            return true;
        }
        else {
            Logger.logError(UiFramework.loggerCategory(this), `setWidgetState: Could not find Widget with id of '${widgetId}'`);
        }
        return false;
    }
    /** Finds a widget with the given id in the active frontstage
     * @param widgetId  Id of the Widget to find
     * @returns The WidgetDef with the given id, or undefined if not found.
     */
    static findWidget(widgetId) {
        const activeFrontstageDef = InternalFrontstageManager.activeFrontstageDef;
        if (activeFrontstageDef)
            return activeFrontstageDef.findWidgetDef(widgetId);
        return undefined;
    }
    /** Opens a nested Frontstage. Nested Frontstages can be stacked.
     * @param nestedFrontstage  Information about the nested Frontstage
     */
    static async openNestedFrontstage(nestedFrontstage) {
        if (InternalFrontstageManager.nestedFrontstageCount === 0)
            InternalFrontstageManager._activePrimaryFrontstageDef =
                InternalFrontstageManager._activeFrontstageDef;
        InternalFrontstageManager.pushNestedFrontstage(nestedFrontstage);
        await InternalFrontstageManager.setActiveFrontstageDef(nestedFrontstage);
    }
    static pushNestedFrontstage(nestedFrontstage) {
        InternalFrontstageManager._nestedFrontstages.push(nestedFrontstage);
    }
    /** Closes the top-most nested Frontstage.
     */
    static async closeNestedFrontstage() {
        InternalFrontstageManager.popNestedFrontstage();
        if (InternalFrontstageManager.nestedFrontstageCount > 0) {
            await InternalFrontstageManager.setActiveFrontstageDef(InternalFrontstageManager.activeNestedFrontstage);
        }
        else {
            await InternalFrontstageManager.setActiveFrontstageDef(InternalFrontstageManager._activePrimaryFrontstageDef);
            InternalFrontstageManager._activePrimaryFrontstageDef = undefined;
        }
    }
    static popNestedFrontstage() {
        InternalFrontstageManager._nestedFrontstages.pop();
    }
    /** Gets the top-most nested Frontstage.
     * @returns Top-most nested Frontstage, or undefined if there is none.
     */
    static get activeNestedFrontstage() {
        if (InternalFrontstageManager._nestedFrontstages.length > 0)
            return InternalFrontstageManager._nestedFrontstages[InternalFrontstageManager._nestedFrontstages.length - 1];
        return undefined;
    }
    /** Gets the number of nested Frontstages.
     * @returns Nested Frontstage count
     */
    static get nestedFrontstageCount() {
        return InternalFrontstageManager._nestedFrontstages.length;
    }
}
InternalFrontstageManager._initialized = false;
InternalFrontstageManager._isLoading = false;
InternalFrontstageManager._activeToolId = "";
InternalFrontstageManager._frontstageDefs = new Map();
InternalFrontstageManager._modalFrontstages = new Array();
// eslint-disable-next-line deprecation/deprecation
InternalFrontstageManager._frontstageProviders = new Map();
InternalFrontstageManager._frontstages = new Map();
InternalFrontstageManager._nineZoneSize = undefined;
InternalFrontstageManager._nestedFrontstages = new Array();
// eslint-disable-next-line deprecation/deprecation
InternalFrontstageManager._toolInformationMap = new Map();
InternalFrontstageManager._handleSyncToolSettingsPropertiesEvent = (args) => {
    InternalFrontstageManager.activeToolSettingsProvider &&
        InternalFrontstageManager.activeToolSettingsProvider.syncToolSettingsProperties(args);
};
// pass on ReloadToolSettingsEvent from ToolAdmin so they are treated by UiProviders
InternalFrontstageManager._handleReloadToolSettingsEvent = () => {
    if (InternalFrontstageManager.activeToolSettingsProvider) {
        InternalFrontstageManager.activeToolSettingsProvider.reloadPropertiesFromTool();
    }
};
/** Handles a Viewport change & sets the active view accordingly */
InternalFrontstageManager._handleSelectedViewportChanged = (args) => {
    if (args.current &&
        InternalFrontstageManager.activeFrontstageDef &&
        !InternalFrontstageManager.isLoading) {
        // eslint-disable-next-line deprecation/deprecation
        InternalFrontstageManager.activeFrontstageDef.setActiveViewFromViewport(args.current);
    }
};
/** Get Frontstage Deactivated event. */
InternalFrontstageManager.onFrontstageDeactivatedEvent = new BeUiEvent(); // eslint-disable-line deprecation/deprecation
/** Get Frontstage Activated event. */
InternalFrontstageManager.onFrontstageActivatedEvent = new BeUiEvent(); // eslint-disable-line deprecation/deprecation
/** Get Frontstage Activated event. */
InternalFrontstageManager.onFrontstageReadyEvent = new BeUiEvent(); // eslint-disable-line deprecation/deprecation
/** Get Modal Frontstage Changed event. */
InternalFrontstageManager.onModalFrontstageChangedEvent = new BeUiEvent(); // eslint-disable-line deprecation/deprecation
/** Get Modal Frontstage Closed event. */
InternalFrontstageManager.onModalFrontstageClosedEvent = new BeUiEvent(); // eslint-disable-line deprecation/deprecation
/** Get Modal Frontstage Requested Closed event.
 * @alpha
 */
InternalFrontstageManager.onCloseModalFrontstageRequestedEvent = new BeUiEvent(); // eslint-disable-line deprecation/deprecation
/** Get Tool Activated event. */
InternalFrontstageManager.onToolActivatedEvent = new BeUiEvent(); // eslint-disable-line deprecation/deprecation
/** Get ToolSetting Reload event. */
// eslint-disable-next-line deprecation/deprecation
InternalFrontstageManager.onToolSettingsReloadEvent = new BeUiEvent();
/** Get Tool Panel Opened event.
 * @internal
 */
// eslint-disable-next-line deprecation/deprecation
InternalFrontstageManager.onToolPanelOpenedEvent = new BeUiEvent();
/** Get Tool Icon Changed event. */
InternalFrontstageManager.onToolIconChangedEvent = new BeUiEvent(); // eslint-disable-line deprecation/deprecation
/** Get Content Layout Activated event. */
InternalFrontstageManager.onContentLayoutActivatedEvent = new BeUiEvent(); // eslint-disable-line deprecation/deprecation
/** Get Content Control Activated event. */
InternalFrontstageManager.onContentControlActivatedEvent = new BeUiEvent(); // eslint-disable-line deprecation/deprecation
/** Get Navigation Aid Activated event. */
InternalFrontstageManager.onNavigationAidActivatedEvent = new BeUiEvent(); // eslint-disable-line deprecation/deprecation
/** Get Widget State Changed event. */
InternalFrontstageManager.onWidgetStateChangedEvent = new BeUiEvent(); // eslint-disable-line deprecation/deprecation
/** @internal */
// eslint-disable-next-line deprecation/deprecation
InternalFrontstageManager.onWidgetDefsUpdatedEvent = new BeUiEvent();
/** @internal */
InternalFrontstageManager.onFrontstageNineZoneStateChangedEvent = 
// eslint-disable-next-line deprecation/deprecation
new BeUiEvent();
/** @internal */
InternalFrontstageManager.onFrontstageRestoreLayoutEvent = 
// eslint-disable-next-line deprecation/deprecation
new BeUiEvent();
/** @internal */
InternalFrontstageManager.onFrontstageWidgetsChangedEvent = 
// eslint-disable-next-line deprecation/deprecation
new BeUiEvent();
/** Get panel state changed event.
 * @alpha
 */
InternalFrontstageManager.onPanelStateChangedEvent = new BeUiEvent(); // eslint-disable-line deprecation/deprecation
/** Get panel pinned changed event.
 * @alpha
 */
InternalFrontstageManager.onPanelPinnedChangedEvent = 
// eslint-disable-next-line deprecation/deprecation
new BeUiEvent();
/** @internal */
InternalFrontstageManager.onPanelSizeChangedEvent = new BeUiEvent();
//# sourceMappingURL=InternalFrontstageManager.js.map