/*---------------------------------------------------------------------------------------------
 * 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
 */
// cSpell:ignore popout
import "./Frontstage.scss";
import produce from "immer";
import * as React from "react";
import { unstable_batchedUpdates } from "react-dom";
import { Logger, ProcessDetector } from "@itwin/core-bentley";
import { Rectangle, UiStateStorageStatus } from "@itwin/core-react";
import { ToolbarPopupAutoHideContext } from "@itwin/components-react";
import { addDockedToolSettings, addFloatingWidget, addPanelWidget, addRemovedTab, addTab, addTabToWidget, createLayoutStore, createNineZoneState, floatingWidgetBringToFront, FloatingWidgets, getTabLocation, getUniqueId, getWidgetPanelSectionId, isFloatingTabLocation, isPanelTabLocation, NineZone, NineZoneStateReducer, PreviewLayoutFeaturesProvider, PreviewMaximizedWidgetFeatureProvider, removeTab, useLayout, WidgetPanels, } from "@itwin/appui-layout-react";
import { useActiveFrontstageDef, } from "../frontstage/FrontstageDef";
import { StagePanelState, toPanelSide } from "../stagepanels/StagePanelDef";
import { UiFramework } from "../UiFramework";
import { useUiStateStorageHandler } from "../uistate/useUiStateStorage";
import { WidgetContent } from "./Content";
import { WidgetPanelsFrontstageContent } from "./FrontstageContent";
import { ModalFrontstageComposer, useActiveModalFrontstageInfo, } from "./ModalFrontstageComposer";
import { WidgetPanelsStatusBar } from "./StatusBar";
import { WidgetPanelsTab } from "./Tab";
import { WidgetPanelsToolbars } from "./Toolbars";
import { ToolSettingsContent, useShouldRenderDockedToolSettings, WidgetPanelsToolSettings, } from "./ToolSettings";
import { useEscapeSetFocusToHome } from "../hooks/useEscapeSetFocusToHome";
import { useSelector } from "react-redux";
import { useUiVisibility } from "../hooks/useUiVisibility";
import { IModelApp } from "@itwin/core-frontend";
import { FloatingWidget } from "./FloatingWidget";
import { InternalFrontstageManager } from "../frontstage/InternalFrontstageManager";
import { WidgetState } from "../widgets/WidgetState";
import { StagePanelSection } from "../stagepanels/StagePanelSection";
import { StagePanelLocation } from "../stagepanels/StagePanelLocation";
import { UiItemsManager } from "../ui-items-provider/UiItemsManager";
import { usePreviewFeatures } from "../preview/PreviewFeatures";
import classNames from "classnames";
function WidgetPanelsFrontstageComponent() {
    const activeModalFrontstageInfo = useActiveModalFrontstageInfo();
    const uiIsVisible = useUiVisibility();
    const previewFeatures = usePreviewFeatures();
    const previewContentAlwaysMaxSizeDockedClass = useShouldRenderDockedToolSettings() &&
        previewFeatures.contentAlwaysMaxSize &&
        "preview-contentAlwaysMaxSize-toolSettingsDocked";
    const previewContentAlwaysMaxSizeTopPanelClass = useLayout((state) => {
        return state.panels.top.widgets.length > 0;
    }) &&
        previewFeatures.contentAlwaysMaxSize &&
        "preview-contentAlwaysMaxSize-topPanelActive";
    return (React.createElement(PreviewLayoutFeaturesProvider, { ...previewFeatures },
        React.createElement(PreviewMaximizedWidgetFeatureProvider, { enabled: previewFeatures.enableMaximizedFloatingWidget },
            React.createElement(WidgetPanelsToolSettings, null),
            React.createElement(ToolbarPopupAutoHideContext.Provider, { value: !uiIsVisible },
                React.createElement(ModalFrontstageComposer, { stageInfo: activeModalFrontstageInfo }),
                React.createElement(WidgetPanels, { className: classNames("uifw-widgetPanels", previewContentAlwaysMaxSizeDockedClass, previewContentAlwaysMaxSizeTopPanelClass), centerContent: React.createElement(WidgetPanelsToolbars, null) },
                    React.createElement(WidgetPanelsFrontstageContent, null)),
                React.createElement(WidgetPanelsStatusBar, null),
                React.createElement(FloatingWidgets, null)))));
}
const widgetContent = React.createElement(WidgetContent, null);
const toolSettingsContent = React.createElement(ToolSettingsContent, null);
const widgetPanelsFrontstage = React.createElement(WidgetPanelsFrontstageComponent, null);
/** @internal */
export function useLayoutStore(frontstageDef) {
    const layoutStore = React.useMemo(() => createLayoutStore(frontstageDef === null || frontstageDef === void 0 ? void 0 : frontstageDef.nineZoneState), [frontstageDef]);
    React.useEffect(() => {
        return InternalFrontstageManager.onFrontstageNineZoneStateChangedEvent.addListener((args) => {
            if (args.frontstageDef !== frontstageDef)
                return;
            unstable_batchedUpdates(() => {
                // https://github.com/pmndrs/zustand/blob/main/docs/guides/event-handler-in-pre-react-18.md
                layoutStore.setState(args.state || defaultNineZone);
            });
        });
    }, [frontstageDef, layoutStore]);
    return layoutStore;
}
/** Update in-memory NineZoneState of newly activated frontstage with up to date size.
 * @internal
 */
export function useUpdateNineZoneSize(frontstageDef) {
    React.useEffect(() => {
        const size = InternalFrontstageManager.nineZoneSize;
        const state = frontstageDef.nineZoneState;
        if (!size || !state)
            return;
        frontstageDef.dispatch({
            type: "RESIZE",
            size: {
                height: size.height,
                width: size.width,
            },
        });
    }, [frontstageDef]);
}
/** @internal */
export function useNineZoneDispatch(frontstageDef) {
    return React.useMemo(() => frontstageDef.dispatch, [frontstageDef]);
}
/** @internal */
export function WidgetPanelsFrontstage() {
    const frontstageDef = useActiveFrontstageDef();
    const layout = useLayoutStore(frontstageDef);
    React.useEffect(() => {
        const triggerWidowCloseProcessing = () => {
            frontstageDef && frontstageDef.setIsApplicationClosing(true);
        };
        window.addEventListener("unload", triggerWidowCloseProcessing);
        return () => {
            window.removeEventListener("unload", triggerWidowCloseProcessing);
        };
    }, [frontstageDef]);
    if (!frontstageDef)
        return null;
    return (React.createElement(ActiveFrontstageDefProvider, { frontstageDef: frontstageDef, layout: layout }));
}
const defaultNineZone = createNineZoneState();
const tabElement = React.createElement(WidgetPanelsTab, null);
const floatingWidgetElement = React.createElement(FloatingWidget, null);
/** @internal */
export function ActiveFrontstageDefProvider({ frontstageDef, layout, }) {
    const dispatch = useNineZoneDispatch(frontstageDef);
    useUpdateNineZoneSize(frontstageDef);
    useSavedFrontstageState(frontstageDef);
    useSaveFrontstageSettings(frontstageDef, layout);
    useItemsManager(frontstageDef);
    const labels = useLabels();
    const uiIsVisible = useUiVisibility();
    const showWidgetIcon = useSelector((state) => {
        const frameworkState = state[UiFramework.frameworkStateKey];
        return !!frameworkState.configurableUiState.showWidgetIcon;
    });
    const autoCollapseUnpinnedPanels = useSelector((state) => {
        const frameworkState = state[UiFramework.frameworkStateKey];
        return !!frameworkState.configurableUiState.autoCollapseUnpinnedPanels;
    });
    const animateToolSettings = useSelector((state) => {
        const frameworkState = state[UiFramework.frameworkStateKey];
        return !!frameworkState.configurableUiState.animateToolSettings;
    });
    const useToolAsToolSettingsLabel = useSelector((state) => {
        const frameworkState = state[UiFramework.frameworkStateKey];
        return !!frameworkState.configurableUiState.useToolAsToolSettingsLabel;
    });
    useFrontstageManager(frontstageDef, useToolAsToolSettingsLabel);
    const handleKeyDown = useEscapeSetFocusToHome();
    return (React.createElement("div", { className: "uifw-widgetPanels-frontstage", onKeyDown: handleKeyDown, role: "presentation" },
        React.createElement(NineZone, { dispatch: dispatch, labels: labels, layout: layout, tab: tabElement, floatingWidget: floatingWidgetElement, showWidgetIcon: showWidgetIcon, autoCollapseUnpinnedPanels: autoCollapseUnpinnedPanels, toolSettingsContent: toolSettingsContent, widgetContent: widgetContent, animateDockedToolSettings: animateToolSettings, uiIsVisible: uiIsVisible }, widgetPanelsFrontstage)));
}
/** @internal */
export function useLabels() {
    return React.useMemo(() => ({
        dockToolSettingsTitle: UiFramework.translate("widget.tooltips.dockToolSettings"),
        moreWidgetsTitle: UiFramework.translate("widget.tooltips.moreWidgets"),
        moreToolSettingsTitle: UiFramework.translate("widget.tooltips.moreToolSettings"),
        pinPanelTitle: UiFramework.translate("widget.tooltips.pinPanel"),
        resizeGripTitle: UiFramework.translate("widget.tooltips.resizeGrip"),
        sendWidgetHomeTitle: UiFramework.translate("widget.tooltips.sendHome"),
        toolSettingsHandleTitle: UiFramework.translate("widget.tooltips.toolSettingsHandle"),
        unpinPanelTitle: UiFramework.translate("widget.tooltips.unpinPanel"),
        popoutActiveTab: UiFramework.translate("widget.tooltips.popoutActiveTab"),
    }), []);
}
function toTabArgs(widget) {
    var _a;
    const label = getWidgetLabel(widget.label);
    const args = {
        allowedPanelTargets: (_a = widget.allowedPanelTargets) === null || _a === void 0 ? void 0 : _a.map((location) => toPanelSide(location)),
        hideWithUiWhenFloating: !!widget.hideWithUiWhenFloating,
        canPopout: widget.canPopout,
        iconSpec: widget.iconSpec,
        isFloatingWidgetResizable: widget.isFloatingStateWindowResizable,
        label,
        preferredFloatingWidgetSize: widget.defaultFloatingSize,
        preferredPanelWidgetSize: widget.preferredPanelSize,
    };
    return args;
}
/** @internal */
export function addWidgets(state, widgets, side, widgetId) {
    const visibleWidgets = widgets.filter((w) => w.isVisible && w.defaultState !== WidgetState.Floating);
    if (visibleWidgets.length === 0)
        return state;
    const tabs = new Array();
    for (const widget of visibleWidgets) {
        state = addTab(state, widget.id, toTabArgs(widget));
        tabs.push(widget.id);
    }
    const activeWidget = visibleWidgets.find((widget) => widget.isActive);
    const minimized = false;
    // istanbul ignore else
    if ((activeWidget === null || activeWidget === void 0 ? void 0 : activeWidget.defaultState) !== WidgetState.Floating) {
        state = addPanelWidget(state, side, widgetId, tabs, {
            activeTabId: activeWidget ? activeWidget.id : tabs[0],
            minimized,
        });
    }
    return state;
}
/** @internal */
// istanbul ignore next
export function appendWidgets(state, widgetDefs, location, section) {
    const side = toPanelSide(location);
    // Add missing widget tabs.
    for (const widgetDef of widgetDefs) {
        const saveTab = state.tabs[widgetDef.id];
        const preferredPanelWidgetSize = saveTab
            ? saveTab.preferredPanelWidgetSize
            : widgetDef.preferredPanelSize;
        const preferredFloatingWidgetSize = saveTab
            ? saveTab.preferredFloatingWidgetSize
            : widgetDef.defaultFloatingSize;
        // if a defaultFloatingSize is specified then this mean the widget can't determine its intrinsic size and must be explicitly sized
        const userSized = saveTab
            ? saveTab.userSized ||
                (!!widgetDef.defaultFloatingSize &&
                    !!saveTab.preferredFloatingWidgetSize)
            : !!widgetDef.defaultFloatingSize;
        state = addTab(state, widgetDef.id, {
            ...toTabArgs(widgetDef),
            preferredFloatingWidgetSize,
            preferredPanelWidgetSize,
            userSized,
        });
        const savedTab = state.savedTabs.byId[widgetDef.id];
        if (savedTab) {
            state = addRemovedTab(state, widgetDef.id);
        }
        else if (widgetDef.isFloatingStateSupported &&
            widgetDef.defaultState === WidgetState.Floating) {
            const floatingContainerId = widgetDef.floatingContainerId
                ? widgetDef.floatingContainerId
                : getUniqueId();
            const widgetContainerId = getWidgetId(location, section);
            const preferredPosition = widgetDef.defaultFloatingPosition;
            if (floatingContainerId in state.widgets) {
                state = addTabToWidget(state, widgetDef.id, floatingContainerId);
            }
            else {
                const tab = state.tabs[widgetDef.id];
                const size = {
                    height: 200,
                    width: 300,
                    ...tab.preferredFloatingWidgetSize,
                };
                const preferredPoint = preferredPosition !== null && preferredPosition !== void 0 ? preferredPosition : {
                    x: (state.size.width - size.width) / 2,
                    y: (state.size.height - size.height) / 2,
                };
                const nzBounds = Rectangle.createFromSize(state.size);
                const bounds = Rectangle.createFromSize(size).offset(preferredPoint);
                const containedBounds = bounds.containIn(nzBounds);
                state = addFloatingWidget(state, floatingContainerId, [widgetDef.id], {
                    bounds: containedBounds.toProps(),
                    home: {
                        widgetId: widgetContainerId,
                        side,
                        widgetIndex: 0,
                    },
                    userSized,
                });
            }
        }
        else {
            const preferredWidgetIndex = section;
            const widgetPanelSectionId = getWidgetPanelSectionId(side, preferredWidgetIndex);
            if (widgetPanelSectionId in state.widgets) {
                state = addTabToWidget(state, widgetDef.id, widgetPanelSectionId);
            }
            else {
                const panel = state.panels[side];
                if (panel.widgets.length < panel.maxWidgetCount) {
                    state = addPanelWidget(state, side, widgetPanelSectionId, [
                        widgetDef.id,
                    ]);
                }
                else {
                    const widgetIndex = Math.min(preferredWidgetIndex, panel.widgets.length - 1);
                    const widgetId = panel.widgets[widgetIndex];
                    state = addTabToWidget(state, widgetDef.id, widgetId);
                }
            }
        }
        if (widgetDef.defaultState === WidgetState.Hidden) {
            state = NineZoneStateReducer(state, {
                type: "WIDGET_TAB_HIDE",
                id: widgetDef.id,
            });
        }
    }
    return state;
}
function processPopoutWidgets(state) {
    // Electron reopens popout windows w/o user interaction.
    if (ProcessDetector.isElectronAppFrontend) {
        return state;
    }
    // Restore popout widgets to main window.
    for (const id of state.popoutWidgets.allIds) {
        state = NineZoneStateReducer(state, {
            type: "POPOUT_WIDGET_SEND_BACK",
            id,
        });
    }
    return state;
}
/** Adds frontstageDef widgets that are missing in NineZoneState.
 * @internal
 */
export function addMissingWidgets(frontstageDef, initialState) {
    var _a, _b, _c, _d, _e, _f, _g, _h;
    let state = initialState;
    state = appendWidgets(state, determineNewWidgets((_a = frontstageDef.leftPanel) === null || _a === void 0 ? void 0 : _a.getPanelSectionDef(StagePanelSection.Start).widgetDefs, state), StagePanelLocation.Left, StagePanelSection.Start);
    state = appendWidgets(state, determineNewWidgets((_b = frontstageDef.leftPanel) === null || _b === void 0 ? void 0 : _b.getPanelSectionDef(StagePanelSection.End).widgetDefs, state), StagePanelLocation.Left, StagePanelSection.End);
    state = appendWidgets(state, determineNewWidgets((_c = frontstageDef.rightPanel) === null || _c === void 0 ? void 0 : _c.getPanelSectionDef(StagePanelSection.Start).widgetDefs, state), StagePanelLocation.Right, StagePanelSection.Start);
    state = appendWidgets(state, determineNewWidgets((_d = frontstageDef.rightPanel) === null || _d === void 0 ? void 0 : _d.getPanelSectionDef(StagePanelSection.End).widgetDefs, state), StagePanelLocation.Right, StagePanelSection.End);
    state = appendWidgets(state, determineNewWidgets((_e = frontstageDef.topPanel) === null || _e === void 0 ? void 0 : _e.getPanelSectionDef(StagePanelSection.Start).widgetDefs, state), StagePanelLocation.Top, StagePanelSection.Start);
    state = appendWidgets(state, determineNewWidgets((_f = frontstageDef.topPanel) === null || _f === void 0 ? void 0 : _f.getPanelSectionDef(StagePanelSection.End).widgetDefs, state), StagePanelLocation.Top, StagePanelSection.End);
    state = appendWidgets(state, determineNewWidgets((_g = frontstageDef.bottomPanel) === null || _g === void 0 ? void 0 : _g.getPanelSectionDef(StagePanelSection.Start).widgetDefs, state), StagePanelLocation.Bottom, StagePanelSection.Start);
    state = appendWidgets(state, determineNewWidgets((_h = frontstageDef.bottomPanel) === null || _h === void 0 ? void 0 : _h.getPanelSectionDef(StagePanelSection.End).widgetDefs, state), StagePanelLocation.Bottom, StagePanelSection.End);
    if (frontstageDef.toolSettings) {
        const newToolSettingsWidgets = determineNewWidgets([frontstageDef.toolSettings], state);
        for (const newToolSettingsWidget of newToolSettingsWidgets) {
            state = addTab(state, newToolSettingsWidget.id, toTabArgs(newToolSettingsWidget));
        }
    }
    return state;
}
/** Hide widgets with Hidden defaultState. */
function hideWidgets(state, frontstageDef) {
    const tabs = Object.values(state.tabs);
    for (const tab of tabs) {
        const widgetDef = frontstageDef.findWidgetDef(tab.id);
        if (!widgetDef)
            continue;
        if (widgetDef.defaultState === WidgetState.Hidden) {
            state = NineZoneStateReducer(state, {
                type: "WIDGET_TAB_HIDE",
                id: widgetDef.id,
            });
        }
        if (widgetDef.defaultState === WidgetState.Unloaded) {
            state = NineZoneStateReducer(state, {
                type: "WIDGET_TAB_UNLOAD",
                id: widgetDef.id,
            });
        }
    }
    return state;
}
/** Removes NineZoneState widgets that are missing in frontstageDef.
 * @internal
 */
function removeMissingWidgets(frontstageDef, state) {
    var _a;
    const toolSettingsTabId = (_a = state.toolSettings) === null || _a === void 0 ? void 0 : _a.tabId;
    for (const [, tab] of Object.entries(state.tabs)) {
        if (tab.id === toolSettingsTabId)
            continue;
        const widgetDef = frontstageDef.findWidgetDef(tab.id);
        if (widgetDef)
            continue;
        state = NineZoneStateReducer(state, {
            type: "WIDGET_TAB_HIDE",
            id: tab.id,
        });
        state = removeTab(state, tab.id);
    }
    return state;
}
function getWidgetLabel(label) {
    return label === "" ? "Widget" : label;
}
/** @internal */
export function getWidgetId(location, section) {
    switch (location) {
        case StagePanelLocation.Left: {
            if (section === StagePanelSection.Start)
                return "leftStart";
            return "leftEnd";
        }
        case StagePanelLocation.Right: {
            if (section === StagePanelSection.Start)
                return "rightStart";
            return "rightEnd";
        }
        case StagePanelLocation.Top: {
            if (section === StagePanelSection.Start)
                return "topStart";
            return "topEnd";
        }
        case StagePanelLocation.Bottom: {
            if (section === StagePanelSection.Start)
                return "bottomStart";
            return "bottomEnd";
        }
    }
}
/** @internal */
export function getPanelSectionWidgets(frontstageDef, location, section) {
    const stagePanel = frontstageDef.getStagePanelDef(location);
    const panelSection = stagePanel === null || stagePanel === void 0 ? void 0 : stagePanel.getPanelSectionDef(section);
    return (panelSection === null || panelSection === void 0 ? void 0 : panelSection.widgetDefs) || [];
}
/** @internal */
export function addPanelWidgets(state, frontstageDef, location) {
    const side = toPanelSide(location);
    const start = getWidgetId(location, StagePanelSection.Start);
    const startWidgets = getPanelSectionWidgets(frontstageDef, location, StagePanelSection.Start);
    state = addWidgets(state, determineNewWidgets(startWidgets, state), side, start);
    const end = getWidgetId(location, StagePanelSection.End);
    const endWidgets = getPanelSectionWidgets(frontstageDef, location, StagePanelSection.End);
    state = addWidgets(state, determineNewWidgets(endWidgets, state), side, end);
    return state;
}
/** @internal */
export function isFrontstageStateSettingResult(settingsResult) {
    if (settingsResult.status === UiStateStorageStatus.Success)
        return true;
    return false;
}
/** @internal */
export function initializePanel(state, frontstageDef, location) {
    const side = toPanelSide(location);
    state = addPanelWidgets(state, frontstageDef, location);
    const panelDef = frontstageDef.getStagePanelDef(location);
    state = produce(state, (draft) => {
        var _a, _b, _c, _d, _e, _f;
        const panel = draft.panels[side];
        panel.minSize = (_b = (_a = panelDef === null || panelDef === void 0 ? void 0 : panelDef.initialConfig) === null || _a === void 0 ? void 0 : _a.minSize) !== null && _b !== void 0 ? _b : panel.minSize;
        panel.pinned = (_c = panelDef === null || panelDef === void 0 ? void 0 : panelDef.pinned) !== null && _c !== void 0 ? _c : panel.pinned;
        panel.resizable = (_d = panelDef === null || panelDef === void 0 ? void 0 : panelDef.resizable) !== null && _d !== void 0 ? _d : panel.resizable;
        panel.maxSize = (_f = (_e = panelDef === null || panelDef === void 0 ? void 0 : panelDef.initialConfig) === null || _e === void 0 ? void 0 : _e.maxSize) !== null && _f !== void 0 ? _f : panel.maxSize;
    });
    if ((panelDef === null || panelDef === void 0 ? void 0 : panelDef.size) !== undefined) {
        state = NineZoneStateReducer(state, {
            type: "PANEL_SET_SIZE",
            side,
            size: panelDef.size,
        });
    }
    return state;
}
/** @internal */
export const stateVersion = 17; // this needs to be bumped when NineZoneState is changed (to recreate the layout).
/** @internal */
export function initializeNineZoneState(frontstageDef) {
    let nineZone = defaultNineZone;
    nineZone = produce(nineZone, (stateDraft) => {
        // istanbul ignore next
        if (!InternalFrontstageManager.nineZoneSize)
            return;
        stateDraft.size = {
            height: InternalFrontstageManager.nineZoneSize.height,
            width: InternalFrontstageManager.nineZoneSize.width,
        };
    });
    nineZone = initializePanel(nineZone, frontstageDef, StagePanelLocation.Left);
    nineZone = initializePanel(nineZone, frontstageDef, StagePanelLocation.Right);
    nineZone = initializePanel(nineZone, frontstageDef, StagePanelLocation.Top);
    nineZone = initializePanel(nineZone, frontstageDef, StagePanelLocation.Bottom);
    nineZone = produce(nineZone, (draft) => {
        var _a, _b, _c, _d;
        for (const [, panel] of Object.entries(draft.panels)) {
            const expanded = panel.widgets.find((widgetId) => draft.widgets[widgetId].minimized === false);
            const firstWidget = panel.widgets.length > 0 ? draft.widgets[panel.widgets[0]] : undefined;
            // istanbul ignore next
            if (!expanded && firstWidget) {
                firstWidget.minimized = false;
            }
        }
        draft.panels.left.collapsed = isPanelCollapsed((_a = frontstageDef.leftPanel) === null || _a === void 0 ? void 0 : _a.panelState);
        draft.panels.right.collapsed = isPanelCollapsed((_b = frontstageDef.rightPanel) === null || _b === void 0 ? void 0 : _b.panelState);
        draft.panels.top.collapsed = isPanelCollapsed((_c = frontstageDef.topPanel) === null || _c === void 0 ? void 0 : _c.panelState);
        draft.panels.bottom.collapsed = isPanelCollapsed((_d = frontstageDef.bottomPanel) === null || _d === void 0 ? void 0 : _d.panelState);
    });
    nineZone = addMissingWidgets(frontstageDef, nineZone);
    const toolSettingsWidgetDef = frontstageDef.toolSettings;
    if (toolSettingsWidgetDef) {
        const toolSettingsTabId = toolSettingsWidgetDef.id;
        nineZone = addDockedToolSettings(nineZone, toolSettingsTabId);
    }
    nineZone = hideWidgets(nineZone, frontstageDef);
    nineZone = processPopoutWidgets(nineZone);
    return nineZone;
}
/** Converts from saved NineZoneState to NineZoneState.
 * @note Restores toolSettings tab.
 * @note Restores tab labels.
 * @internal
 */
export function restoreNineZoneState(frontstageDef, saved) {
    let state = {
        ...saved,
        tabs: {},
    };
    for (const [, tab] of Object.entries(saved.tabs)) {
        // Re-add the placeholder tab in order to correctly remove it from the state if WidgetDef is not found.
        state = addTab(state, tab.id);
        const widgetDef = frontstageDef.findWidgetDef(tab.id);
        if (!widgetDef) {
            Logger.logInfo(UiFramework.loggerCategory(restoreNineZoneState), "WidgetDef is not found for saved tab.", () => ({
                frontstageId: frontstageDef.id,
                tabId: tab.id,
            }));
            state = removeTab(state, tab.id);
            continue;
        }
        state = produce(state, (draft) => {
            draft.tabs[tab.id] = {
                ...tab,
                ...toTabArgs(widgetDef),
                preferredFloatingWidgetSize: tab.preferredFloatingWidgetSize,
                preferredPanelWidgetSize: tab.preferredPanelWidgetSize,
            };
        });
    }
    state = produce(state, (draft) => {
        // remove center panel section if one is found in left or right panels
        const oldLeftMiddleIndex = saved.panels.left.widgets.findIndex((value) => value === "leftMiddle");
        // istanbul ignore next
        if (-1 !== oldLeftMiddleIndex) {
            draft.panels.left.widgets = saved.panels.left.widgets.filter((value) => value !== "leftMiddle");
            if ("leftEnd" in draft.widgets) {
                draft.widgets.leftMiddle.tabs.forEach((tab) => draft.widgets.leftEnd.tabs.push(tab));
            }
            else {
                draft.widgets.leftEnd = { ...draft.widgets.leftMiddle };
                delete draft.widgets.leftMiddle;
            }
        }
        const oldRightMiddleIndex = saved.panels.right.widgets.findIndex((value) => value === "rightMiddle");
        // istanbul ignore next
        if (-1 !== oldRightMiddleIndex) {
            draft.panels.right.widgets = saved.panels.right.widgets.filter((value) => value !== "rightMiddle");
            if ("rightEnd" in draft.widgets) {
                draft.widgets.rightMiddle.tabs.forEach((tab) => draft.widgets.rightEnd.tabs.push(tab));
            }
            else {
                draft.widgets.rightEnd = { ...draft.widgets.rightMiddle };
                delete draft.widgets.rightMiddle;
            }
        }
        return;
    });
    if (InternalFrontstageManager.nineZoneSize &&
        (0 !== InternalFrontstageManager.nineZoneSize.height ||
            0 !== InternalFrontstageManager.nineZoneSize.width)) {
        state = NineZoneStateReducer(state, {
            type: "RESIZE",
            size: {
                height: InternalFrontstageManager.nineZoneSize.height,
                width: InternalFrontstageManager.nineZoneSize.width,
            },
        });
    }
    state = addMissingWidgets(frontstageDef, state);
    state = hideWidgets(state, frontstageDef);
    state = processPopoutWidgets(state);
    return state;
}
/** Prepares NineZoneState to be saved.
 * @note Removes toolSettings tab.
 * @note Removes tab labels and iconSpec.
 * @internal
 */
export function packNineZoneState(state) {
    let packed = {
        ...state,
        tabs: {},
    };
    packed = produce(packed, (draft) => {
        for (const [, tab] of Object.entries(state.tabs)) {
            draft.tabs[tab.id] = {
                id: tab.id,
                preferredFloatingWidgetSize: tab.preferredFloatingWidgetSize,
                userSized: tab.userSized,
            };
        }
    });
    return packed;
}
/** @internal */
export function isPanelCollapsed(panelState) {
    return panelState !== StagePanelState.Open;
}
/** @internal */
export function showWidget(state, id) {
    const location = getTabLocation(state, id);
    if (!location)
        return state;
    state = produce(state, (draft) => {
        const widget = draft.widgets[location.widgetId];
        widget.activeTabId = id;
        widget.minimized = false;
        if (isPanelTabLocation(location)) {
            const panel = draft.panels[location.side];
            panel.collapsed = false;
        }
    });
    if (isFloatingTabLocation(location)) {
        state = floatingWidgetBringToFront(state, location.floatingWidgetId);
    }
    return state;
}
/** @internal */
export function expandWidget(state, id) {
    const location = getTabLocation(state, id);
    if (!location)
        return state;
    return produce(state, (draft) => {
        const widget = draft.widgets[location.widgetId];
        if (isPanelTabLocation(location)) {
            const panel = draft.panels[location.side];
            panel.splitterPercent =
                panel.widgets.findIndex((wId) => wId === location.widgetId) === 0
                    ? 100
                    : 0;
        }
        widget.minimized = false;
    });
}
/** @internal */
export function useSavedFrontstageState(frontstageDef) {
    const uiStateStorage = useUiStateStorageHandler();
    const uiStateStorageRef = React.useRef(uiStateStorage);
    const isMountedRef = React.useRef(false);
    React.useEffect(() => {
        isMountedRef.current = true;
        return () => {
            isMountedRef.current = false;
        };
    }, []);
    React.useEffect(() => {
        uiStateStorageRef.current = uiStateStorage;
    }, [uiStateStorage]);
    React.useEffect(() => {
        async function fetchFrontstageState() {
            // Switching to previously initialized frontstage.
            if (frontstageDef.nineZoneState) {
                frontstageDef.nineZoneState = processPopoutWidgets(frontstageDef.nineZoneState);
                return;
            }
            const id = frontstageDef.id;
            const version = frontstageDef.version;
            const settingResult = await uiStateStorageRef.current.getSetting(FRONTSTAGE_SETTINGS_NAMESPACE, getFrontstageStateSettingName(id));
            if (!isMountedRef.current)
                return;
            if (isFrontstageStateSettingResult(settingResult)) {
                const setting = settingResult.setting;
                if (setting.version === version &&
                    setting.stateVersion === stateVersion) {
                    frontstageDef.nineZoneState = restoreNineZoneState(frontstageDef, setting.nineZone);
                    return;
                }
            }
            frontstageDef.nineZoneState = initializeNineZoneState(frontstageDef);
        }
        void fetchFrontstageState();
    }, [frontstageDef]);
}
/** @internal */
export function useSaveFrontstageSettings(frontstageDef, store) {
    const uiSettingsStorage = useUiStateStorageHandler();
    const pendingSave = React.useRef(() => { });
    const saveSetting = React.useMemo(() => {
        const debounced = debounce(async (frontstage, state) => {
            const id = frontstage.id;
            const setting = {
                id,
                version: frontstage.version,
                stateVersion,
                nineZone: packNineZoneState(state),
            };
            await uiSettingsStorage.saveSetting(FRONTSTAGE_SETTINGS_NAMESPACE, getFrontstageStateSettingName(id), setting);
        }, 1000);
        const save = (frontstage, state) => {
            if (state.draggedTab)
                return;
            debounced(frontstage, state);
        };
        save.cancel = debounced.cancel;
        pendingSave.current = debounced.immediate;
        return save;
    }, [uiSettingsStorage]);
    React.useEffect(() => {
        return () => {
            pendingSave.current();
        };
    }, [frontstageDef]);
    React.useEffect(() => {
        return () => {
            saveSetting.cancel();
        };
    }, [saveSetting]);
    React.useEffect(() => {
        saveSetting(frontstageDef, store.getState());
    }, [saveSetting, frontstageDef, store]);
    React.useEffect(() => {
        return store.subscribe(() => {
            saveSetting(frontstageDef, store.getState());
        });
    }, [saveSetting, frontstageDef, store]);
}
/** @internal */
export const FRONTSTAGE_SETTINGS_NAMESPACE = "uifw-frontstageSettings";
/** @internal */
export function getFrontstageStateSettingName(frontstageId) {
    return `frontstageState[${frontstageId}]`;
}
// istanbul ignore next
function debounce(func, duration) {
    let timeout;
    let handler;
    const debounced = (...args) => {
        handler = () => {
            timeout = undefined;
            return func(...args);
        };
        window.clearTimeout(timeout);
        timeout = window.setTimeout(handler, duration);
    };
    /**
     * Will cancel the timeout without running the function.
     */
    debounced.cancel = () => {
        window.clearTimeout(timeout);
        timeout = undefined;
    };
    /**
     * If not already ran, will run the function immediately and remove the timeout.
     */
    debounced.immediate = () => {
        if (timeout === undefined)
            return;
        debounced.cancel();
        handler === null || handler === void 0 ? void 0 : handler();
    };
    return debounced;
}
/** @internal */
export function useFrontstageManager(frontstageDef, useToolAsToolSettingsLabel) {
    const uiSettingsStorage = useUiStateStorageHandler();
    React.useEffect(() => {
        return InternalFrontstageManager.onFrontstageRestoreLayoutEvent.addListener((args) => {
            // TODO: track restoring frontstages to support workflows:  i.e. prevent loading frontstage OR saving layout when delete is pending
            void uiSettingsStorage.deleteSetting(FRONTSTAGE_SETTINGS_NAMESPACE, getFrontstageStateSettingName(args.frontstageDef.id));
            if (frontstageDef.id === args.frontstageDef.id) {
                args.frontstageDef.nineZoneState =
                    initializeNineZoneState(frontstageDef);
            }
            else {
                args.frontstageDef.nineZoneState = undefined;
            }
        });
    }, [uiSettingsStorage, frontstageDef]);
    const defaultLabel = React.useMemo(() => UiFramework.translate("widget.labels.toolSettings"), []);
    React.useEffect(() => {
        const updateLabel = () => {
            var _a, _b;
            const state = frontstageDef.nineZoneState;
            if (!state)
                return;
            const toolSettingsTabId = (_a = state.toolSettings) === null || _a === void 0 ? void 0 : _a.tabId;
            if (!toolSettingsTabId)
                return;
            const toolId = UiFramework.frontstages.activeToolId;
            const label = useToolAsToolSettingsLabel
                ? ((_b = IModelApp.tools.find(toolId)) === null || _b === void 0 ? void 0 : _b.flyover) || defaultLabel
                : defaultLabel;
            frontstageDef.dispatch({
                type: "WIDGET_TAB_SET_LABEL",
                id: toolSettingsTabId,
                label,
            });
        };
        // Whenever the frontstageDef or the useTool... changes, keep the label up to date.
        updateLabel();
        const removeListenerFuncs = [];
        // If useTool... is true, listen for events to keep up the label up to date.
        if (useToolAsToolSettingsLabel) {
            removeListenerFuncs.push(UiFramework.frontstages.onFrontstageReadyEvent.addListener(updateLabel), InternalFrontstageManager.onFrontstageRestoreLayoutEvent.addListener(updateLabel), UiFramework.frontstages.onToolActivatedEvent.addListener(updateLabel), UiFramework.frontstages.onToolSettingsReloadEvent.addListener(updateLabel));
        }
        return () => {
            removeListenerFuncs.forEach((removeListener) => removeListener());
        };
    }, [frontstageDef, useToolAsToolSettingsLabel, defaultLabel]);
}
/** @internal */
// istanbul ignore next
export function useItemsManager(frontstageDef) {
    const refreshNineZoneState = (def) => {
        // Fired for both registered/unregistered. Update definitions and remove/add missing widgets.
        def.updateWidgetDefs();
        let state = def.nineZoneState;
        if (!state)
            return;
        state = addMissingWidgets(def, state);
        state = removeMissingWidgets(def, state);
        def.nineZoneState = state;
    };
    React.useEffect(() => {
        // Need to refresh anytime frontstageDef changes because uiItemsProvider may have added something to
        // another stage before it was possibly unloaded in this stage.
        refreshNineZoneState(frontstageDef);
    }, [frontstageDef]);
    React.useEffect(() => {
        return UiItemsManager.onUiProviderRegisteredEvent.addListener(() => {
            refreshNineZoneState(frontstageDef);
        });
    }, [frontstageDef]);
}
// istanbul ignore next
function determineNewWidgets(defs, state) {
    defs = defs || [];
    const uniqueDefs = defs.filter((def, index, array) => {
        return index === array.findIndex((def1) => def.id === def1.id);
    });
    return uniqueDefs.filter((def) => !(def.id in state.tabs));
}
//# sourceMappingURL=Frontstage.js.map