import React, { useEffect, useRef, useState } from "react";
import { DashbarItem } from "./DashbarItem";
import { useDashbar } from "../../../hooks/useDashbar";
import { useToolbar } from "../../../hooks/useToolbar";
import { FinsembleDnDContext, AllowableDnDAxis } from "../../legacyControls/FinsembleDnDContext";
import clone from "lodash/cloneDeep";
import "../../../assets/css/dashbar.css";
import { FEAGlobals } from "../../../../common/Globals";
let cachedWidgetTop;
let cachedWidgetHeight;
let createWidgetTasks = [];
export const Dashbar = ({ className, children, widgetTitleGenerator }) => {
    const { dashbarItems, reorderDashbarItems } = useDashbar();
    const { launchPreferences } = useToolbar();
    const widgetsRef = useRef({});
    const [widgets, _setWidgets] = useState({});
    const [horizontalBounds, setHorizontalBounds] = useState(0);
    const [wasReorganized, setReorganized] = useState(false);
    const [fastRender, setFastRender] = useState(Date.now());
    const [isUpdatingState, setIsUpdatingState] = useState(false);
    const [secondUpdateRequired, setSecondUpdateRequired] = useState(false);
    const [allowHorizontalScroll, setAllowHorizontalScroll] = useState(true);
    const [leftItemsCount, setLeftItemsCount] = useState(0);
    const [rightItemsCount, setRightItemsCount] = useState(0);
    let movingIndex = null; // The dashbarItem index being dragged, null if none
    let titleGrabPosition = null; // The mouse of event when a widget title is dragged
    let offsetX = null; // The original grab location of a given view titlebar
    const SCROLL_BUTTON_DEFAULT_WIDTH = 30;
    const defaultWidgetTitleGenerator = (widget, titleRef) => {
        const handleWidgetTitleClick = () => {
            if (widget.raiseIntentOptions) {
                window.fdc3.raiseIntent(widget.raiseIntentOptions.intent, widget.raiseIntentOptions.context, widget.raiseIntentOptions.targetApp);
            }
        };
        return (React.createElement("div", { ref: titleRef, onClick: () => handleWidgetTitleClick() },
            React.createElement("span", { dangerouslySetInnerHTML: { __html: widget.title } })));
    };
    const titleGenerator = widgetTitleGenerator !== null && widgetTitleGenerator !== void 0 ? widgetTitleGenerator : defaultWidgetTitleGenerator;
    /**
     * Set the reference (instance variable) to keeps the same value of widgets to prevent a difficult to reproduce bug
     * where it would somehow create two browserviews for the same widget in rapid succession.
     */
    const setWidgets = (widgets) => {
        widgetsRef.current = widgets;
        _setWidgets(widgets);
    };
    /**
     *
     * @param {object} params
     * @param {number} oldIndex The index of the item being moved
     * @param {number} newIndex The index to move the item to
     */
    const reorderItems = async ({ oldIndex, newIndex }) => {
        movingIndex = null;
        titleGrabPosition = null;
        offsetX = null;
        setReorganized(true);
        // Update the redux store
        await reorderDashbarItems({ oldIndex, newIndex });
    };
    const launchDashbar = () => {
        launchPreferences("Dashbar");
    };
    const { dashbar } = FEAGlobals.FSBL.Clients.WindowClient.getSpawnData();
    const defaultLoadingIconUrl = "../../assets/img/Finsemble-installation.gif";
    // Dashbar is disabled (no config in toolbar) or dashbar height is not specified
    if (!dashbar || !dashbar.height) {
        return null;
    }
    const toolbarHeight = FEAGlobals.finsembleWindow.windowOptions.height;
    const dashbarHeight = `${dashbar.height}px`;
    const verticalBounds = toolbarHeight - dashbar.height;
    const widgetDividerWidth = dashbar.widgetDividerWidth || 0;
    const wrapperClasses = `finsemble-dashbar ${className || ""}`.trim();
    const getTotalWidth = () => {
        let totalWidth = 0;
        dashbarItems.forEach((item) => {
            totalWidth = totalWidth + item.width;
        });
        return totalWidth;
    };
    // For calculation widgets position that function uses the HTML elements.
    // If something will be changed in HTML hierarchy for widgets, that function should be refactored.
    const setWidgetsPositions = async () => {
        var _a, _b, _c, _d;
        FEAGlobals.FSBL.Clients.Logger.system.debug("Dashbar: Setting widget positions");
        const { data: bounds } = await FEAGlobals.finsembleWindow.getBounds();
        const widgetsWidth = getTotalWidth();
        const scrollButtonsEnabled = widgetsWidth >= ((_a = bounds === null || bounds === void 0 ? void 0 : bounds.width) !== null && _a !== void 0 ? _a : 0);
        // show or hide scroll buttons based on widgets size and window width
        if (allowHorizontalScroll != scrollButtonsEnabled) {
            setAllowHorizontalScroll(scrollButtonsEnabled);
        }
        const elements = document.getElementsByClassName("finsemble-dashbar-item");
        let toolbarWidth = (_b = bounds === null || bounds === void 0 ? void 0 : bounds.width) !== null && _b !== void 0 ? _b : 0;
        let SCROLL_BUTTON_WIDTH = SCROLL_BUTTON_DEFAULT_WIDTH;
        if (scrollButtonsEnabled) {
            // Get actual width in case of client styling
            const chevrons = document.querySelectorAll("#dashbar-container > .chevron-container");
            if (chevrons.length > 0) {
                SCROLL_BUTTON_WIDTH = chevrons[0].offsetWidth;
                toolbarWidth = toolbarWidth - 2 * SCROLL_BUTTON_WIDTH;
            }
        }
        let tempLeft = 0;
        let leftButtonSize = 0;
        if (scrollButtonsEnabled) {
            tempLeft = SCROLL_BUTTON_WIDTH;
            leftButtonSize = SCROLL_BUTTON_WIDTH;
        }
        let newLeftItemsCount = 0;
        let newRightItemsCount = 0;
        for (const dashbarItem of elements) {
            // rect.width already contains the divider width
            const rect = dashbarItem.getBoundingClientRect();
            const dashbarItemFrame = dashbarItem.firstChild;
            const widget = widgets[dashbarItemFrame.id];
            // Happens when widgets were removed, but dom-elements still exists. So just ignore.
            if (!widget)
                continue;
            const width = Math.round(rect.width);
            const left = Math.round(rect.left);
            let hideLeftWidget = left < SCROLL_BUTTON_WIDTH && scrollButtonsEnabled;
            // Workaround for the case where on the scaled monitor happens bad rounding for html item size.
            // Sometimes the difference between scroll button position and widget container left side is pretty small and should be ignored
            if (!Number.isInteger(window.devicePixelRatio)) {
                hideLeftWidget = hideLeftWidget && Math.abs(left - SCROLL_BUTTON_WIDTH) > window.devicePixelRatio;
            }
            if (hideLeftWidget) {
                widget.bounds.width = 0;
                widget.bounds.left = tempLeft;
                newLeftItemsCount++;
            }
            else {
                widget.bounds.width = width - widgetDividerWidth;
                widget.bounds.left = tempLeft;
                tempLeft = tempLeft + width;
            }
            let offsetRight = left + width - widgetDividerWidth - leftButtonSize - toolbarWidth;
            if (offsetRight > 0) {
                widget.bounds.width = 0;
                newRightItemsCount++;
            }
            FEAGlobals.FSBL.Clients.Logger.system.debug("Dashbar: Setting widget position. view.setBounds", `ViewID ${(_c = widget.view) === null || _c === void 0 ? void 0 : _c.id}`, `view title ${widget.title}`, widget.url, widget.bounds, rect);
            (_d = widget.view) === null || _d === void 0 ? void 0 : _d.setBounds(widget.bounds);
        }
        if (leftItemsCount !== newLeftItemsCount)
            setLeftItemsCount(newLeftItemsCount);
        if (rightItemsCount !== newRightItemsCount)
            setRightItemsCount(newRightItemsCount);
    };
    const addWidgets = async () => {
        var _a;
        // Clone the reference since `widget` could possibly have an old value
        const _widgets = clone(widgetsRef.current);
        let tempHorizontalBounds = horizontalBounds;
        // Check against the reference since `widget` could possibly have an old value
        const newItems = dashbarItems.filter((item) => !widgetsRef.current.hasOwnProperty(item.id));
        FEAGlobals.FSBL.Clients.Logger.system.debug("Dashbar: Adding widgets", newItems);
        for (const item of newItems) {
            const widgetBounds = {
                top: verticalBounds,
                left: tempHorizontalBounds,
                width: item.width - widgetDividerWidth,
                height: item.height || dashbar.height,
            };
            _widgets[item.id] = {
                url: item.url,
                title: item.title,
                bounds: widgetBounds,
                view: null,
                width: item.width,
                raiseIntentOptions: item.raiseIntentOptions,
                loadingIconUrl: (_a = dashbar.loadingIconUrl) !== null && _a !== void 0 ? _a : defaultLoadingIconUrl,
            };
            const webContentsName = `(Dashbar) ${item.id}`;
            FEAGlobals.FSBL.Clients.Logger.system.debug("Dashbar: Adding widget webview", _widgets[item.id]);
            const bv = await FEAGlobals.finsembleWindow.addBrowserView({
                url: item.url,
                webContentsName,
                bounds: widgetBounds,
                allowAutoResize: false,
                visible: false,
            });
            bv.onContentLoaded(async () => {
                await bv.showView();
                // after widget is loaded we should re-render parent container to calculate correct widget size
                setFastRender(Date.now());
            });
            _widgets[item.id].view = bv;
            tempHorizontalBounds = tempHorizontalBounds + item.width;
        }
        // Set the state before the promises have completed to allows loading widgets to be rendered while still loading.
        setWidgets(_widgets);
        setHorizontalBounds(tempHorizontalBounds);
    };
    const removeWidgets = async () => {
        var _a, _b, _c, _d;
        const _widgets = clone(widgets);
        // Widgets is an object, with each key being a reference to an item in dashbarItems
        // but dashbarItems is an array of objects. Its necessary to loop through them to determine
        // the differences to figure out which items were removed.
        const toRemove = [];
        const remainingWidgets = [];
        Object.keys(_widgets).forEach((widgetId) => {
            let found = false;
            for (let i = 0; i < dashbarItems.length; i++) {
                const dashItem = dashbarItems[i];
                if (dashItem.id === widgetId) {
                    found = true;
                    break;
                }
            }
            if (found) {
                remainingWidgets.push(widgetId);
            }
            else {
                toRemove.push(widgetId);
            }
        });
        FEAGlobals.FSBL.Clients.Logger.system.debug("Dashbar: removing widgets", toRemove, remainingWidgets);
        // toRemove is an array of widgetIds which were no longer found in dashbarItems
        // As items are removed, the remaining items have to be shuffled to fill the space
        for (const id of toRemove) {
            if (_widgets.hasOwnProperty(id)) {
                // The remove operation can take some time to wait while content loading completed.
                // If client disable widget before content is loaded we can see UI delay.
                // To avoid it needs hide the browser view first then remove it without awaiting.
                await ((_a = _widgets[id].view) === null || _a === void 0 ? void 0 : _a.hideView());
                (_b = _widgets[id].view) === null || _b === void 0 ? void 0 : _b.remove();
                delete _widgets[id];
            }
            else {
                FEAGlobals.FSBL.Clients.Logger.system.error(`Error removing widget: ${id} from dashbar`);
            }
        }
        // Shuffle remaining views to fill any gaps that were left
        let tempNewHorizontalBounds = 0;
        for (const id of remainingWidgets) {
            const widget = _widgets[id];
            widget.bounds = {
                width: widget.bounds.width,
                height: widget.bounds.height,
                left: tempNewHorizontalBounds,
                top: widget.bounds.top,
            };
            FEAGlobals.FSBL.Clients.Logger.system.debug("Dashbar: Removed widgets. view.setBounds", `ViewID ${(_c = widget.view) === null || _c === void 0 ? void 0 : _c.id}`, `view title ${widget.title}`, widget.url, widget.bounds);
            (_d = widget.view) === null || _d === void 0 ? void 0 : _d.setBounds(widget.bounds);
            tempNewHorizontalBounds = tempNewHorizontalBounds + widget.bounds.width;
        }
        setWidgets(_widgets);
        setHorizontalBounds(tempNewHorizontalBounds);
    };
    const reorganizeWidgets = () => {
        const _widgets = clone(widgets);
        FEAGlobals.FSBL.Clients.Logger.system.debug("Dashbar: Reorganising widgets", Object.keys(widgets));
        // Each view needs to be resituated to fill any holes
        // left by views that have been removed. Use a tempBounds
        // int to keep track of new widths and set the overall width
        // once everything is re-seated
        let tempBounds = 0;
        dashbarItems.forEach((item) => {
            var _a, _b;
            const widget = _widgets[item.id];
            widget.bounds = {
                height: cachedWidgetHeight || widget.bounds.height,
                width: widget.bounds.width,
                top: cachedWidgetTop || verticalBounds,
                left: tempBounds,
            };
            FEAGlobals.FSBL.Clients.Logger.system.debug("Dashbar: Reorganising widget. view.setBounds", `ViewID ${(_a = widget.view) === null || _a === void 0 ? void 0 : _a.id}`, `view title ${widget.title}`, widget.url, widget.bounds);
            (_b = widget.view) === null || _b === void 0 ? void 0 : _b.setBounds(widget.bounds);
            delete _widgets[item.id];
            _widgets[item.id] = widget;
            tempBounds = tempBounds + item.width;
        });
        setReorganized(false);
        setWidgets(_widgets);
        setHorizontalBounds(tempBounds);
    };
    const doUpdate = async () => {
        FEAGlobals.FSBL.Clients.Logger.system.debug("Dashbar: doUpdate()");
        /**
         * To keep consistent widgets state wait while all widgets finish loading.
         * That check creates a queue of new widgets, so the client could see a delay between enabling new widget and when it actually is rendered.
         *
         * Without this check, if client is doing a widget manipulation (reordering/adding/removing) while loading,
         * the widget's position will break and some of them could disappear.
         */
        if (createWidgetTasks.length > 0) {
            FEAGlobals.FSBL.Clients.Logger.system.debug("Dashbar: doUpdate() waiting for addToComplete");
            await Promise.all(createWidgetTasks);
            FEAGlobals.FSBL.Clients.Logger.system.debug("Dashbar: doUpdate() addToComplete done. continuing doUpdate()");
            createWidgetTasks = [];
        }
        const WIDGET_COUNT = Object.keys(widgets).length;
        if (dashbarItems.length > WIDGET_COUNT) {
            //Items have been added
            await addWidgets();
        }
        else if (dashbarItems.length < WIDGET_COUNT) {
            //Items have been removed
            await removeWidgets();
        }
        else if (wasReorganized) {
            if (movingIndex)
                return;
            // Length is the same, order has changed
            reorganizeWidgets();
        }
        await setWidgetsPositions();
        setIsUpdatingState(false);
    };
    const updateItems = () => {
        if (isUpdatingState === false) {
            setIsUpdatingState(true);
            doUpdate();
        }
        else if (!secondUpdateRequired) {
            setSecondUpdateRequired(true);
        }
    };
    /**
     * When the distributed store's 'dashbaritems' is updated it will cause this hook to
     * execute. The local representation of widgets will be mutated, then a call to
     * setWidgets will re-render the component
     */
    useEffect(() => {
        updateItems();
    }, [dashbarItems]);
    useEffect(() => {
        // Force a re-render when fastRender changes
    }, [fastRender]);
    /**
     * DashbarItems can be changed very quickly before all widgets will be initialized.
     * Double-calling updateItems() fixes a bug.
     */
    useEffect(() => {
        if (secondUpdateRequired && !isUpdatingState) {
            updateItems();
            setSecondUpdateRequired(false);
        }
    }, [isUpdatingState, secondUpdateRequired]);
    /**
     * Sets the title height as determined by the DashbarItem
     * component, since the CSS can be changed by the user
     * the component will render the title and pass back
     * computed height of that title, then the Dashbar will
     * make room for item
     *
     * @param {number} titleHeight The height of the span containing the title
     * @param {string} id The key'ed id of the view
     */
    const setTitleHeight = (titleHeight, id) => {
        var _a, _b, _c;
        const widget = widgets[id];
        FEAGlobals.FSBL.Clients.Logger.system.debug("Dashbar: Setting title height", `ViewID ${(_a = widget.view) === null || _a === void 0 ? void 0 : _a.id}`, `View Title ${widget.title}`, widget.url, widget.bounds, `Title height ${titleHeight}`, `cachedTop ${cachedWidgetTop}`);
        if (cachedWidgetTop) {
            titleHeight = 0;
        }
        if (widget.bounds.height !== cachedWidgetHeight || widget.bounds.top !== cachedWidgetTop) {
            const newBounds = {
                top: cachedWidgetTop || widget.bounds.top + titleHeight,
                left: widget.bounds.left,
                height: cachedWidgetHeight || widget.bounds.height - titleHeight,
                width: widget.bounds.width,
            };
            cachedWidgetTop = newBounds.top;
            cachedWidgetHeight = newBounds.height;
            FEAGlobals.FSBL.Clients.Logger.system.debug("Dashbar: Setting title height view.setBounds", `ViewID ${(_b = widget.view) === null || _b === void 0 ? void 0 : _b.id}`, `view title ${widget.title}`, widget.url, newBounds);
            (_c = widget.view) === null || _c === void 0 ? void 0 : _c.setBounds(newBounds);
            widget.bounds = newBounds;
        }
    };
    /**
     * When a view starts moving, bring it to the front
     *
     * @param {MouseEvent} event The event passed by react-sortable-hoc
     */
    const startMove = (node, event) => {
        var _a;
        movingIndex = node.index;
        if (offsetX === null && titleGrabPosition !== null) {
            offsetX = event.clientX - titleGrabPosition;
        }
        const widget = Object.values(widgets)[node.index];
        (_a = widget.view) === null || _a === void 0 ? void 0 : _a.bringToFront();
    };
    /**
     * Event triggered by view movement
     *
     * @param {MouseEvent} event
     */
    const moveItem = (event) => {
        var _a, _b, _c;
        if (movingIndex !== null) {
            const movingWidget = Object.values(widgets)[movingIndex];
            (_a = movingWidget.view) === null || _a === void 0 ? void 0 : _a.setBounds({
                top: movingWidget.bounds.top,
                height: movingWidget.bounds.height,
                width: movingWidget.bounds.width,
                left: event.clientX - (offsetX || 0),
            });
            // Some completely not react very hacky stuff to make browserviews move in conjunction with the titles
            const elements = document.getElementsByClassName("dashbar-item-frame");
            for (const element of elements) {
                const rect = element.getBoundingClientRect();
                const widget = widgets[element.id];
                if (widget !== movingWidget && widget.bounds.left !== rect.left) {
                    widget.bounds.left = rect.left;
                    FEAGlobals.FSBL.Clients.Logger.system.debug("Dashbar: moveItem view.setBounds", `ViewID ${(_b = widget.view) === null || _b === void 0 ? void 0 : _b.id}`, `view title ${widget.title}`, widget.url, widget.bounds);
                    (_c = widget.view) === null || _c === void 0 ? void 0 : _c.setBounds(widget.bounds);
                }
            }
        }
    };
    const setGrabLocation = (location) => {
        titleGrabPosition = location;
    };
    /**
     * Every time a widget is added or removed, modify the key on the FinsembleDnDContext element forcing a rerender.
     *
     * This is a workaround for certain cases where removing a dashbar item would align the panels incorrectly in
     * FinsembleDnDContext. The scrolling works by the FinsembleDnDContext setting scrollLeft value. All calculations
     * in FinsembleDnDContext are correct but due to a browser bug, when removing component under certain conditions,
     * it somehow sets false maximum value for this scrollLeft value. Any value higher than the maximum is capped
     * causing it to half scroll an element.
     *
     */
    const elementKey = Object.entries(widgets)
        .map((entry) => entry[0])
        .sort()
        .join("-");
    const WIDGET_COUNT = Object.keys(widgets).length;
    return (React.createElement("div", { className: wrapperClasses, style: { height: dashbarHeight } },
        WIDGET_COUNT > 0 && (React.createElement(FinsembleDnDContext, { key: elementKey, onDragEnd: reorderItems, onDragStart: startMove, onDragMove: moveItem, onLayoutUpdate: setWidgetsPositions, className: "finsemble-dashbar-section rearrange-region", id: "dashbar-container", axis: AllowableDnDAxis.x, lockAxis: AllowableDnDAxis.x, allowHorizontalScroll: allowHorizontalScroll, showScrollButtonsTooltip: true, leftButtonTooltip: `${leftItemsCount} more`, rightButtonTooltip: `${rightItemsCount} more`, metadata: { dashbar } }, Object.values(widgets).map((widget, i) => (React.createElement(DashbarItem, { key: i, id: Object.keys(widgets)[i], index: i, widgetTitleGenerator: titleGenerator, widget: widget, dashbarHeight: dashbar.height, widgetDividerWidth: widgetDividerWidth, setTitleHeight: setTitleHeight, setGrabLocation: setGrabLocation }))))),
        WIDGET_COUNT === 0 && (React.createElement("div", { className: "add-dashbar-item-button", onClick: launchDashbar },
            React.createElement("span", null,
                React.createElement("i", { className: "ff-plus" })))),
        children));
};
//# sourceMappingURL=Dashbar.js.map