/*---------------------------------------------------------------------------------------------
 * 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
 */
import { ConditionalBooleanValue, ConditionalStringValue, } from "@itwin/appui-abstract";
import { useRefs, useResizeObserver } from "@itwin/core-react";
import classnames from "classnames";
import * as React from "react";
import { eqlOverflown } from "../layout/tool-settings/Docked";
import { StatusBarLabelIndicator } from "../statusbar/LabelIndicator";
import { SyncUiEventDispatcher } from "../syncui/SyncUiEventDispatcher";
import { isProviderItem } from "../ui-items-provider/isProviderItem";
import { StatusBarOverflow } from "./Overflow";
import { StatusBarCenterSection, StatusBarLeftSection, StatusBarRightSection, StatusBarSpaceBetween, } from "./StatusBar";
import { isStatusBarActionItem, isStatusBarCustomItem, isStatusBarLabelItem, StatusBarSection, } from "./StatusBarItem";
import { StatusBarItemsManager } from "./StatusBarItemsManager";
import { useDefaultStatusBarItems } from "./useDefaultStatusBarItems";
import { useUiItemsProviderStatusBarItems } from "./useUiItemsProviderStatusBarItems";
import { StatusBarCornerComponentContext } from "./StatusBarCornerComponentContext";
/** 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 === StatusBarSection.Center)
        sectionValue = 100000;
    else if (item.section === StatusBarSection.Context)
        sectionValue = 200000;
    else if (item.section === StatusBarSection.Right)
        sectionValue = 300000;
    return sectionValue + item.itemPriority;
}
const DockedStatusBarEntryContext = React.createContext(null);
DockedStatusBarEntryContext.displayName = "nz:DockedStatusBarEntryContext";
/** @internal */
export function useStatusBarEntry() {
    return React.useContext(DockedStatusBarEntryContext);
}
/** Used in [[StatusBarComposer]] component to display a statusbar item.
 * @internal future
 */
export function DockedStatusBarItem(props) {
    const { onResize } = useStatusBarEntry();
    const ref = useResizeObserver(onResize);
    const className = classnames("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));
}
/** 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. */
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(() => {
        // Note: that items with conditions have condition run when loaded into the items manager
        return SyncUiEventDispatcher.onSyncUiEvent.addListener((args) => {
            if (0 === syncIdsOfInterest.length)
                return;
            if (syncIdsOfInterest.some((value) => args.eventIds.has(value.toLowerCase()))) {
                // process each item that has interest
                itemsManager.refreshAffectedItems(args.eventIds);
            }
        });
    }, [itemsManager, itemsManager.items, syncIdsOfInterest]);
}
function StatusBarLabelItemComponent(props) {
    const label = ConditionalStringValue.getValue(props.label);
    // eslint-disable-next-line deprecation/deprecation
    return React.createElement(StatusBarLabelIndicator, { iconSpec: props.icon, label: label });
}
function StatusBarActionItemComponent(props) {
    const title = ConditionalStringValue.getValue(props.tooltip);
    return (
    // eslint-disable-next-line deprecation/deprecation
    React.createElement(StatusBarLabelIndicator, { title: title, onClick: props.execute, 
        // eslint-disable-next-line deprecation/deprecation
        iconSpec: props.icon }));
}
/** 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 sort status bar items from left to right */
const sortItems = (items) => {
    const sortedItems = [];
    [
        StatusBarSection.Left,
        StatusBarSection.Center,
        StatusBarSection.Context,
        StatusBarSection.Right,
    ].forEach((section) => {
        items
            .filter((item) => item.section.valueOf() === section.valueOf())
            .sort((a, b) => a.itemPriority - b.itemPriority)
            .forEach((item) => sortedItems.push(item));
    });
    return sortedItems;
};
/** local function to ensure a width value is defined for a status bar entries.  */
function verifiedMapEntries(map) {
    for (const [, val] of map) {
        if (val === undefined)
            return undefined;
    }
    return map;
}
/** local function to sort status bar item widths */
const sortWidths = (widths, statusBarItems) => {
    const widthsArray = Array.from(widths.entries());
    widthsArray.sort((a, b) => {
        const aIndex = statusBarItems.findIndex((item) => item.id === a[0]);
        const bIndex = statusBarItems.findIndex((item) => item.id === b[0]);
        return aIndex - bIndex;
    });
    return new Map(widthsArray);
};
/** 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][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
 */
export function StatusBarComposer(props) {
    const { className, style, items, mainClassName, leftClassName, centerClassName, rightClassName, } = props;
    const [defaultItemsManager, setDefaultItemsManager] = React.useState(() => new StatusBarItemsManager(items));
    const containerWidth = React.useRef(undefined);
    const isInitialMount = React.useRef(true);
    React.useEffect(() => {
        if (isInitialMount.current)
            isInitialMount.current = false;
        else {
            setDefaultItemsManager(new StatusBarItemsManager(items));
        }
    }, [items]);
    const defaultItems = useDefaultStatusBarItems(defaultItemsManager);
    const syncIdsOfInterest = React.useMemo(() => StatusBarItemsManager.getSyncIdsOfInterest(defaultItems), [defaultItems]);
    useStatusBarItemSyncEffect(defaultItemsManager, syncIdsOfInterest);
    const [addonItemsManager] = React.useState(() => new StatusBarItemsManager());
    const addonItems = useUiItemsProviderStatusBarItems(addonItemsManager);
    const addonSyncIdsOfInterest = React.useMemo(() => StatusBarItemsManager.getSyncIdsOfInterest(addonItems), [addonItems]);
    useStatusBarItemSyncEffect(addonItemsManager, addonSyncIdsOfInterest);
    const entryWidths = React.useRef(new Map());
    const overflowWidth = React.useRef(undefined);
    const [overflown, setOverflown] = React.useState();
    const statusBarItems = React.useMemo(() => {
        const combinedItems = combineItems(defaultItems, addonItems);
        return sortItems(combinedItems);
    }, [defaultItems, addonItems]);
    const itemsNotInOverflow = React.useMemo(() => {
        return statusBarItems.filter((item) => !isItemInOverflow(item.id, overflown));
    }, [overflown, statusBarItems]);
    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 (!eqlOverflown(overflown, newOverflown)) {
            setOverflown(newOverflown);
        }
    }, [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 (oldW === undefined || Math.abs(oldW - w) > 2) {
            entryWidths.current.set(key, w);
            if (oldW === undefined) {
                const widths = verifiedMapEntries(entryWidths.current);
                if (widths !== undefined)
                    entryWidths.current = sortWidths(widths, statusBarItems);
            }
            calculateOverflow();
        }
    }, [calculateOverflow, statusBarItems]);
    const getSectionName = (section) => {
        switch (section) {
            case StatusBarSection.Center:
            case StatusBarSection.Stage:
                return "status-bar-center";
            case StatusBarSection.Context:
                return "status-bar-right-start";
            case StatusBarSection.Right:
            case StatusBarSection.Selection:
                return "status-bar-right-end";
            case StatusBarSection.Left:
            case 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) => {
        const providerId = isProviderItem(item) ? item.providerId : undefined;
        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) },
                React.createElement(StatusBarCornerComponentContext.Provider, { value: key === itemsNotInOverflow[0].id
                        ? "left-corner"
                        : (key ===
                            itemsNotInOverflow[itemsNotInOverflow.length - 1].id &&
                            overflown?.length === 0) ||
                            isItemInOverflow(key, overflown)
                            ? "right-corner"
                            : undefined },
                    isStatusBarCustomItem(item) && item.content,
                    isStatusBarActionItem(item) && (React.createElement(StatusBarActionItemComponent, { ...item })),
                    isStatusBarLabelItem(item) && (React.createElement(StatusBarLabelItemComponent, { ...item }))))));
    }, [handleEntryResize, itemsNotInOverflow, overflown]);
    const getSectionItems = React.useCallback((section) => {
        const sectionItems = statusBarItems
            .filter((item) => item.section.valueOf() === section.valueOf() &&
            !isItemInOverflow(item.id, overflown) &&
            !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))));
    }, [statusBarItems, overflown, getComponent]);
    const getOverflowItems = React.useCallback(() => {
        const itemsInOverflow = statusBarItems
            .filter((item) => isItemInOverflow(item.id, overflown) &&
            !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))));
    }, [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 resizeObserverRef = 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 = useRefs(containerRef, resizeObserverRef);
    const leftItems = React.useMemo(() => getSectionItems(StatusBarSection.Left), [getSectionItems]);
    const centerItems = React.useMemo(() => getSectionItems(StatusBarSection.Center), [getSectionItems]);
    const rightItems = React.useMemo(() => getSectionItems(StatusBarSection.Right), [getSectionItems]);
    const contextItems = React.useMemo(() => getSectionItems(StatusBarSection.Context), [getSectionItems]);
    const overflowItems = React.useMemo(() => getOverflowItems(), [getOverflowItems]);
    const containerClassName = classnames("uifw-statusBar-docked", className);
    return (React.createElement("div", { className: containerClassName, ref: refs, style: style, role: "presentation" },
        React.createElement(StatusBarSpaceBetween, { className: mainClassName },
            React.createElement(StatusBarLeftSection, { className: leftClassName }, leftItems),
            React.createElement(StatusBarCenterSection, { className: centerClassName },
                centerItems,
                contextItems),
            React.createElement(StatusBarRightSection, { className: rightClassName },
                rightItems,
                (!overflown || overflown.length > 0) && (React.createElement(StatusBarOverflow, { onResize: handleOverflowResize, overflowItems: overflowItems }))))));
}
//# sourceMappingURL=StatusBarComposer.js.map