"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module StatusBar
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.StatusBarComposer = exports.DockedStatusBarItem = exports.useStatusBarEntry = void 0;
const classnames_1 = require("classnames");
const React = require("react");
const appui_abstract_1 = require("@itwin/appui-abstract");
const core_react_1 = require("@itwin/core-react");
const appui_layout_react_1 = require("@itwin/appui-layout-react");
const SyncUiEventDispatcher_1 = require("../syncui/SyncUiEventDispatcher");
const Indicator_1 = require("../statusfields/Indicator");
const Overflow_1 = require("./Overflow");
const OverflowPanel_1 = require("./OverflowPanel");
const StatusBar_1 = require("./StatusBar");
const StatusBarItem_1 = require("./StatusBarItem");
const useDefaultStatusBarItems_1 = require("./useDefaultStatusBarItems");
const useUiItemsProviderStatusBarItems_1 = require("./useUiItemsProviderStatusBarItems");
/* eslint-disable deprecation/deprecation */
/** Private  function to generate a value that will allow the proper order to be maintained when items are placed in overflow panel */
function getCombinedSectionItemPriority(item) {
    let sectionValue = 0;
    if (item.section === appui_abstract_1.StatusBarSection.Center)
        sectionValue = 100000;
    else if (item.section === appui_abstract_1.StatusBarSection.Context)
        sectionValue = 200000;
    else if (item.section === appui_abstract_1.StatusBarSection.Right)
        sectionValue = 300000;
    return sectionValue + item.itemPriority;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
const DockedStatusBarEntryContext = React.createContext(null);
DockedStatusBarEntryContext.displayName = "nz:DockedStatusBarEntryContext";
/** @internal */
function useStatusBarEntry() {
    return React.useContext(DockedStatusBarEntryContext);
}
exports.useStatusBarEntry = useStatusBarEntry;
/** Used in [[StatusBarComposer]] component to display a statusbar item.
 * @internal future
 */
function DockedStatusBarItem(props) {
    const { onResize } = useStatusBarEntry();
    const ref = (0, core_react_1.useResizeObserver)(onResize);
    const className = (0, classnames_1.default)("uifw-statusbar-item-container", props.className);
    return (React.createElement("div", { "data-item-id": props.itemId, "data-item-type": "status-bar-item", "data-item-location": props.section, "data-item-priority": props.itemPriority, "data-item-provider-id": props.providerId, className: className, ref: ref, style: props.style }, props.children));
}
exports.DockedStatusBarItem = DockedStatusBarItem;
/** Wrapper for status bar entries so their size can be used to determine if the status bar container can display them or if they will need to be placed in an overflow panel. */
// eslint-disable-next-line @typescript-eslint/naming-convention, no-shadow
const DockedStatusBarEntry = React.memo(function DockedStatusbarEntry({ children, entryKey, getOnResize }) {
    const onResize = React.useMemo(() => getOnResize(entryKey), [getOnResize, entryKey]);
    const entry = React.useMemo(() => ({
        isOverflown: false,
        onResize,
    }), [onResize]);
    return (React.createElement(DockedStatusBarEntryContext.Provider, { value: entry }, children));
});
/** Private function to set up sync event monitoring of statusbar items */
function useStatusBarItemSyncEffect(itemsManager, syncIdsOfInterest) {
    React.useEffect(() => {
        const handleSyncUiEvent = (args) => {
            if (0 === syncIdsOfInterest.length)
                return;
            // istanbul ignore else
            if (syncIdsOfInterest.some((value) => args.eventIds.has(value.toLowerCase()))) {
                // process each item that has interest
                itemsManager.refreshAffectedItems(args.eventIds);
            }
        };
        // Note: that items with conditions have condition run when loaded into the items manager
        SyncUiEventDispatcher_1.SyncUiEventDispatcher.onSyncUiEvent.addListener(handleSyncUiEvent);
        return () => {
            SyncUiEventDispatcher_1.SyncUiEventDispatcher.onSyncUiEvent.removeListener(handleSyncUiEvent);
        };
    }, [itemsManager, itemsManager.items, syncIdsOfInterest]);
}
/** function to produce a StatusBarItem component from an AbstractStatusBarLabelItem */
function generateActionStatusLabelItem(item, isInFooterMode) {
    const iconPaddingClass = item.labelSide === appui_abstract_1.StatusBarLabelSide.Left ? "nz-icon-padding-right" : "nz-icon-padding-left";
    return (React.createElement(appui_layout_react_1.FooterIndicator // eslint-disable-line deprecation/deprecation
    , { isInFooterMode: isInFooterMode },
        item.icon && React.createElement(core_react_1.Icon, { iconSpec: item.icon }),
        item.label && React.createElement("span", { className: iconPaddingClass }, appui_abstract_1.ConditionalStringValue.getValue(item.label))));
}
/** function to produce a StatusBarItem component from an AbstractStatusBarActionItem */
function generateActionStatusBarItem(item, isInFooterMode) {
    return React.createElement(Indicator_1.Indicator, { toolTip: appui_abstract_1.ConditionalStringValue.getValue(item.tooltip), opened: false, onClick: item.execute, iconSpec: item.icon, isInFooterMode: isInFooterMode });
}
/** local function to combine items from Stage and from Extensions */
function combineItems(stageItems, addonItems) {
    const items = [];
    if (stageItems.length) {
        // Walk through each and ensure no duplicate ids are added.
        stageItems.forEach((srcItem) => {
            if (-1 === items.findIndex((item) => item.id === srcItem.id)) {
                items.push(srcItem);
            }
        });
    }
    if (addonItems.length) {
        // Walk through each and ensure no duplicate ids are added.
        addonItems.forEach((srcItem) => {
            if (-1 === items.findIndex((item) => item.id === srcItem.id)) {
                items.push(srcItem);
            }
        });
    }
    return items;
}
/** local function to ensure a width value is defined for a status bar entries.  */
function verifiedMapEntries(map) {
    for (const [, val] of map) {
        // istanbul ignore next
        if (val === undefined)
            return undefined;
    }
    return map;
}
/** Returns a subset of docked entry keys that exceed given width and should be placed in overflow panel. */
function getItemToPlaceInOverflow(width, docked, overflowWidth) {
    let settingsWidth = 0;
    let i = 0;
    for (; i < docked.length; i++) {
        const w = docked[i][1];
        const newSettingsWidth = settingsWidth + w;
        if (newSettingsWidth > width) {
            settingsWidth += overflowWidth;
            break;
        }
        settingsWidth = newSettingsWidth;
    }
    let j = i;
    for (; j > 0; j--) {
        if (settingsWidth <= width)
            break;
        const w = docked[j][1];
        settingsWidth -= w;
    }
    const overflowItems = new Array();
    for (i = j; i < docked.length; i++) {
        overflowItems.push(docked[i][0]);
    }
    return overflowItems;
}
function isItemInOverflow(id, overflowItemIds) {
    if (!overflowItemIds || 0 === overflowItemIds.length)
        return false;
    return !!overflowItemIds.find((value) => value === id);
}
/** Component to load components into the [[StatusBar]].
 * @public
 */
function StatusBarComposer(props) {
    const { className, style, items, mainClassName, leftClassName, centerClassName, rightClassName } = props;
    const [defaultItemsManager, setDefaultItemsManager] = React.useState(() => new appui_abstract_1.StatusBarItemsManager(items));
    const [isOverflowPanelOpen, setIsOverflowPanelOpen] = React.useState(false);
    const containerWidth = React.useRef(undefined);
    const isInitialMount = React.useRef(true);
    React.useEffect(() => {
        if (isInitialMount.current)
            isInitialMount.current = false;
        else {
            setDefaultItemsManager(new appui_abstract_1.StatusBarItemsManager(items));
        }
    }, [items]);
    const defaultItems = (0, useDefaultStatusBarItems_1.useDefaultStatusBarItems)(defaultItemsManager);
    const syncIdsOfInterest = React.useMemo(() => appui_abstract_1.StatusBarItemsManager.getSyncIdsOfInterest(defaultItems), [defaultItems]);
    useStatusBarItemSyncEffect(defaultItemsManager, syncIdsOfInterest);
    const statusBarContext = React.useContext(StatusBar_1.StatusBarContext);
    const [addonItemsManager] = React.useState(() => new appui_abstract_1.StatusBarItemsManager());
    const addonItems = (0, useUiItemsProviderStatusBarItems_1.useUiItemsProviderStatusBarItems)(addonItemsManager);
    const addonSyncIdsOfInterest = React.useMemo(() => appui_abstract_1.StatusBarItemsManager.getSyncIdsOfInterest(addonItems), [addonItems]);
    useStatusBarItemSyncEffect(addonItemsManager, addonSyncIdsOfInterest);
    const statusBarItems = React.useMemo(() => combineItems(defaultItems, addonItems), [defaultItems, addonItems]);
    const entryWidths = React.useRef(new Map());
    const overflowWidth = React.useRef(undefined);
    const [overflown, setOverflown] = React.useState();
    const calculateOverflow = React.useCallback(() => {
        const widths = verifiedMapEntries(entryWidths.current);
        if (containerWidth.current === undefined ||
            widths === undefined ||
            overflowWidth.current === undefined) {
            setOverflown(new Array());
            return;
        }
        // Calculate overflow.
        const newOverflown = getItemToPlaceInOverflow(containerWidth.current, [...widths.entries()], overflowWidth.current);
        if (!(0, appui_layout_react_1.eqlOverflown)(overflown, newOverflown)) {
            setOverflown(newOverflown);
            // istanbul ignore next
            if (0 === newOverflown.length && isOverflowPanelOpen)
                setIsOverflowPanelOpen(false);
        }
    }, [isOverflowPanelOpen, overflown]);
    const handleOverflowResize = React.useCallback((w) => {
        const calculate = overflowWidth.current !== w;
        overflowWidth.current = w;
        calculate && calculateOverflow();
    }, [calculateOverflow]);
    const handleEntryResize = React.useCallback((key) => (w) => {
        const oldW = entryWidths.current.get(key);
        if ((undefined === oldW) || Math.abs(oldW - w) > 2) {
            entryWidths.current.set(key, w);
            calculateOverflow();
        }
    }, [calculateOverflow]);
    const getSectionName = (section) => {
        switch (section) {
            case appui_abstract_1.StatusBarSection.Center:
            case appui_abstract_1.StatusBarSection.Stage:
                return "status-bar-center";
            case appui_abstract_1.StatusBarSection.Context:
                return "status-bar-right-start";
            case appui_abstract_1.StatusBarSection.Right:
            case appui_abstract_1.StatusBarSection.Selection:
                return "status-bar-right-end";
            case appui_abstract_1.StatusBarSection.Left:
            case appui_abstract_1.StatusBarSection.Message:
                return "status-bar-left";
        }
    };
    /** generate a wrapped status bar entry that will report its size. */
    const getComponent = React.useCallback((item, key, itemPriority, section, providerId) => {
        return (React.createElement(DockedStatusBarEntry, { key: key, entryKey: key, getOnResize: handleEntryResize },
            React.createElement(DockedStatusBarItem, { key: key, itemId: item.id, itemPriority: itemPriority, providerId: providerId, section: getSectionName(section) },
                (0, StatusBarItem_1.isStatusBarItem)(item) && item.reactNode,
                (0, appui_abstract_1.isAbstractStatusBarActionItem)(item) && generateActionStatusBarItem(item, statusBarContext.isInFooterMode),
                (0, appui_abstract_1.isAbstractStatusBarLabelItem)(item) && generateActionStatusLabelItem(item, statusBarContext.isInFooterMode))));
        // eslint-disable-next-line deprecation/deprecation
    }, [statusBarContext.isInFooterMode, handleEntryResize]);
    const getSectionItems = React.useCallback((section) => {
        const sectionItems = statusBarItems
            .filter((item) => item.section === section && !isItemInOverflow(item.id, overflown) && !appui_abstract_1.ConditionalBooleanValue.getValue(item.isHidden))
            .sort((a, b) => a.itemPriority - b.itemPriority);
        return sectionItems.map((sectionItem) => (React.createElement(React.Fragment, { key: sectionItem.id }, getComponent(sectionItem, sectionItem.id, sectionItem.itemPriority, sectionItem.section, sectionItem.providerId))));
    }, [statusBarItems, overflown, getComponent]);
    const getOverflowItems = React.useCallback(() => {
        const itemsInOverflow = statusBarItems
            .filter((item) => isItemInOverflow(item.id, overflown) && !appui_abstract_1.ConditionalBooleanValue.getValue(item.isHidden))
            .sort((a, b) => getCombinedSectionItemPriority(a) - getCombinedSectionItemPriority(b)).reverse();
        return itemsInOverflow.map((item) => (React.createElement(React.Fragment, { key: item.id }, getComponent(item, item.id, item.itemPriority, item.section, item.providerId))));
    }, [statusBarItems, overflown, getComponent]);
    const handleContainerResize = React.useCallback((w) => {
        if ((undefined === containerWidth.current) || Math.abs(containerWidth.current - w) > 2) {
            containerWidth.current = w;
            calculateOverflow();
        }
    }, [calculateOverflow]);
    const containerRef = React.useRef(null);
    const targetRef = React.useRef(null);
    const resizeObserverRef = (0, core_react_1.useResizeObserver)(handleContainerResize);
    // allow  both the containerRef and the resize observer function that takes a ref to be processed when the ref is set.
    const refs = (0, core_react_1.useRefs)(containerRef, resizeObserverRef);
    const onOverflowClick = React.useCallback(() => {
        setIsOverflowPanelOpen((prev) => !prev);
    }, []);
    // istanbul ignore next
    const handleOnClose = React.useCallback(() => {
        setIsOverflowPanelOpen(false);
    }, []);
    const leftItems = React.useMemo(() => getSectionItems(appui_abstract_1.StatusBarSection.Left), [getSectionItems]);
    const centerItems = React.useMemo(() => getSectionItems(appui_abstract_1.StatusBarSection.Center), [getSectionItems]);
    const rightItems = React.useMemo(() => getSectionItems(appui_abstract_1.StatusBarSection.Right), [getSectionItems]);
    const contextItems = React.useMemo(() => getSectionItems(appui_abstract_1.StatusBarSection.Context), [getSectionItems]);
    const overflowItems = React.useMemo(() => getOverflowItems(), [getOverflowItems]);
    const containerClassName = (0, classnames_1.default)("uifw-statusbar-docked", className);
    return (React.createElement("div", { className: containerClassName, ref: refs, style: style, role: "presentation" },
        React.createElement(StatusBar_1.StatusBarSpaceBetween, { className: mainClassName },
            React.createElement(StatusBar_1.StatusBarLeftSection, { className: leftClassName }, leftItems),
            React.createElement(StatusBar_1.StatusBarCenterSection, { className: centerClassName },
                centerItems,
                contextItems),
            React.createElement(StatusBar_1.StatusBarRightSection, { className: rightClassName },
                rightItems,
                (!overflown || overflown.length > 0) && (React.createElement(React.Fragment, null,
                    React.createElement(Overflow_1.StatusBarOverflow, { onClick: onOverflowClick, onResize: handleOverflowResize, ref: targetRef }),
                    overflowItems.length > 0 && isOverflowPanelOpen && targetRef.current &&
                        React.createElement(OverflowPanel_1.StatusBarOverflowPanel, { onClose: handleOnClose, open: true, target: targetRef.current },
                            React.createElement("div", { className: "uifw-statusbar-overflow-items-container", "data-testid": "uifw-statusbar-overflow-items-container" }, overflowItems.map((overflowEntry) => overflowEntry)))))))));
}
exports.StatusBarComposer = StatusBarComposer;
//# sourceMappingURL=StatusBarComposer.js.map