Source: plot/buttons/PlotlyCustomModeBar.js

export { PlotlyCustomModeBar }

/**
 * Class extending plotly's mode bar functionality.
 */
class PlotlyCustomModeBar {

    /**
     * Create a custom plotly mode bar.
     * @param {HTMLElement} graphDiv - Plotly graphDiv configuration.
     * @param {Array} [data=[]] - Plotly data configuration.
     * @param {Object} [layout={}] - Plotly layout configuration.
     * @param {Object} [config={}] - Plotly config configuration.
     */
    constructor(graphDiv, data = [], layout = {}, config = {}) {

        this.graphDiv = graphDiv;
        this.data = data;
        this.layout = layout;
        this.config = config;

        this.customButtons = [];
    }

    /**
     * Register custom buttons by providing their plotly button configuration.
     * @param {Array} buttonList - Containing plotly's button objects.
     */
    registerCustomButtons(buttonList = []) {
        this.customButtons = this.customButtons.concat(buttonList);
        this.insertCustomButton();
    }

    _getActiveButtons() {
        const buttonsToRemove = [].concat(this.layout?.modebar?.remove, this.config?.modeBarButtonsToRemove);
        const buttonsToAdd = [].concat(this.layout?.modebar?.add, this.config?.modeBarButtonsToAdd);

        const buttonsToRemoveFlat = buttonsToRemove.flat();
        const buttonsToAddFlat = buttonsToAdd.flat();
        return [buttonsToAddFlat, buttonsToRemoveFlat];
    }

    /**
     * Check if a button is configured to be visible in plotly's mode bar.
     * @param {String} buttonName - Name of the button.
     * @returns {Boolean} Status.
     */
    isButtonRequested(buttonName) {
        const [buttonsToAddFlat, buttonsToRemoveFlat] = this._getActiveButtons();
        if (buttonsToAddFlat.includes(buttonName) && !buttonsToRemoveFlat.includes(buttonName))
            return true;
        return false;
    }

    /** 
     * Set the order in which the buttons appear in Plotly's mode bar.
     * 
     * @implNote: This method applies the predefined button order with respect to plotly's add (A) and remove button variables (B).
     * It uses plotly's "modeBarButton" configuration, that in contrast to this method, does not take A and B into account.
     * Consequently, when buttons should be ordered, the current plotly version is not sufficient enough for vef's plotly inheritance.
     * @param {Array} defaultButtonOrder - List containing the sorted button names.
     */
    applyButtonOrder(defaultButtonOrder) {

        const modeBarButtons = this.config?.modeBarButtons;
        if (modeBarButtons?.length > 1) {
            console.warn('Plotly variable "modeBarButton" is already defined, no ordering of buttons based on preset');
            return;
        }

        const [buttonsToAddFlat, buttonsToRemoveFlat] = this._getActiveButtons();
        buttonsToAddFlat.sort((a, b) => {
            return defaultButtonOrder.indexOf(a.name || a) - defaultButtonOrder.indexOf(b.name || b);
        });

        const setModeBarButtons = buttonsToAddFlat.reduce((acc, curr, idx) => {
            if (!buttonsToRemoveFlat.includes(curr))
                acc.push([curr]);
            return acc;
        }, []);
        this.config.modeBarButtons = setModeBarButtons;
    }

    /**
     * Replace the names of the custom buttons with the button object. Thus, the names of custom button can be used in the plotly configuration instead of the button object.
     */
    insertCustomButton() {
        if (this.layout?.modebar?.add)
            this.layout.modebar.add = this._insertCustomButton(this.layout?.modebar?.add);
        if (this.config?.modeBarButtonsToAdd)
            this.config.modeBarButtonsToAdd = this._insertCustomButton(this.config?.modeBarButtonsToAdd);
        if (this.config?.modeBarButtons)
            this.config.modeBarButtons = this._insertCustomButton(this.config?.modeBarButtons);
    }

    _insertCustomButton(buttonList) {
        if (buttonList && buttonList.length > 0) {
            const buttonListCollect = [];
            buttonList.forEach((buttonGroup) => {
                if (!Array.isArray(buttonGroup)) buttonGroup = [buttonGroup];
                const buttonGroupCollect = [];
                buttonGroup.forEach((button) => {
                    const matchedCustomButton = this.customButtons.find(item => item.name == button);
                    if (matchedCustomButton) button = matchedCustomButton;
                    buttonGroupCollect.push(button);
                });
                if (buttonGroupCollect.length > 0)
                    buttonListCollect.push(buttonGroupCollect);
            });
            buttonList = buttonListCollect;
        }
        return buttonList;
    }
}