/*---------------------------------------------------------------------------------------------
 * 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 * as React from "react";
import { IModelApp } from "@itwin/core-frontend";
import { UiError } from "@itwin/appui-abstract";
import { Rectangle } from "@itwin/core-react";
import { dockWidgetContainer, floatWidget, getTabLocation, getWidgetLocation, isFloatingTabLocation, isPanelTabLocation, isPopoutTabLocation, isPopoutWidgetLocation, panelSides, popoutWidgetToChildWindow, } from "@itwin/appui-layout-react";
import { ContentGroupProvider } from "../content/ContentGroup";
import { StagePanelDef, StagePanelState, toPanelSide, } from "../stagepanels/StagePanelDef";
import { UiFramework } from "../UiFramework";
import { WidgetDef, WidgetType } from "../widgets/WidgetDef";
import { TimeTracker } from "../configurableui/TimeTracker";
import { PopoutWidget } from "../childwindow/PopoutWidget";
import { FrameworkStateReducer } from "../widget-panels/Frontstage";
import { assert, BentleyStatus, ProcessDetector } from "@itwin/core-bentley";
import { StageUsage } from "./StageUsage";
import { StagePanelLocation } from "../stagepanels/StagePanelLocation";
import { WidgetState } from "../widgets/WidgetState";
import { InternalFrontstageManager } from "./InternalFrontstageManager";
import { InternalContentDialogManager } from "../dialog/InternalContentDialogManager";
/** FrontstageDef class provides an API for a Frontstage.
 * @public
 */
export class FrontstageDef {
    constructor() {
        this._id = "";
        this._isStageClosing = false;
        this._isReady = false;
        this._isApplicationClosing = false;
        this._version = 0;
        this._timeTracker = new TimeTracker();
    }
    get id() {
        return this._id;
    }
    get usage() {
        return this._usage !== undefined ? this._usage : StageUsage.General;
    }
    get version() {
        return this._version;
    }
    get contentGroupProvider() {
        return this._contentGroupProvider;
    }
    get floatingContentControls() {
        return this._floatingContentControls;
    }
    get toolSettings() {
        return this._toolSettings;
    }
    get statusBar() {
        return this._statusBar;
    }
    get contentManipulation() {
        return this._contentManipulation;
    }
    get viewNavigation() {
        return this._viewNavigation;
    }
    get topPanel() {
        return this._topPanel;
    }
    get leftPanel() {
        return this._leftPanel;
    }
    get rightPanel() {
        return this._rightPanel;
    }
    get bottomPanel() {
        return this._bottomPanel;
    }
    get contentLayoutDef() {
        return this._contentLayoutDef;
    }
    get contentGroup() {
        return this._contentGroup;
    }
    get frontstageProvider() {
        return this._frontstageProvider;
    }
    toStagePanelLocation(side) {
        switch (side) {
            case "bottom":
                return StagePanelLocation.Bottom;
            case "left":
                return StagePanelLocation.Left;
            case "right":
                return StagePanelLocation.Right;
            case "top":
                return StagePanelLocation.Top;
        }
    }
    populateStateMaps(nineZone, panelMap, widgetMap) {
        for (const panelSide of panelSides) {
            const panel = nineZone.panels[panelSide];
            const location = this.toStagePanelLocation(panelSide);
            const panelDef = this.getStagePanelDef(location);
            if (panelDef) {
                const panelState = panel.collapsed
                    ? StagePanelState.Minimized
                    : StagePanelState.Open;
                panelMap.set(panelDef, panelState);
            }
            for (const widgetId of panel.widgets) {
                const widget = nineZone.widgets[widgetId];
                // istanbul ignore else
                if (widget) {
                    for (const tabId of widget.tabs) {
                        const widgetDef = this.findWidgetDef(tabId);
                        if (widgetDef) {
                            let widgetState = WidgetState.Open;
                            if (widget.minimized || tabId !== widget.activeTabId)
                                widgetState = WidgetState.Closed;
                            widgetMap.set(widgetDef, widgetState);
                        }
                    }
                }
            }
        }
        // istanbul ignore next
        for (const widgetId of nineZone.floatingWidgets.allIds) {
            const widget = nineZone.widgets[widgetId];
            if (widget) {
                for (const tabId of widget.tabs) {
                    const widgetDef = this.findWidgetDef(tabId);
                    if (widgetDef) {
                        let widgetState = WidgetState.Open;
                        if (widget.minimized || tabId !== widget.activeTabId)
                            widgetState = WidgetState.Closed;
                        widgetMap.set(widgetDef, widgetState);
                    }
                }
            }
        }
        const toolSettingsDef = this.toolSettings;
        if (toolSettingsDef) {
            if (!nineZone.toolSettings) {
                widgetMap.set(toolSettingsDef, WidgetState.Hidden);
            }
            else if (nineZone.toolSettings.type === "docked") {
                widgetMap.set(toolSettingsDef, WidgetState.Open);
            }
        }
    }
    triggerStateChangeEventForWidgetsAndPanels(state) {
        // istanbul ignore else
        if (!(this._isStageClosing || this._isApplicationClosing)) {
            if (state) {
                const originalPanelStateMap = new Map();
                const originalWidgetStateMap = new Map();
                const newPanelStateMap = new Map();
                const newWidgetStateMap = new Map();
                this._nineZoneState &&
                    this.populateStateMaps(this._nineZoneState, originalPanelStateMap, originalWidgetStateMap);
                this.populateStateMaps(state, newPanelStateMap, newWidgetStateMap);
                // set internal state value before triggering events
                this._nineZoneState = state;
                // now walk and trigger set state events
                newWidgetStateMap.forEach((stateValue, widgetDef) => {
                    const originalState = originalWidgetStateMap.get(widgetDef);
                    if (originalState !== stateValue) {
                        UiFramework.frontstages.onWidgetStateChangedEvent.emit({
                            widgetDef,
                            widgetState: stateValue,
                        });
                        widgetDef.onWidgetStateChanged();
                    }
                });
                newPanelStateMap.forEach((stateValue, panelDef) => {
                    const originalState = originalPanelStateMap.get(panelDef);
                    if (originalState !== stateValue) {
                        UiFramework.frontstages.onPanelStateChangedEvent.emit({
                            panelDef,
                            panelState: stateValue,
                        });
                    }
                });
                for (const panelSide of panelSides) {
                    const panel = state.panels[panelSide];
                    const location = this.toStagePanelLocation(panelSide);
                    const panelDef = this.getStagePanelDef(location);
                    if (panelDef) {
                        panelDef.size = panel.size;
                        panelDef.pinned = panel.pinned;
                    }
                }
            }
            else {
                this._nineZoneState = state;
            }
        }
    }
    /** @internal */
    get nineZoneState() {
        return this._nineZoneState;
    }
    set nineZoneState(state) {
        if (this._nineZoneState === state)
            return;
        if (!this._nineZoneState) {
            this._nineZoneState = state;
        }
        else {
            this.triggerStateChangeEventForWidgetsAndPanels(state);
        }
        // istanbul ignore next - don't trigger any side effects until stage "isReady"
        if (!(this._isStageClosing || this._isApplicationClosing) || this.isReady) {
            InternalFrontstageManager.onFrontstageNineZoneStateChangedEvent.emit({
                frontstageDef: this,
                state,
            });
        }
    }
    /** @internal */
    get savedWidgetDefs() {
        return this._savedWidgetDefs;
    }
    set savedWidgetDefs(widgets) {
        this._savedWidgetDefs = widgets;
    }
    /** @internal */
    get timeTracker() {
        return this._timeTracker;
    }
    /** Created a [[FrontstageDef]] and initialize it */
    static async create(provider) {
        const def = new FrontstageDef();
        def._frontstageProvider = provider;
        const config = provider.frontstageConfig();
        await def.initializeFromConfig(config);
        return def;
    }
    /** Handles when the Frontstage becomes activated */
    async _onActivated() { }
    /** Handles when the Frontstage becomes activated */
    async onActivated() {
        this.updateWidgetDefs();
        const provider = this._contentGroupProvider;
        if (provider && this._initialConfig) {
            this._contentGroup = await provider.contentGroup(this._initialConfig);
        }
        // istanbul ignore next
        if (!this._contentGroup)
            throw new UiError(UiFramework.loggerCategory(this), `onActivated: Content Group not defined`);
        this._contentLayoutDef = UiFramework.content.layouts.getForGroup(this._contentGroup);
        UiFramework.frontstages.onContentLayoutActivatedEvent.emit({
            contentLayout: this._contentLayoutDef,
            contentGroup: this._contentGroup,
        });
        this._timeTracker.startTiming();
        await this._onActivated();
    }
    /** Handles when the Frontstage becomes inactive */
    async _onDeactivated() { }
    /** Handles when the Frontstage becomes inactive */
    async onDeactivated() {
        for (const control of this._widgetControls) {
            control.onFrontstageDeactivated();
        }
        for (const control of this.contentControls) {
            control.onFrontstageDeactivated();
        }
        // istanbul ignore else
        if (this.contentGroup)
            this.contentGroup.onFrontstageDeactivated();
        // istanbul ignore next
        if (this.contentGroupProvider)
            await this.contentGroupProvider.onFrontstageDeactivated();
        this._timeTracker.stopTiming();
        this._isStageClosing = true; // this keeps widgets in child windows from automatically re-docking
        UiFramework.childWindows.closeAll();
        if (this._floatingContentControls) {
            InternalContentDialogManager.closeAll();
            this._floatingContentControls = undefined;
        }
        if (this._toolAdminDefaultToolId) {
            IModelApp.toolAdmin.defaultToolId = this._toolAdminDefaultToolId;
            this._toolAdminDefaultToolId = undefined;
        }
        await this._onDeactivated();
        this._isStageClosing = false;
    }
    /** @internal */
    setIsApplicationClosing(value) {
        this._isApplicationClosing = value;
    }
    /** Returns once the contained widgets and content controls are ready to use */
    async waitUntilReady() {
        this._isReady = false;
        // create an array of control-ready promises
        const controlReadyPromises = new Array();
        this._widgetControls.forEach((control) => {
            controlReadyPromises.push(control.isReady);
        });
        // istanbul ignore else
        if (this.contentLayoutDef) {
            const usedContentIndexes = this.contentLayoutDef.getUsedContentIndexes();
            this.contentControls.forEach((control, index) => {
                // istanbul ignore else
                if (usedContentIndexes.includes(index))
                    controlReadyPromises.push(control.isReady);
            });
        }
        await Promise.all(controlReadyPromises);
        // Frontstage ready
        this._isReady = true;
    }
    /** Handles when the Frontstage becomes active */
    _onFrontstageReady() { }
    /** Handles when the Frontstage becomes active */
    onFrontstageReady() {
        for (const control of this._widgetControls) {
            control.onFrontstageReady();
        }
        for (const control of this.contentControls) {
            control.onFrontstageReady();
        }
        // istanbul ignore else
        if (this.contentGroup)
            this.contentGroup.onFrontstageReady();
        if (IModelApp.toolAdmin && IModelApp.viewManager && this._initialConfig) {
            const defaultTool = this._initialConfig.defaultTool;
            if (defaultTool) {
                this._toolAdminDefaultToolId = IModelApp.toolAdmin.defaultToolId;
                IModelApp.toolAdmin.defaultToolId = defaultTool;
                void IModelApp.tools.run(defaultTool);
            }
            else {
                void IModelApp.toolAdmin.startDefaultTool();
            }
        }
        this._onFrontstageReady();
    }
    /** Sets the Content Layout and Content Group */
    setContentLayoutAndGroup(contentLayoutDef, contentGroup) {
        this._contentLayoutDef = contentLayoutDef;
        this._contentGroup = contentGroup;
    }
    /** Sets the active view content control to the default or first */
    async setActiveContent() {
        let contentControl;
        let activated = false;
        // istanbul ignore else
        if (!contentControl && this.contentControls.length >= 0) {
            contentControl = this.contentControls[0];
        }
        if (contentControl) {
            UiFramework.content.setActive(contentControl.reactNode, true);
            if (contentControl.viewport) {
                const status = await IModelApp.viewManager.setSelectedView(contentControl.viewport);
                activated = status === BentleyStatus.SUCCESS;
            }
        }
        return activated;
    }
    /** Sets the active view content control */
    setActiveView(newContent, oldContent) {
        if (oldContent)
            oldContent.onDeactivated();
        newContent.onActivated();
        UiFramework.frontstages.onContentControlActivatedEvent.emit({
            activeContentControl: newContent,
            oldContentControl: oldContent,
        });
    }
    /** Sets the active view content control based on the selected viewport. */
    setActiveViewFromViewport(viewport) {
        const contentControl = this.contentControls.find((control) => control.viewport === viewport);
        // istanbul ignore else
        if (contentControl) {
            UiFramework.content.setActive(contentControl.reactNode, true);
            return true;
        }
        // istanbul ignore next
        return false;
    }
    /** Gets a [[StagePanelDef]] based on a given panel location
     * @beta
     */
    getStagePanelDef(location) {
        let panelDef;
        switch (location) {
            case StagePanelLocation.Top:
                panelDef = this.topPanel;
                break;
            case StagePanelLocation.Left:
                panelDef = this.leftPanel;
                break;
            case StagePanelLocation.Right:
                panelDef = this.rightPanel;
                break;
            case StagePanelLocation.Bottom:
                panelDef = this.bottomPanel;
                break;
        }
        // Panels can be undefined in a Frontstage
        return panelDef;
    }
    /** Gets a list of [[StagePanelDef]]s
     * @beta
     */
    get panelDefs() {
        const locations = [
            StagePanelLocation.Left,
            StagePanelLocation.Right,
            StagePanelLocation.Top,
            StagePanelLocation.Bottom,
        ];
        const panels = [];
        locations.forEach((location) => {
            const panel = this.getStagePanelDef(location);
            if (panel)
                panels.push(panel);
        });
        return panels;
    }
    /** Finds a [[WidgetDef]] based on a given id */
    findWidgetDef(id) {
        for (const widgetDef of this.widgetDefs) {
            if (widgetDef.id === id)
                return widgetDef;
        }
        return undefined;
    }
    /** Gets the list of [[WidgetControl]]s */
    get _widgetControls() {
        const widgetControls = new Array();
        this.panelDefs.forEach((panelDef) => {
            panelDef.widgetDefs.forEach((widgetDef) => {
                const widgetControl = widgetDef.widgetControl;
                // istanbul ignore if
                if (widgetControl)
                    widgetControls.push(widgetControl);
            });
        });
        return widgetControls;
    }
    addFloatingContentControl(contentControl) {
        // istanbul ignore next
        if (!contentControl)
            return;
        if (!this._floatingContentControls)
            this._floatingContentControls = new Array();
        this._floatingContentControls.push(contentControl);
        UiFramework.content.onAvailableContentChangedEvent.emit({
            contentId: contentControl.uniqueId,
        });
    }
    dropFloatingContentControl(contentControl) {
        // istanbul ignore next
        if (!contentControl || !this._floatingContentControls)
            return;
        const index = this._floatingContentControls.indexOf(contentControl);
        // istanbul ignore else
        if (index > -1) {
            this._floatingContentControls.splice(index, 1);
            UiFramework.content.onAvailableContentChangedEvent.emit({
                contentId: contentControl.uniqueId,
            });
        }
    }
    /** Gets the list of [[ContentControl]]s */
    get contentControls() {
        const contentControls = new Array();
        // istanbul ignore else
        if (this.contentGroup) {
            contentControls.push(...this.contentGroup.getContentControls());
        }
        if (this._floatingContentControls) {
            contentControls.push(...this._floatingContentControls);
        }
        return contentControls;
    }
    /** Initializes a FrontstageDef from FrontstageConfig.
     * @internal
     */
    async initializeFromConfig(config) {
        this._id = config.id;
        this._initialConfig = config;
        if (config.contentGroup instanceof ContentGroupProvider) {
            this._contentGroupProvider = config.contentGroup;
        }
        else {
            this._contentGroup = config.contentGroup;
        }
        this._usage = config.usage;
        this._version = config.version;
        this._toolSettings = createWidgetDef(config.toolSettings, WidgetType.ToolSettings);
        this._statusBar = createWidgetDef(config.statusBar, WidgetType.StatusBar);
        this._contentManipulation = createWidgetDef(config.contentManipulation, WidgetType.Tool);
        this._viewNavigation = createWidgetDef(config.viewNavigation, WidgetType.Navigation);
        this._topPanel = createStagePanelDef(config, StagePanelLocation.Top);
        this._leftPanel = createStagePanelDef(config, StagePanelLocation.Left);
        this._rightPanel = createStagePanelDef(config, StagePanelLocation.Right);
        this._bottomPanel = createStagePanelDef(config, StagePanelLocation.Bottom);
    }
    /** @internal */
    updateWidgetDefs() {
        // Tracks provided widgets to prevent duplicates.
        const allStageWidgetDefs = [];
        // Process panels before zones so extension can explicitly target a widget for a StagePanelSection
        this.panelDefs.forEach((stagePanelDef) => {
            stagePanelDef.updateDynamicWidgetDefs(this.id, this.usage, stagePanelDef.location, undefined, allStageWidgetDefs);
        });
    }
    /** @beta */
    restoreLayout() {
        for (const panelDef of this.panelDefs) {
            panelDef.size = panelDef.defaultSize;
            panelDef.panelState = panelDef.defaultState;
        }
        for (const widgetDef of this.widgetDefs) {
            widgetDef.setWidgetState(widgetDef.defaultState);
        }
        InternalFrontstageManager.onFrontstageRestoreLayoutEvent.emit({
            frontstageDef: this,
        });
    }
    /** Used only in UI 2.0 to determine WidgetState from NinezoneState
     *  @internal
     */
    getWidgetCurrentState(widgetDef) {
        const state = this.nineZoneState;
        // istanbul ignore else
        if (state) {
            const toolSettingsTabId = state.toolSettings?.tabId;
            if (toolSettingsTabId === widgetDef.id &&
                state.toolSettings?.type === "docked") {
                return WidgetState.Open;
            }
            const location = getTabLocation(state, widgetDef.id);
            // istanbul ignore next
            if (!location)
                return WidgetState.Hidden;
            if (isFloatingTabLocation(location)) {
                const floatingWidget = state.floatingWidgets.byId[location.floatingWidgetId];
                if (floatingWidget && floatingWidget.hidden)
                    return WidgetState.Hidden;
                else
                    return WidgetState.Floating;
            }
            let collapsedPanel = false;
            // istanbul ignore else
            if ("side" in location) {
                const panel = state.panels[location.side];
                collapsedPanel =
                    panel.collapsed || undefined === panel.size || 0 === panel.size;
            }
            const widgetContainer = state.widgets[location.widgetId];
            if (widgetDef.id === widgetContainer.activeTabId && !collapsedPanel)
                return WidgetState.Open;
            else
                return WidgetState.Closed;
        }
        return widgetDef.defaultState;
    }
    /** Used only in UI 2.0 to determine StagePanelState and size from NinezoneState
     *  @internal
     */
    getPanelCurrentState(panelDef) {
        // istanbul ignore next
        if (this.nineZoneState) {
            const side = toPanelSide(panelDef.location);
            const panel = this.nineZoneState.panels[side];
            if (panel)
                return [
                    panel.collapsed ? StagePanelState.Minimized : StagePanelState.Open,
                    panel.size ?? 0,
                    panel.pinned,
                ];
            return [StagePanelState.Off, 0, false];
        }
        return [panelDef.defaultState, panelDef.defaultSize ?? 0, true];
    }
    isPopoutWidget(widgetId) {
        // istanbul ignore else
        if (this.nineZoneState) {
            const location = getTabLocation(this.nineZoneState, widgetId);
            // istanbul ignore else
            if (location)
                return isPopoutTabLocation(location);
        }
        return false;
    }
    isFloatingWidget(widgetId) {
        // istanbul ignore else
        if (this.nineZoneState) {
            const location = getTabLocation(this.nineZoneState, widgetId);
            // istanbul ignore else
            if (location)
                return isFloatingTabLocation(location);
        }
        return false;
    }
    /** Create a new floating panel that contains the widget specified by its Id. Supported only when in
     *  UI 2.0 or higher.
     * @param widgetId case sensitive Widget Id
     * @param point Position of top left corner of floating panel in pixels. If undefined {x:50, y:100} is used.
     * @param size defines the width and height of the floating panel. If undefined and widget has been floated before
     * the previous size is used, else {height:400, width:400} is used.
     * @beta
     */
    floatWidget(widgetId, point, size) {
        // istanbul ignore else
        if (this.nineZoneState) {
            const location = getTabLocation(this.nineZoneState, widgetId);
            if (location) {
                let popoutWidgetContainerId;
                if (isPopoutTabLocation(location)) {
                    popoutWidgetContainerId = location.popoutWidgetId;
                }
                const state = floatWidget(this.nineZoneState, widgetId, point, size);
                // istanbul ignore else
                if (state) {
                    this.nineZoneState = state;
                    setTimeout(() => {
                        popoutWidgetContainerId &&
                            UiFramework.childWindows.close(popoutWidgetContainerId, true);
                    }, 600);
                }
            }
        }
    }
    /** Check widget and panel state to determine whether the widget is currently displayed
     * @param widgetId case-sensitive Widget Id
     * @public
     */
    isWidgetDisplayed(widgetId) {
        let widgetIsVisible = false;
        // istanbul ignore else
        if (this.nineZoneState) {
            const tabLocation = getTabLocation(this.nineZoneState, widgetId);
            // istanbul ignore else
            if (tabLocation) {
                if (isFloatingTabLocation(tabLocation)) {
                    const floatingWidget = this.nineZoneState.floatingWidgets.byId[tabLocation.floatingWidgetId];
                    // istanbul ignore else
                    if (!!!floatingWidget.hidden)
                        widgetIsVisible = true;
                }
                else if (isPopoutTabLocation(tabLocation)) {
                    widgetIsVisible = true;
                }
                else {
                    // istanbul ignore else
                    if (isPanelTabLocation(tabLocation)) {
                        const panel = this.nineZoneState.panels[tabLocation.side];
                        const widgetDef = this.findWidgetDef(widgetId);
                        if (widgetDef &&
                            widgetDef.state === WidgetState.Open &&
                            !panel.collapsed)
                            widgetIsVisible = true;
                    }
                }
            }
        }
        return widgetIsVisible;
    }
    /** Opens window for specified PopoutWidget container. Used to reopen popout when running in Electron.
     * @internal */
    openPopoutWidgetContainer(state, widgetContainerId) {
        const location = getWidgetLocation(state, widgetContainerId);
        // istanbul ignore next
        if (!location)
            return;
        // istanbul ignore next
        if (!isPopoutWidgetLocation(location))
            return;
        const widget = state.widgets[widgetContainerId];
        const popoutWidget = state.popoutWidgets.byId[location.popoutWidgetId];
        // Popout widget should only contain a single tab.
        // istanbul ignore next
        if (widget.tabs.length !== 1)
            return;
        const tabId = widget.tabs[0];
        const widgetDef = this.findWidgetDef(tabId);
        // istanbul ignore next
        if (!widgetDef)
            return;
        const popoutContent = (React.createElement(PopoutWidget, { widgetContainerId: widgetContainerId, widgetDef: widgetDef }));
        const bounds = Rectangle.create(popoutWidget.bounds);
        const position = {
            width: bounds.getWidth(),
            height: bounds.getHeight(),
            left: bounds.left,
            top: bounds.top,
        };
        UiFramework.childWindows.open(widgetContainerId, widgetDef.label, popoutContent, position, UiFramework.useDefaultPopoutUrl);
    }
    /** Create a new popout/child window that contains the widget specified by its Id. Supported only when in
     *  UI 2.0 or higher.
     * @param widgetId case sensitive Widget Id
     * @param point Position of top left corner of floating panel in pixels. If undefined {x:0, y:0} is used.
     * @param size defines the width and height of the floating panel. If undefined and widget has been floated before
     * the previous size is used, else {height:800, width:600} is used.
     * @beta
     */
    popoutWidget(widgetId, point, size) {
        // istanbul ignore next
        if (!this.nineZoneState)
            return;
        let location = getTabLocation(this.nineZoneState, widgetId);
        if (!location || isPopoutTabLocation(location))
            return;
        const widgetDef = this.findWidgetDef(widgetId);
        // istanbul ignore next
        if (!widgetDef)
            return;
        // get the state to apply that will pop-out the specified WidgetTab to child window.
        let preferredBounds = Rectangle.createFromSize({ height: 800, width: 600 });
        // istanbul ignore next
        if (widgetDef.popoutBounds)
            preferredBounds = widgetDef.popoutBounds;
        if (size)
            preferredBounds = preferredBounds.setSize(size);
        if (point)
            preferredBounds = preferredBounds.setPosition(point);
        const state = popoutWidgetToChildWindow(this.nineZoneState, widgetId, preferredBounds);
        this.nineZoneState = state;
        // now that the state is updated get the id of the container that houses the widgetTab/widgetId
        location = getTabLocation(state, widgetId);
        assert(!!location && isPopoutTabLocation(location));
        const widgetContainerId = location.widgetId;
        const popoutWidget = state.popoutWidgets.byId[widgetContainerId];
        const bounds = Rectangle.create(popoutWidget.bounds);
        setTimeout(() => {
            const popoutContent = (React.createElement(PopoutWidget, { widgetContainerId: widgetContainerId, widgetDef: widgetDef }));
            const position = {
                width: bounds.getWidth(),
                height: bounds.getHeight(),
                left: bounds.left,
                top: bounds.top,
            };
            UiFramework.childWindows.open(widgetContainerId, widgetDef.label, popoutContent, position, UiFramework.useDefaultPopoutUrl);
        });
    }
    get isStageClosing() {
        return this._isStageClosing;
    }
    get isApplicationClosing() {
        return this._isApplicationClosing;
    }
    get isReady() {
        return this._isReady;
    }
    /** @internal */
    saveChildWindowSizeAndPosition(childWindowId, childWindow) {
        if (!this.nineZoneState)
            return;
        const location = getWidgetLocation(this.nineZoneState, childWindowId);
        if (!location || !isPopoutWidgetLocation(location))
            return;
        const widget = this.nineZoneState.widgets[location.popoutWidgetId];
        const tabId = widget.tabs[0];
        const widgetDef = this.findWidgetDef(tabId);
        if (!widgetDef)
            return;
        const adjustmentWidth = ProcessDetector.isElectronAppFrontend ? 16 : 0;
        const adjustmentHeight = ProcessDetector.isElectronAppFrontend ? 39 : 0;
        const width = childWindow.innerWidth + adjustmentWidth;
        const height = childWindow.innerHeight + adjustmentHeight;
        const bounds = Rectangle.createFromSize({ width, height }).offset({
            x: childWindow.screenX,
            y: childWindow.screenY,
        });
        widgetDef.popoutBounds = bounds;
    }
    /** Method used to possibly change a Popout Widget back to a docked widget if the user was the one closing the popout's child
     * window.
     *  @internal
     */
    dockPopoutWidgetContainer(widgetContainerId) {
        if (!this.nineZoneState)
            return;
        // Make sure the widgetContainerId is still in popout state. We don't want to set it to docked if the window is being closed because
        // an API call has moved the widget from a popout state to a floating state.
        // istanbul ignore else
        const location = getWidgetLocation(this.nineZoneState, widgetContainerId);
        if (!location || !isPopoutWidgetLocation(location))
            return;
        const state = dockWidgetContainer(this.nineZoneState, widgetContainerId, true);
        this.nineZoneState = state;
    }
    /** Finds the container with the specified widget and re-docks all widgets
     * back to the panel zone location that was used when the floating container
     * was generated. Supported only when in UI 2.0 or higher.
     * @param widgetId  case sensitive Widget Id.
     * @beta
     */
    dockWidgetContainer(widgetId) {
        // istanbul ignore else
        if (this.nineZoneState) {
            const location = getTabLocation(this.nineZoneState, widgetId);
            if (location) {
                const widgetContainerId = location.widgetId;
                const state = dockWidgetContainer(this.nineZoneState, widgetContainerId, true);
                state && (this.nineZoneState = state);
                if (isPopoutTabLocation(location)) {
                    UiFramework.childWindows.close(location.widgetId, true);
                }
            }
        }
    }
    setFloatingWidgetContainerBounds(floatingWidgetId, bounds) {
        if (!this.nineZoneState ||
            !(floatingWidgetId in this.nineZoneState.floatingWidgets.byId))
            return false;
        let state = FrameworkStateReducer(this.nineZoneState, {
            type: "FLOATING_WIDGET_SET_BOUNDS",
            id: floatingWidgetId,
            bounds,
        }, this);
        state = FrameworkStateReducer(state, {
            type: "FLOATING_WIDGET_SET_USER_SIZED",
            id: floatingWidgetId,
            userSized: true,
        }, this);
        this.nineZoneState = state;
        return true;
    }
    getFloatingWidgetContainerIds() {
        if (!this.nineZoneState)
            return [];
        return [...this.nineZoneState.floatingWidgets.allIds];
    }
    getFloatingWidgetContainerIdByWidgetId(widgetId) {
        if (!this.nineZoneState)
            return undefined;
        const location = getTabLocation(this.nineZoneState, widgetId);
        // istanbul ignore else
        if (location && isFloatingTabLocation(location)) {
            return location.floatingWidgetId;
        }
        // istanbul ignore next
        return undefined;
    }
    getFloatingWidgetContainerBounds(floatingWidgetId) {
        if (!floatingWidgetId)
            return undefined;
        // istanbul ignore else
        if (this.nineZoneState &&
            floatingWidgetId in this.nineZoneState.floatingWidgets.byId) {
            const foundWidget = document.querySelector(`div.nz-widget-floatingWidget[data-widget-id='${floatingWidgetId}']`);
            // istanbul ignore next
            if (foundWidget) {
                const domRect = foundWidget.getBoundingClientRect();
                return {
                    left: domRect.left,
                    right: domRect.right,
                    top: domRect.top,
                    bottom: domRect.bottom,
                };
            }
            return this.nineZoneState.floatingWidgets.byId[floatingWidgetId].bounds;
        }
        return undefined;
    }
    *_widgetDefs() {
        for (const panelDef of this.panelDefs) {
            for (const widgetDef of panelDef.widgetDefs) {
                yield widgetDef;
            }
        }
        if (this.toolSettings)
            yield this.toolSettings;
        if (this.statusBar)
            yield this.statusBar;
        return undefined;
    }
    /** Iterable of all widget definitions in a frontstage.
     * @internal
     */
    get widgetDefs() {
        const defs = this._widgetDefs();
        return {
            [Symbol.iterator]() {
                return defs;
            },
        };
    }
}
function createWidgetDef(config, type) {
    if (!config)
        return undefined;
    const widgetDef = WidgetDef.create(config, type);
    return widgetDef;
}
function createStagePanelDef(frontstageConfig, location) {
    const config = getStagePanel(location, frontstageConfig);
    const panelDef = new StagePanelDef();
    panelDef.initializeFromConfig(config, location);
    return panelDef;
}
function getStagePanel(location, config) {
    switch (location) {
        case StagePanelLocation.Top:
            return config.topPanel;
        case StagePanelLocation.Left:
            return config.leftPanel;
        case StagePanelLocation.Right:
            return config.rightPanel;
        case StagePanelLocation.Bottom:
            return config.bottomPanel;
    }
}
/** Hook that returns active frontstage id.
 * @public
 */
export const useActiveFrontstageId = () => {
    const def = useActiveFrontstageDef();
    const id = React.useMemo(() => (def ? /* istanbul ignore next */ def.id : ""), [def]);
    return id;
};
/** Hook to return the active Frontstage def */
/** @public */
export function useActiveFrontstageDef() {
    const [def, setDef] = React.useState(UiFramework.frontstages.activeFrontstageDef);
    React.useEffect(() => {
        return UiFramework.frontstages.onFrontstageActivatedEvent.addListener((args) => {
            setDef(args.activatedFrontstageDef);
        });
    }, []);
    return def;
}
/** Hook that returns the widgetDef for a specific widgetId within the active frontstage.
 * @public
 */
export function useSpecificWidgetDef(widgetId) {
    const frontstageDef = useActiveFrontstageDef();
    return frontstageDef?.findWidgetDef(widgetId);
}
//# sourceMappingURL=FrontstageDef.js.map