/* *
 *
 *  (c) 2009-2026 Highsoft AS
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 *  Authors:
 *  - Paweł Lysy
 *  - Sebastian Bochan
 *
 * */
'use strict';
import EditRenderer from './EditRenderer.js';
import EditGlobals from './EditGlobals.js';
import ConfirmationPopup from './ConfirmationPopup.js';
import { createElement, fireEvent, merge } from '../../Shared/Utilities.js';
import { error } from '../../Core/Utilities.js';
/* *
 *
 *  Class
 *
 * */
/**
 * Accordion menu class.
 */
class AccordionMenu {
    /* *
     *
     *  Constructor
     *
     * */
    constructor(iconsURLPrefix, closeSidebar) {
        this.changedOptions = {};
        this.chartOptionsJSON = {};
        this.oldOptionsBuffer = {};
        this.waitingForConfirmation = false;
        this.iconsURLPrefix = iconsURLPrefix;
        this.closeSidebar = closeSidebar;
    }
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Renders the menu for given component.
     *
     * @param container
     * The HTML Element to render the menu in.
     *
     * @param component
     * The component to render the menu for.

     * @param sidebarMainContainer
     * The main container of the sidebar.
     */
    renderContent(container, component, sidebarMainContainer) {
        const { editMode } = component.board;
        const menu = this;
        const editableOptions = component.editableOptions.getOptions();
        let options;
        let content;
        this.component = component;
        this.oldOptionsBuffer = component.getEditableOptions();
        if (editMode) {
            this.confirmationPopup = new ConfirmationPopup(component.board.container, editMode.iconsURLPrefix, editMode, { close: { icon: '' } });
        }
        const accordionContainer = createElement('div', {
            className: EditGlobals.classNames.accordionMenu
        }, {}, container);
        for (let i = 0, end = editableOptions.length; i < end; i++) {
            options = editableOptions[i];
            content = EditRenderer.renderCollapseHeader(accordionContainer, merge({
                iconsURLPrefix: menu.iconsURLPrefix,
                lang: (component.board?.editMode || EditGlobals).lang
            }, options)).content;
            this.renderAccordion(options, content, component);
        }
        const buttonContainer = createElement('div', {
            className: EditGlobals.classNames.accordionMenuButtonsContainer
        }, {}, sidebarMainContainer);
        EditRenderer.renderButton(buttonContainer, {
            text: (component.board?.editMode || EditGlobals)
                .lang.confirmButton,
            className: EditGlobals.classNames.popupConfirmBtn,
            callback: async () => {
                await this.confirmChanges();
                fireEvent(editMode, 'confirmEditing');
            }
        });
        EditRenderer.renderButton(buttonContainer, {
            text: (component.board?.editMode || EditGlobals)
                .lang.cancelButton,
            className: EditGlobals.classNames.popupCancelBtn,
            callback: () => {
                this.cancelChanges();
                fireEvent(editMode, 'cancelEditing');
            }
        });
        sidebarMainContainer.appendChild(buttonContainer);
    }
    /**
     * Update the options object with new nested value, based on the property
     * path. If the objects in the path are not defined, the function will
     * create them.
     *
     * @param propertyPath
     * Path of the property for which the value should be updated.
     * Example: ```['chartOptions', 'chart', 'type']```
     *
     * @param value
     * New value of the property.
     */
    updateOptions(propertyPath, value) {
        const pathLength = propertyPath.length - 1;
        let currentLevel = this.changedOptions;
        let currentChartOptionsLevel;
        let currentOldChartOptionsBufferLevel;
        let currentGridOptionsLevel;
        let currentOldGridOptionsBufferLevel;
        if (pathLength === 0 && propertyPath[0] === 'chartOptions') {
            try {
                const parsedValue = JSON.parse(value);
                this.chartOptionsJSON = parsedValue;
            }
            catch (e) {
                // TODO: Handle the wrong config passed from the user.
                error('Dashboards Error: Wrong JSON config structure passed ' +
                    'as chart options. \n____________\n' +
                    String(e));
            }
        }
        for (let i = 0; i < pathLength; i++) {
            const key = propertyPath[i];
            if (!currentLevel[key]) {
                currentLevel[key] = {};
            }
            currentLevel = currentLevel[key];
            if (key === 'gridOptions') {
                const realGridOptions = this.component.grid?.options;
                if (realGridOptions) {
                    const oldOptionsBuffer = this.oldOptionsBuffer;
                    if (!oldOptionsBuffer.gridOptions) {
                        oldOptionsBuffer.gridOptions = {};
                    }
                    currentOldGridOptionsBufferLevel =
                        oldOptionsBuffer.gridOptions;
                    currentGridOptionsLevel = realGridOptions;
                }
            }
            else if (currentGridOptionsLevel &&
                currentOldGridOptionsBufferLevel) {
                currentGridOptionsLevel = currentGridOptionsLevel[key];
                if (currentOldGridOptionsBufferLevel[key] === void 0) {
                    currentOldGridOptionsBufferLevel[key] = {};
                }
                currentOldGridOptionsBufferLevel =
                    currentOldGridOptionsBufferLevel[key];
            }
            if (key === 'chartOptions') {
                const realChartOptions = this.component.chart?.options;
                if (realChartOptions) {
                    const oldOptionsBuffer = this.oldOptionsBuffer;
                    if (!oldOptionsBuffer.chartOptions) {
                        oldOptionsBuffer.chartOptions = {};
                    }
                    currentOldChartOptionsBufferLevel =
                        oldOptionsBuffer.chartOptions;
                    currentChartOptionsLevel = realChartOptions;
                }
            }
            else if (currentChartOptionsLevel &&
                currentOldChartOptionsBufferLevel) {
                currentChartOptionsLevel = currentChartOptionsLevel[key];
                if (currentOldChartOptionsBufferLevel[key] === void 0) {
                    currentOldChartOptionsBufferLevel[key] = {};
                }
                currentOldChartOptionsBufferLevel =
                    currentOldChartOptionsBufferLevel[key];
            }
        }
        const lastKey = propertyPath[pathLength];
        currentLevel[lastKey] = value;
        if (currentOldChartOptionsBufferLevel && currentChartOptionsLevel) {
            currentOldChartOptionsBufferLevel[lastKey] = (currentOldChartOptionsBufferLevel[lastKey] ??
                currentChartOptionsLevel[lastKey]);
        }
        // eslint-disable-next-line @typescript-eslint/no-floating-promises
        this.component?.update(this.changedOptions);
    }
    /**
     * Renders either a basic or nested element. This function can be
     * recursively called, if there are multiple nested options.
     *
     * @param options
     * Configuration object of the Component options.
     *
     * @param parentNode
     * A container where the accordion is rendered.
     *
     * @param component
     * the component for which the menu should be rendered.
     */
    renderAccordion(options, parentNode, component) {
        if (options.type === 'nested') {
            return this.renderNested(parentNode, options, component);
        }
        const renderFunction = EditRenderer.getRendererFunction(options.type);
        const lang = (component.board?.editMode || EditGlobals).lang;
        if (!renderFunction) {
            return;
        }
        renderFunction(parentNode, {
            ...options,
            iconsURLPrefix: this.iconsURLPrefix,
            value: component.getEditableOptionValue(options.propertyPath),
            enabledOnOffLabels: options.type === 'toggle',
            lang,
            onchange: (value) => this.updateOptions(options.propertyPath || [], value)
        });
    }
    /**
     * Render nested menu for the component.
     *
     * @param parentElement
     * HTML element to which the nested structure should be rendered to
     *
     * @param options
     * configuration object for the options
     *
     * @param component
     * The component instance for the options should be rendered
     */
    renderNested(parentElement, options, component) {
        if (!parentElement || !options.nestedOptions) {
            return;
        }
        const nestedOptions = options.nestedOptions;
        for (let i = 0, iEnd = nestedOptions.length; i < iEnd; ++i) {
            const name = nestedOptions[i].name;
            const accordionOptions = nestedOptions[i].options;
            const showToggle = !!nestedOptions[i].showToggle;
            const propertyPath = nestedOptions[i].propertyPath || [];
            const lang = (component.board?.editMode || EditGlobals).lang;
            const collapsedHeader = EditRenderer.renderCollapseHeader(parentElement, {
                name,
                isEnabled: !!component.getEditableOptionValue(propertyPath),
                iconsURLPrefix: this.iconsURLPrefix,
                showToggle: showToggle,
                onchange: (value) => this.updateOptions(propertyPath, value),
                isNested: !!accordionOptions,
                isStandalone: !accordionOptions,
                lang
            });
            for (let j = 0, jEnd = accordionOptions?.length; j < jEnd; ++j) {
                this.renderAccordion(merge(accordionOptions[j], { lang, isNested: true }), collapsedHeader.content, component);
            }
        }
        return;
    }
    /**
     * Closes the sidebar discarding changes. If there are any changes, it will
     * show a confirmation popup. If no changes, it will close the sidebar.
     */
    cancelChanges() {
        if (Object.keys(this.changedOptions).length < 1) {
            this.closeSidebar();
        }
        else {
            this.showCancelConfirmationPopup();
        }
    }
    /**
     * Confirms changes made in the component.
     *
     * @fires EditMode#componentChanged
     */
    async confirmChanges() {
        const component = this.component;
        if (!component) {
            return;
        }
        if (component.type === 'Highcharts' &&
            Object.keys(this.chartOptionsJSON).length) {
            await component.update({
                chartOptions: this.chartOptionsJSON
            });
        }
        else if (component.type === 'HTML') {
            const options = this.changedOptions;
            await component.update(options, true);
        }
        fireEvent(component.board.editMode, 'componentChanged', {
            target: component,
            changedOptions: merge({}, this.changedOptions),
            oldOptions: merge({}, this.oldOptionsBuffer)
        });
        this.changedOptions = {};
        this.chartOptionsJSON = {};
        this.closeSidebar();
    }
    /**
     * Discards changes made in the component.
     *
     * @fires EditMode#componentChangesDiscarded
     */
    async discardChanges() {
        const component = this.component;
        if (!component) {
            return;
        }
        await component.update(this.oldOptionsBuffer);
        fireEvent(component.board.editMode, 'componentChangesDiscarded', {
            target: component,
            changedOptions: merge({}, this.changedOptions),
            oldOptions: merge({}, this.oldOptionsBuffer)
        });
        this.changedOptions = {};
        this.chartOptionsJSON = {};
    }
    /**
     * Shows a confirmation popup when the user tries to discard changes.
     */
    showCancelConfirmationPopup() {
        const popup = this.confirmationPopup;
        const editMode = this.component?.board?.editMode;
        if (!popup || !editMode || this.waitingForConfirmation) {
            return;
        }
        this.waitingForConfirmation = true;
        popup.show({
            text: editMode.lang.confirmDiscardChanges,
            confirmButton: {
                value: editMode.lang.confirmButton,
                callback: async () => {
                    await this.discardChanges();
                    this.waitingForConfirmation = false;
                    this.closeSidebar();
                },
                context: this
            },
            cancelButton: {
                value: editMode.lang.cancelButton,
                callback: () => {
                    popup.closePopup();
                    editMode.setEditOverlay();
                    setTimeout(() => {
                        this.waitingForConfirmation = false;
                    }, 100);
                }
            }
        });
    }
}
/* *
 *
 *  Default Export
 *
 * */
export default AccordionMenu;
