/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/* eslint-disable deprecation/deprecation */
/** @packageDocumentation
 * @module Frontstage
 */
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Logger } from "@itwin/core-bentley";
import { StagePanelLocation, UiItemsManager, WidgetState } from "@itwin/appui-abstract";
import { Rectangle } from "@itwin/core-react";
import { Zones as NZ_Zones, StagePanels, StagePanelsManager, widgetZoneIds, } from "@itwin/appui-layout-react";
import { ContentLayout } from "../content/ContentLayout";
import { getNestedStagePanelKey } from "../stagepanels/StagePanel";
import { StagePanelDef } from "../stagepanels/StagePanelDef";
import { UiFramework } from "../UiFramework";
import { ToolSettingsContent } from "../widgets/ToolSettingsContent";
import { isToolSettingsWidgetManagerProps, Zone, ZoneLocation } from "../zones/Zone";
import { ZoneDef } from "../zones/ZoneDef";
import { InternalFrontstageManager } from "./InternalFrontstageManager";
/** Frontstage React component.
 * A Frontstage is a full-screen configuration designed to enable the user to accomplish a task.
 * @deprecated in 3.6. Use [[FrontstageConfig]] instead.
 * @public
 */
export class Frontstage extends React.Component {
    /** @internal */
    constructor(props) {
        super(props);
        this._isMounted = false;
        this._contentRefs = new Map(); // eslint-disable-line deprecation/deprecation
        this._zonesMeasurer = React.createRef();
        this._floatingZonesMeasurer = React.createRef();
        this._zonesStyle = {
            pointerEvents: "none",
        };
        this._zonesFooterModeStyle = {
            ...this._zonesStyle,
            display: "flex",
            flexFlow: "column",
        };
        this._uiVisibilityChanged = (args) => {
            // istanbul ignore else
            if (this._isMounted)
                this.setState({ isUiVisible: args.visible });
        };
        this._handleWidgetsChanged = (_args) => {
            this.updateWidgetDefs();
        };
        this._handleUiProviderRegisteredEvent = (_args) => {
            this.updateWidgetDefs();
        };
        this._getContentRef = (widget) => {
            const ref = this._contentRefs.get(widget);
            if (ref)
                return ref;
            const newRef = (el) => {
                if (this._isMounted) {
                    this.setState((prevState) => ({
                        widgetIdToContent: {
                            ...prevState.widgetIdToContent,
                            [widget]: el === null ? undefined : el,
                        },
                    }));
                }
            };
            this._contentRefs.set(widget, newRef);
            return newRef;
        };
        this.state = {
            isUiVisible: UiFramework.getIsUiVisible(),
            widgetIdToContent: {},
        };
    }
    /** React lifecycle method.
     * @internal
     */
    async componentDidMount() {
        this._isMounted = true;
        UiFramework.onUiVisibilityChanged.addListener(this._uiVisibilityChanged);
        UiFramework.widgetManager.onWidgetsChanged.addListener(this._handleWidgetsChanged);
        UiItemsManager.onUiProviderRegisteredEvent.addListener(this._handleUiProviderRegisteredEvent);
    }
    componentDidUpdate() {
        if (!this._zonesMeasurer.current || !this._floatingZonesMeasurer.current || !this.props.runtimeProps)
            return;
        let floatingBounds = Rectangle.create(this._floatingZonesMeasurer.current.getBoundingClientRect());
        const bounds = Rectangle.create(this._zonesMeasurer.current.getBoundingClientRect());
        const offset = bounds.topLeft().getOffsetTo(floatingBounds.topLeft());
        floatingBounds = floatingBounds.setPosition(offset);
        this.props.runtimeProps.nineZoneChangeHandler.handleZonesBoundsChange(bounds);
        this.props.runtimeProps.nineZoneChangeHandler.handleFloatingZonesBoundsChange(floatingBounds);
    }
    /** React lifecycle method.
     * @internal
     */
    componentWillUnmount() {
        this._isMounted = false;
        UiFramework.onUiVisibilityChanged.removeListener(this._uiVisibilityChanged);
        UiItemsManager.onUiProviderRegisteredEvent.removeListener(this._handleUiProviderRegisteredEvent);
    }
    updateWidgetDefs() {
        // istanbul ignore else
        if (!this.props.runtimeProps)
            return;
        // istanbul ignore next
        {
            const frontstageDef = this.props.runtimeProps.frontstageDef;
            frontstageDef.updateWidgetDefs();
            InternalFrontstageManager.onWidgetDefsUpdatedEvent.emit();
            if (this._isMounted)
                this.forceUpdate();
        }
    }
    /** @internal */
    static createZoneDef(zoneNode, zoneLocation, props) {
        if (zoneNode) {
            const zoneDef = new ZoneDef();
            const zoneElement = Frontstage.getZoneElement(zoneLocation, props);
            zoneDef.zoneLocation = zoneLocation;
            // istanbul ignore else
            if (zoneElement && React.isValidElement(zoneElement)) {
                Zone.initializeZoneDef(zoneDef, zoneElement.props);
                return zoneDef;
            }
        }
        return undefined;
    }
    static getZoneElement(zoneId, props) {
        switch (zoneId) {
            case ZoneLocation.TopLeft:
                return props.contentManipulationTools ? props.contentManipulationTools : props.topLeft; // eslint-disable-line deprecation/deprecation
            case ZoneLocation.TopCenter:
                return props.toolSettings ? props.toolSettings : props.topCenter; // eslint-disable-line deprecation/deprecation
            case ZoneLocation.TopRight:
                return props.viewNavigationTools ? /* istanbul ignore next */ props.viewNavigationTools : props.topRight; // eslint-disable-line deprecation/deprecation
            case ZoneLocation.CenterLeft:
                return props.centerLeft; // eslint-disable-line deprecation/deprecation
            case ZoneLocation.CenterRight:
                return props.centerRight; // eslint-disable-line deprecation/deprecation
            case ZoneLocation.BottomLeft:
                return props.bottomLeft; // eslint-disable-line deprecation/deprecation
            case ZoneLocation.BottomCenter:
                return props.statusBar ? props.statusBar : props.bottomCenter; // eslint-disable-line deprecation/deprecation
            case ZoneLocation.BottomRight:
                return props.bottomRight; // eslint-disable-line deprecation/deprecation
        }
        // Zones can be undefined in a Frontstage
        // istanbul ignore next
        return undefined;
    }
    /** @internal */
    static createStagePanelDef(panelLocation, props) {
        const panelDef = new StagePanelDef();
        const panelElement = Frontstage.getStagePanelElement(panelLocation, props);
        panelDef.initializeFromProps(panelElement === null || panelElement === void 0 ? void 0 : panelElement.props, panelLocation);
        return panelDef;
    }
    static getStagePanelElement(location, props) {
        let panelElement;
        switch (location) {
            case StagePanelLocation.Top:
                panelElement = props.topPanel;
                break;
            case StagePanelLocation.TopMost:
                panelElement = props.topMostPanel; // eslint-disable-line deprecation/deprecation
                break;
            case StagePanelLocation.Left:
                panelElement = props.leftPanel;
                break;
            case StagePanelLocation.Right:
                panelElement = props.rightPanel;
                break;
            case StagePanelLocation.Bottom:
                panelElement = props.bottomPanel;
                break;
            case StagePanelLocation.BottomMost:
                panelElement = props.bottomMostPanel; // eslint-disable-line deprecation/deprecation
                break;
            // istanbul ignore next
            default:
                throw new RangeError();
        }
        // Panels can be undefined in a Frontstage
        return panelElement;
    }
    // This uses ConfigurableUi to render the content
    doContentLayoutRender() {
        let contentLayout;
        // istanbul ignore else
        if (this.props.runtimeProps && this.props.runtimeProps.frontstageDef) {
            const frontstageDef = this.props.runtimeProps.frontstageDef;
            // istanbul ignore else
            if (frontstageDef.contentLayoutDef && frontstageDef.contentGroup)
                contentLayout = (React.createElement(ContentLayout, { contentLayout: frontstageDef.contentLayoutDef, contentGroup: frontstageDef.contentGroup }));
            else
                Logger.logError(UiFramework.loggerCategory(this), `FrontstageDef.contentLayoutDef and FrontstageDef.contentGroup are required for <ContentLayout> component`);
        }
        return contentLayout;
    }
    cloneStagePanelElement(panelDef, runtimeProps) {
        if (!this.state.isUiVisible && UiFramework.visibility.showHidePanels)
            return null;
        // istanbul ignore else
        if (panelDef) {
            const { location } = panelDef;
            const panelElement = Frontstage.getStagePanelElement(location, this.props);
            // istanbul ignore else
            if (panelElement && React.isValidElement(panelElement)) {
                const panelKey = getNestedStagePanelKey(panelDef.location);
                const panels = runtimeProps.nineZone.nested.panels[panelKey.id];
                const panel = StagePanelsManager.getPanel(panelKey.type, panels);
                const draggedWidget = runtimeProps.nineZone.zones.draggedWidget;
                const panelRuntimeProps = {
                    draggedWidgetId: draggedWidget ? /* istanbul ignore next */ draggedWidget.id : undefined,
                    getWidgetContentRef: this._getContentRef,
                    isInFooterMode: runtimeProps.nineZone.zones.isInFooterMode,
                    isTargeted: !!runtimeProps.nineZone.zones.target,
                    panel,
                    panelDef,
                    stagePanelChangeHandler: runtimeProps.stagePanelChangeHandler,
                    widgetChangeHandler: runtimeProps.widgetChangeHandler,
                    widgets: runtimeProps.nineZone.zones.widgets,
                    widgetTabs: runtimeProps.widgetTabs,
                    zoneDefProvider: runtimeProps.zoneDefProvider,
                };
                return React.cloneElement(panelElement, { runtimeProps: panelRuntimeProps });
            }
        }
        return null;
    }
    // eslint-disable-next-line deprecation/deprecation
    cloneZoneElements(zoneIds, runtimeProps) {
        return zoneIds.map((zoneId) => {
            const zoneElement = Frontstage.getZoneElement(zoneId, this.props);
            if (!zoneElement || !React.isValidElement(zoneElement))
                return null;
            const zoneDef = runtimeProps.zoneDefProvider.getZoneDef(zoneId);
            // istanbul ignore if
            if (!zoneDef)
                return null;
            const nestedPanelsManager = InternalFrontstageManager.NineZoneManager.getNestedPanelsManager();
            const panelsManager = nestedPanelsManager.getPanelsManager("inner");
            const type = panelsManager.findWidget(zoneId, runtimeProps.nineZone.nested.panels.inner);
            // istanbul ignore if
            if (type !== undefined)
                return null;
            const zonesManager = InternalFrontstageManager.NineZoneManager.getZonesManager();
            const zones = runtimeProps.nineZone.zones;
            const ghostOutline = zonesManager.getGhostOutlineBounds(zoneId, zones);
            const dropTarget = zonesManager.getDropTarget(zoneId, zones);
            const zone = getExtendedZone(zoneId, zones, runtimeProps.zoneDefProvider);
            const widget = zone.widgets.length > 0 ? zones.widgets[zone.widgets[0]] : undefined;
            const openWidgetId = zone.widgets.find((wId) => zones.widgets[wId].tabIndex >= 0);
            const activeTabIndex = openWidgetId ? zones.widgets[openWidgetId].tabIndex : 0;
            const draggedWidget = runtimeProps.nineZone.zones.draggedWidget;
            const disabledResizeHandles = zonesManager.getDisabledResizeHandles(zoneId, zones);
            // istanbul ignore next
            const zoneRuntimeProps = {
                activeTabIndex,
                disabledResizeHandles,
                draggedWidget: draggedWidget && /* istanbul ignore next */ draggedWidget.id === zoneId ? /* istanbul ignore next */ draggedWidget : undefined,
                dropTarget,
                getWidgetContentRef: this._getContentRef,
                ghostOutline,
                isHidden: (zoneDef.isStatusBar && this.props.isInFooterMode && ( /* istanbul ignore next */this.state.isUiVisible || /* istanbul ignore next */ !UiFramework.visibility.showHideFooter)) ?
                    /* istanbul ignore next */ false : !this.state.isUiVisible,
                isInFooterMode: runtimeProps.nineZone.zones.isInFooterMode,
                openWidgetId,
                targetChangeHandler: runtimeProps.targetChangeHandler,
                widgetChangeHandler: runtimeProps.widgetChangeHandler,
                widgetTabs: runtimeProps.widgetTabs,
                widget,
                zoneDef,
                zoneDefProvider: runtimeProps.zoneDefProvider,
                zone,
            };
            return React.cloneElement(zoneElement, { key: zoneId, runtimeProps: zoneRuntimeProps });
        });
    }
    // eslint-disable-next-line deprecation/deprecation
    cloneWidgetContentElements(zones, runtimeProps) {
        // eslint-disable-next-line deprecation/deprecation
        const widgets = zones.reduce((prev, zoneId) => {
            const zoneDef = runtimeProps.zoneDefProvider.getZoneDef(zoneId);
            // istanbul ignore if
            if (!zoneDef)
                return prev;
            const widgetDefs = zoneDef.widgetDefs.filter((widgetDef) => {
                if (zoneId === 2)
                    return widgetDef.isVisible;
                return widgetDef.isVisible && !widgetDef.isFloating;
            });
            for (let i = 0; i < widgetDefs.length; i++) {
                prev.push({
                    id: zoneId,
                    def: widgetDefs[i],
                    tabIndex: i,
                });
            }
            return prev;
        }, []);
        return widgets.map((widget) => {
            const nzWidget = runtimeProps.nineZone.zones.widgets[widget.id];
            return (React.createElement(WidgetContentRenderer, { anchor: nzWidget.horizontalAnchor, isHidden: nzWidget.tabIndex !== widget.tabIndex, key: `${widget.id}_${widget.tabIndex}`, renderTo: this.state.widgetIdToContent[widget.id], toolSettingsMode: isToolSettingsWidgetManagerProps(nzWidget) ? nzWidget.mode : undefined, widgetDef: widget.def }));
        });
    }
    /** React render method
     * @internal
     */
    render() {
        const { runtimeProps } = this.props;
        if (runtimeProps === undefined)
            return null;
        /** For Nine-zone area; includes ContentLayout */
        const ninezoneStyle = {
            position: "relative",
            height: "100%",
        };
        const frontstageDef = runtimeProps.frontstageDef;
        return (React.createElement("div", { style: ninezoneStyle, id: "uifw-ninezone-area", className: this.props.className },
            React.createElement(NZ_Zones, { style: runtimeProps.nineZone.zones.isInFooterMode ? this._zonesFooterModeStyle : this._zonesStyle },
                React.createElement(StagePanels, { bottomPanel: this.cloneStagePanelElement(frontstageDef.bottomMostPanel, runtimeProps), topPanel: this.cloneStagePanelElement(frontstageDef.topMostPanel, runtimeProps) },
                    React.createElement(StagePanels, { bottomPanel: this.cloneStagePanelElement(frontstageDef.bottomPanel, runtimeProps), leftPanel: this.cloneStagePanelElement(frontstageDef.leftPanel, runtimeProps), rightPanel: this.cloneStagePanelElement(frontstageDef.rightPanel, runtimeProps), topPanel: this.cloneStagePanelElement(frontstageDef.topPanel, runtimeProps) },
                        React.createElement("div", { id: "uifw-ninezone-zones-area", ref: this._zonesMeasurer, style: {
                                height: "100%",
                                position: "relative",
                            } },
                            this.doContentLayoutRender(),
                            this.cloneZoneElements(Frontstage._zoneIds, runtimeProps)))),
                this.cloneZoneElements([8], runtimeProps),
                React.createElement("div", { id: "uifw-ninezone-floating-zones-area", ref: this._floatingZonesMeasurer, style: {
                        height: "100%",
                        width: "100%",
                        position: "absolute",
                    } })),
            this.cloneWidgetContentElements(Frontstage._zoneIds, runtimeProps)));
    }
}
Frontstage._zoneIds = widgetZoneIds.filter((z) => z !== 8); // eslint-disable-line deprecation/deprecation
class WidgetContentRenderer extends React.PureComponent {
    constructor(props) {
        super(props);
        this._isMounted = false;
        this._content = document.createElement("span");
        this._handleWidgetStateChangedEvent = (args) => {
            if (this.props.widgetDef !== args.widgetDef || !this._isMounted)
                return;
            this.forceUpdate();
        };
        this._handleToolActivatedEvent = () => {
            if (this.props.toolSettingsMode === undefined || !this._isMounted)
                return;
            // force update when tool is activated
            this.setState((prevState) => ({ widgetKey: prevState.widgetKey + 1 }));
        };
        this.state = {
            widgetKey: 0,
        };
    }
    componentDidMount() {
        this._isMounted = true;
        UiFramework.frontstages.onWidgetStateChangedEvent.addListener(this._handleWidgetStateChangedEvent);
        UiFramework.frontstages.onToolActivatedEvent.addListener(this._handleToolActivatedEvent);
        this._content.style.display = this.props.isHidden ? "none" : "flex";
        this._content.style.flexDirection = "column";
        this._content.style.height = "100%";
        if (!this.props.renderTo)
            return;
        this.props.renderTo.appendChild(this._content);
    }
    componentDidUpdate(prevProps) {
        // istanbul ignore next
        if (!this._isMounted)
            return;
        if (this.props.isHidden !== prevProps.isHidden) {
            this._content.style.display = this.props.isHidden ? "none" : "flex";
        }
        if (!this.props.renderTo || prevProps.renderTo === this.props.renderTo)
            return;
        this.props.widgetDef.saveTransientState();
        this.props.renderTo.appendChild(this._content);
        const shouldRemount = this.props.widgetDef.restoreTransientState() || this.props.widgetDef.isToolSettings;
        shouldRemount && this.setState((prevState) => ({ widgetKey: prevState.widgetKey + 1 }));
    }
    componentWillUnmount() {
        this._isMounted = false;
        this._content.parentNode && this._content.parentNode.removeChild(this._content);
        UiFramework.frontstages.onWidgetStateChangedEvent.removeListener(this._handleWidgetStateChangedEvent);
        UiFramework.frontstages.onToolActivatedEvent.removeListener(this._handleToolActivatedEvent);
    }
    render() {
        if (this.props.toolSettingsMode !== undefined) {
            return ReactDOM.createPortal((React.createElement(ToolSettingsContent, { anchor: this.props.anchor, key: this.state.widgetKey, mode: this.props.toolSettingsMode }, InternalFrontstageManager.activeToolSettingsProvider && InternalFrontstageManager.activeToolSettingsProvider.toolSettingsNode)), this._content);
        }
        if (this.props.widgetDef.state === WidgetState.Unloaded)
            return null;
        return ReactDOM.createPortal((React.createElement(React.Fragment, { key: this.state.widgetKey }, this.props.widgetDef.reactNode)), this._content);
    }
}
/** @internal */
// eslint-disable-next-line deprecation/deprecation
export const getExtendedZone = (zoneId, zones, defProvider) => {
    const zone = zones.zones[zoneId];
    if (zoneId === 1 || zoneId === 3) {
        let extendOverId = zoneId; // eslint-disable-line deprecation/deprecation
        const zonesManager = InternalFrontstageManager.NineZoneManager.getZonesManager();
        let bottomZoneId = zonesManager.bottomZones.getInitial(extendOverId);
        while (bottomZoneId !== undefined) {
            const bottomZoneDef = defProvider.getZoneDef(bottomZoneId);
            if (bottomZoneDef && bottomZoneDef.widgetDefs.length !== 0)
                break;
            extendOverId = bottomZoneId;
            bottomZoneId = zonesManager.bottomZones.getInitial(bottomZoneId);
        }
        const bottom = zones.zones[extendOverId].bounds.bottom;
        if (bottom === zone.bounds.bottom)
            return zone;
        return {
            ...zone,
            bounds: {
                ...zone.bounds,
                bottom,
            },
        };
    }
    return zone;
};
/** 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;
};
/** @internal */
export function useActiveFrontstageDef() {
    const [def, setDef] = React.useState(UiFramework.frontstages.activeFrontstageDef);
    React.useEffect(() => {
        // istanbul ignore next
        const handleActivated = (args) => {
            setDef(args.activatedFrontstageDef);
        };
        UiFramework.frontstages.onFrontstageActivatedEvent.addListener(handleActivated);
        return () => {
            UiFramework.frontstages.onFrontstageActivatedEvent.removeListener(handleActivated);
        };
    }, []);
    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 === null || frontstageDef === void 0 ? void 0 : frontstageDef.findWidgetDef(widgetId);
}
//# sourceMappingURL=Frontstage.js.map