/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Item
 */
import classnames from "classnames";
import * as React from "react";
import { Logger } from "@itwin/core-bentley";
import { SpecialKey } from "@itwin/appui-abstract";
import { BadgeUtilities, Icon, withOnOutsideClick } from "@itwin/core-react";
import { Direction, ExpandableItem, GroupColumn, GroupTool, GroupToolExpander, Item, NestedGroup as NestedToolGroupComponent, ToolbarDirectionContext, Group as ToolGroupComponent, withDragInteraction, } from "@itwin/appui-layout-react";
import { ToolGroupPanelContext } from "../frontstage/FrontstageComposer";
import { ActionButtonItemDef } from "../shared/ActionButtonItemDef";
import { ItemDefBase } from "../shared/ItemDefBase";
import { ItemList, ItemMap } from "../shared/ItemMap";
import { SyncUiEventDispatcher } from "../syncui/SyncUiEventDispatcher";
import { UiFramework } from "../UiFramework";
import { PropsHelper } from "../utils/PropsHelper";
import { ToolbarDragInteractionContext } from "./DragInteraction";
import { InternalFrontstageManager } from "../frontstage/InternalFrontstageManager";
// eslint-disable-next-line @typescript-eslint/naming-convention, deprecation/deprecation
const ToolGroup = withOnOutsideClick(ToolGroupComponent, undefined, false);
// eslint-disable-next-line @typescript-eslint/naming-convention, deprecation/deprecation
const NestedToolGroup = withOnOutsideClick(NestedToolGroupComponent, undefined, false);
// eslint-disable-next-line @typescript-eslint/naming-convention, deprecation/deprecation
const ItemWithDragInteraction = withDragInteraction(Item);
// -----------------------------------------------------------------------------
// GroupItemDef class
// -----------------------------------------------------------------------------
/** An Item that opens a group of items.
 * @public
 */
export class GroupItemDef extends ActionButtonItemDef {
    constructor(groupItemProps, onItemExecuted) {
        super(groupItemProps, onItemExecuted);
        /** @internal */
        this.overflow = false;
        this._panelLabel = "";
        this.groupId = (groupItemProps.groupId !== undefined) ? groupItemProps.groupId : "";
        if (groupItemProps.groupId)
            this.groupId = groupItemProps.groupId;
        else {
            GroupItemDef._sId++;
            this.groupId = GroupItemDef.groupIdPrefix + GroupItemDef._sId;
        }
        this.directionExplicit = (groupItemProps.direction !== undefined); // eslint-disable-line deprecation/deprecation
        this.direction = (groupItemProps.direction !== undefined) ? groupItemProps.direction : Direction.Bottom; // eslint-disable-line deprecation/deprecation
        this.itemsInColumn = (groupItemProps.itemsInColumn !== undefined) ? groupItemProps.itemsInColumn : 7;
        this._panelLabel = PropsHelper.getStringSpec(groupItemProps.panelLabel, groupItemProps.panelLabelKey); // eslint-disable-line deprecation/deprecation
        this.items = groupItemProps.items;
        this.defaultActiveItemId = groupItemProps.defaultActiveItemId;
    }
    get id() {
        return this.groupId;
    }
    /** Get the panelLabel string */
    get panelLabel() {
        return PropsHelper.getStringFromSpec(this._panelLabel);
    }
    /** Set the panelLabel.
     * @param v A string or a function to get the string.
     */
    setPanelLabel(v) {
        this._panelLabel = v;
    }
    resolveItems(force) {
        if (this._itemList && !force)
            return;
        this._itemList = new ItemList();
        this._itemMap = new ItemMap();
        this.items.map((item) => {
            const id = item.id;
            this._itemList.addItem(item);
            this._itemMap.set(id, item);
        });
    }
    getItemById(id) {
        return this._itemMap.get(id);
    }
    getItemByIndex(index) {
        return this._itemList.items[index];
    }
    get itemCount() {
        return this._itemList.items.length;
    }
    execute() {
    }
    /** @deprecated in 3.6. Used in UI1.0 only. */
    toolbarReactNode(index) {
        this.resolveItems();
        const key = this.getKey(index);
        return (React.createElement(GroupItem, { groupItemDef: this, key: key, onSizeKnown: this.handleSizeKnown }));
    }
}
GroupItemDef._sId = 0;
GroupItemDef.groupIdPrefix = "Group-";
/** Group Item React component.
 * @internal
 */
export class GroupItem extends React.Component {
    constructor(props) {
        super(props);
        this._componentUnmounting = false;
        this._childRefreshRequired = false;
        this._trayIndex = 0;
        this._closeOnPanelOpened = true;
        this._ref = React.createRef();
        this._handleSyncUiEvent = (args) => {
            // istanbul ignore next
            if (this._componentUnmounting)
                return;
            let refreshState = false;
            // istanbul ignore else
            if (this._childSyncIds && this._childSyncIds.size > 0)
                if ([...this._childSyncIds].some((value) => args.eventIds.has(value.toLowerCase())))
                    this._childRefreshRequired = true; // this is cleared when render occurs
            let newState = { ...this.state };
            if (this.props.groupItemDef.stateSyncIds && this.props.groupItemDef.stateSyncIds.length > 0) // eslint-disable-line deprecation/deprecation
                refreshState = this.props.groupItemDef.stateSyncIds.some((value) => args.eventIds.has(value.toLowerCase())); // eslint-disable-line deprecation/deprecation
            if (refreshState || this._childRefreshRequired) {
                if (this.props.groupItemDef.stateFunc) // eslint-disable-line deprecation/deprecation
                    newState = this.props.groupItemDef.stateFunc(newState); // eslint-disable-line deprecation/deprecation
                // istanbul ignore else
                if ((this.state.isActive !== newState.isActive) || (this.state.isEnabled !== newState.isEnabled) || (this.state.isVisible !== newState.isVisible)
                    || this._childRefreshRequired) {
                    this.setState({ isActive: newState.isActive, isEnabled: newState.isEnabled, isVisible: newState.isVisible, isPressed: newState.isPressed });
                }
            }
        };
        this._handleKeyDown = (e) => {
            // istanbul ignore else
            if (e.key === SpecialKey.Escape) {
                this.closeGroupButton();
                UiFramework.keyboardShortcuts.setFocusToHome();
            }
        };
        this._handleOpenPanel = () => {
            this.setState({
                isPressed: true,
            });
            this._closeOnPanelOpened = false;
            InternalFrontstageManager.onToolPanelOpenedEvent.emit();
            this._closeOnPanelOpened = true;
        };
        this._handleClick = () => {
            this.setState((prevState) => ({
                isPressed: !prevState.isPressed,
            }));
        };
        this._handleDragInteractionClick = () => {
            const activeItem = this.getItemById(this.state.activeItemId);
            activeItem && activeItem instanceof ActionButtonItemDef && activeItem.execute();
        };
        this._handleOverflowClick = () => {
            this.setState((prevState) => {
                const isPressed = !prevState.isPressed;
                return {
                    isPressed,
                };
            }, () => {
                this._closeOnPanelOpened = false;
                !!this.state.isPressed && InternalFrontstageManager.onToolPanelOpenedEvent.emit();
                this._closeOnPanelOpened = true;
            });
        };
        this._handleDragInteractionOutsideClick = (e) => {
            if (this.props.groupItemDef.overflow) {
                this._ref.current && (e.target instanceof Node) && !this._ref.current.contains(e.target) && this.closeGroupButton();
                return;
            }
            this.closeGroupButton();
        };
        this._handleOutsideClick = (e) => {
            this._ref.current && (e.target instanceof Node) && !this._ref.current.contains(e.target) && this.closeGroupButton();
        };
        this._handleToolActivatedEvent = ({ toolId }) => {
            this.setState({
                activeToolId: toolId,
            });
        };
        this._handleToolPanelOpenedEvent = () => {
            // istanbul ignore else
            if (!this._closeOnPanelOpened)
                return;
            // istanbul ignore next
            this.closeGroupButton();
        };
        this._handleBack = () => {
            this.setState((prevState) => {
                const trayId = prevState.backTrays.length > 0 ? prevState.backTrays[prevState.backTrays.length - 1] : prevState.trayId;
                const backTrays = prevState.backTrays.slice(0, -1);
                return {
                    trayId,
                    backTrays,
                };
            });
        };
        this._handleExpanderClick = (trayId) => {
            this.setState((prevState) => {
                return {
                    trayId,
                    backTrays: [...prevState.backTrays, prevState.trayId],
                };
            });
        };
        this._loadChildSyncIds(props);
        const state = this.getGroupItemState(this.props);
        const activeItemId = this.props.groupItemDef.defaultActiveItemId !== undefined ? this.props.groupItemDef.defaultActiveItemId : getFirstItemId(this.props.groupItemDef);
        this.state = {
            ...state,
            activeItemId,
        };
    }
    _loadChildSyncIds(props) {
        // istanbul ignore else
        if (props.groupItemDef && props.groupItemDef.items.length > 0) {
            props.groupItemDef.items.forEach((itemDef) => {
                const item = itemDef;
                if (item.stateSyncIds.length > 0) { // eslint-disable-line deprecation/deprecation
                    if (undefined === this._childSyncIds)
                        this._childSyncIds = new Set();
                    item.stateSyncIds.forEach((value) => this._childSyncIds.add(value)); // eslint-disable-line deprecation/deprecation
                }
            });
        }
    }
    componentDidMount() {
        SyncUiEventDispatcher.onSyncUiEvent.addListener(this._handleSyncUiEvent);
        UiFramework.frontstages.onToolActivatedEvent.addListener(this._handleToolActivatedEvent);
        InternalFrontstageManager.onToolPanelOpenedEvent.addListener(this._handleToolPanelOpenedEvent);
    }
    componentWillUnmount() {
        this._componentUnmounting = true;
        SyncUiEventDispatcher.onSyncUiEvent.removeListener(this._handleSyncUiEvent);
        UiFramework.frontstages.onToolActivatedEvent.removeListener(this._handleToolActivatedEvent);
        InternalFrontstageManager.onToolPanelOpenedEvent.removeListener(this._handleToolPanelOpenedEvent);
    }
    shouldComponentUpdate(nextProps, nextState) {
        if (!PropsHelper.isShallowEqual(nextState, this.state))
            return true;
        if (!PropsHelper.isShallowEqual(nextProps, this.props))
            return true;
        // istanbul ignore else
        if (this._childRefreshRequired) {
            this._childRefreshRequired = false;
            return true;
        }
        // istanbul ignore next
        return false;
    }
    getGroupItemState(props) {
        const groupItemDef = props.groupItemDef;
        // Separate into trays
        const trays = new Map();
        const trayId = this.resetTrayId();
        this.processGroupItemDef(groupItemDef, trayId, trays);
        return {
            activeToolId: UiFramework.frontstages.activeToolId,
            groupItemDef,
            isEnabled: groupItemDef.isEnabled,
            isPressed: groupItemDef.isPressed,
            isVisible: groupItemDef.isVisible,
            trayId,
            backTrays: [],
            trays,
        };
    }
    resetTrayId() {
        this._trayIndex = 1;
        return `tray-${this._trayIndex}`;
    }
    generateTrayId() {
        return `tray-${this._trayIndex}`;
    }
    processGroupItemDef(groupItemDef, trayId, trays) {
        // Separate into column items
        const items = new Map();
        for (let itemIndex = 0; itemIndex < groupItemDef.itemCount; itemIndex++) {
            const item = groupItemDef.getItemByIndex(itemIndex);
            if (item instanceof GroupItemDef) {
                item.resolveItems();
                this._trayIndex++;
                const itemTrayId = this.generateTrayId();
                const groupItem = {
                    iconSpec: item.iconSpec, label: item.label, trayId: itemTrayId,
                    badgeType: item.badgeType,
                };
                items.set(item.id, groupItem);
                this.processGroupItemDef(item, itemTrayId, trays);
            }
            else {
                items.set(item.id, item);
            }
        }
        trays.set(trayId, {
            items,
            title: groupItemDef.panelLabel ? groupItemDef.panelLabel : groupItemDef.tooltip,
            groupItemDef,
        });
    }
    componentDidUpdate(prevProps, _prevState) {
        if (this.props !== prevProps) {
            // istanbul ignore next
            if (this.props.groupItemDef !== prevProps.groupItemDef)
                Logger.logTrace(UiFramework.loggerCategory(this), `Different GroupItemDef for same groupId of ${this.state.groupItemDef.groupId}`);
            this.setState((_, props) => this.getGroupItemState(props));
        }
    }
    get _tray() {
        const tray = this.state.trays.get(this.state.trayId);
        // istanbul ignore next
        if (!tray)
            throw new RangeError();
        return tray;
    }
    getTray(trayId) {
        const tray = this.state.trays.get(trayId);
        // istanbul ignore next
        if (!tray)
            throw new RangeError();
        return tray;
    }
    render() {
        if (!this.state.isVisible)
            return null;
        const activeItem = this.getItemById(this.state.activeItemId);
        if (!activeItem)
            return null;
        const { groupItemDef, className, ...props } = this.props;
        const classNames = classnames(className, groupItemDef.overflow && "nz-toolbar-item-overflow");
        const badge = BadgeUtilities.getComponentForBadgeType(groupItemDef.badgeType);
        return (React.createElement(ToolbarDirectionContext.Consumer, null, (direction) => (React.createElement(ToolbarDragInteractionContext.Consumer, null, (dragInteraction) => (React.createElement(ExpandableItem // eslint-disable-line deprecation/deprecation
        , { ...props, className: classNames, key: this.state.groupItemDef.id, panel: this.getGroupTray(dragInteraction) }, dragInteraction ?
            (React.createElement("div", { ref: this._ref },
                React.createElement(ItemWithDragInteraction, { badge: badge, className: groupItemDef.overflow ? "nz-ellipsis-icon" : undefined, direction: direction, icon: React.createElement(Icon, { iconSpec: groupItemDef.overflow ? "nz-ellipsis" : activeItem.iconSpec }), isActive: groupItemDef.overflow ? false : this.state.activeToolId === this.state.activeItemId, isDisabled: !this.state.isEnabled, onClick: groupItemDef.overflow ? this._handleOverflowClick : this._handleDragInteractionClick, onKeyDown: this._handleKeyDown, onOpenPanel: this._handleOpenPanel, onSizeKnown: this.props.onSizeKnown, title: groupItemDef.overflow ? groupItemDef.label : activeItem.label }))) :
            (React.createElement("div", { ref: this._ref },
                React.createElement(Item // eslint-disable-line deprecation/deprecation
                , { badge: badge, className: groupItemDef.overflow ? "nz-ellipsis-icon" : undefined, icon: React.createElement(Icon, { iconSpec: groupItemDef.overflow ? "nz-ellipsis" : groupItemDef.iconSpec }), isDisabled: !this.state.isEnabled, onClick: this._handleClick, onKeyDown: this._handleKeyDown, onSizeKnown: this.props.onSizeKnown, title: this.state.groupItemDef.label })))))))));
    }
    closeGroupButton() {
        const trayId = this.resetTrayId();
        this.setState({
            isPressed: false,
            trayId,
            backTrays: [],
        });
    }
    handleToolGroupItemClicked(trayKey, itemKey) {
        const trayId = this.resetTrayId();
        this.setState({
            activeItemId: itemKey,
            isPressed: false,
            trayId,
            backTrays: [],
        }, () => {
            const tray = this.getTray(trayKey);
            const childItem = tray.groupItemDef.getItemById(itemKey);
            childItem && childItem instanceof ActionButtonItemDef && childItem.execute();
        });
    }
    getGroupTray(dragInteraction) {
        if (!this.state.isPressed)
            return undefined;
        return (React.createElement(ToolGroupPanelContext.Consumer, null, (activateOnPointerUp) => {
            const tray = this._tray;
            const itemsInColumn = tray.groupItemDef.itemsInColumn;
            const items = [...tray.items.keys()];
            activateOnPointerUp = activateOnPointerUp && dragInteraction;
            // Divide items equally between columns.
            const numberOfColumns = Math.ceil(items.length / itemsInColumn);
            const numberItemsInColumn = Math.ceil(items.length / numberOfColumns);
            const columnToItems = items.reduce((acc, item, index) => {
                const columnIndex = Math.floor(index / numberItemsInColumn);
                if (columnIndex >= acc.length) {
                    return [
                        ...acc,
                        [item],
                    ];
                }
                return [
                    ...acc.slice(0, columnIndex),
                    [
                        ...acc[columnIndex],
                        item,
                    ],
                    ...acc.slice(columnIndex + 1),
                ];
            }, []);
            const columns = (columnToItems.map((columnItems, columnIndex) => React.createElement(GroupColumn, { key: columnIndex },
                " ",
                columnItems.map((itemKey) => {
                    const item = tray.items.get(itemKey);
                    const icon = React.createElement(Icon, { iconSpec: item.iconSpec });
                    let isVisible = true;
                    let isActive = itemKey === this.state.activeToolId;
                    let isEnabled = true;
                    const badge = BadgeUtilities.getComponentForBadgeType(item.badgeType);
                    if (item instanceof ItemDefBase) {
                        isVisible = item.isVisible; // eslint-disable-line deprecation/deprecation
                        isEnabled = item.isEnabled; // eslint-disable-line deprecation/deprecation
                        if (item.stateFunc) { // eslint-disable-line deprecation/deprecation
                            const newState = item.stateFunc({ isVisible, isActive, isEnabled }); // eslint-disable-line deprecation/deprecation
                            isVisible = undefined !== newState.isVisible ? newState.isVisible : /* istanbul ignore next */ isVisible;
                            isEnabled = undefined !== newState.isEnabled ? newState.isEnabled : /* istanbul ignore next */ isEnabled;
                            isActive = undefined !== newState.isActive ? newState.isActive : /* istanbul ignore next */ isActive;
                        }
                    }
                    const trayId = item.trayId;
                    if (trayId) {
                        return (isVisible &&
                            React.createElement(GroupToolExpander, { isDisabled: !isEnabled, key: itemKey, label: item.label, icon: icon, badge: badge, onClick: () => this._handleExpanderClick(trayId), onPointerUp: activateOnPointerUp ? () => this._handleExpanderClick(trayId) : undefined }));
                    }
                    return (isVisible &&
                        React.createElement(GroupTool, { isDisabled: !isEnabled, isActive: isActive, key: itemKey, label: item.label, onClick: () => this.handleToolGroupItemClicked(this.state.trayId, itemKey), onPointerUp: activateOnPointerUp ? () => this.handleToolGroupItemClicked(this.state.trayId, itemKey) : undefined, icon: icon, badge: badge }));
                }))));
            if (this.state.backTrays.length > 0)
                return (React.createElement(NestedToolGroup, { columns: columns, onBack: this._handleBack, onBackPointerUp: activateOnPointerUp ? this._handleBack : undefined, onOutsideClick: dragInteraction ? this._handleDragInteractionOutsideClick : this._handleOutsideClick, title: tray.title }));
            return (React.createElement(ToolGroup, { columns: columns, onOutsideClick: dragInteraction ? this._handleDragInteractionOutsideClick : this._handleOutsideClick, title: tray.title }));
        }));
    }
    getItemById(id) {
        for (const [, tray] of this.state.trays) {
            const item = tray.groupItemDef.getItemById(id);
            if (item)
                return item;
        }
        return undefined;
    }
}
/** Group Button React component
 * @deprecated in 3.5. Use [GroupButton]($appui-abstract) instead.
 * @public
 */
export function GroupButton(props) {
    const groupItemDef = new GroupItemDef(props);
    groupItemDef.resolveItems();
    return (React.createElement(GroupItem, { ...props, groupItemDef: groupItemDef, key: groupItemDef.id }));
}
/** @internal */
export const getFirstItem = (groupItemDef) => {
    for (const item of groupItemDef.items) {
        if (item instanceof GroupItemDef) {
            const firstItem = getFirstItem(item);
            if (firstItem)
                return firstItem;
            continue;
        }
        return item;
    }
    return undefined;
};
/** @internal */
export const getFirstItemId = (groupItemDef) => {
    const item = getFirstItem(groupItemDef);
    return item ? item.id : "";
};
//# sourceMappingURL=GroupItem.js.map