/*
 * Copyright (C) 2023-2024  Yomitan Authors
 * Copyright (C) 2020-2022  Yomichan Authors
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

import {EventListenerCollection} from '../../core/event-listener-collection.js';
import {clone} from '../../core/utilities.js';
import {querySelectorNotNull} from '../../dom/query-selector.js';
import {ProfileConditionsUI} from './profile-conditions-ui.js';

export class ProfileController {
    /**
     * @param {import('./settings-controller.js').SettingsController} settingsController
     * @param {import('./modal-controller.js').ModalController} modalController
     */
    constructor(settingsController, modalController) {
        /** @type {import('./settings-controller.js').SettingsController} */
        this._settingsController = settingsController;
        /** @type {import('./modal-controller.js').ModalController} */
        this._modalController = modalController;
        /** @type {ProfileConditionsUI} */
        this._profileConditionsUI = new ProfileConditionsUI(settingsController);
        /** @type {?number} */
        this._profileConditionsIndex = null;
        /** @type {HTMLSelectElement} */
        this._profileActiveSelect = querySelectorNotNull(document, '#profile-active-select');
        /** @type {HTMLSelectElement} */
        this._profileTargetSelect = querySelectorNotNull(document, '#profile-target-select');
        /** @type {HTMLSelectElement} */
        this._profileCopySourceSelect = querySelectorNotNull(document, '#profile-copy-source-select');
        /** @type {HTMLElement} */
        this._removeProfileNameElement = querySelectorNotNull(document, '#profile-remove-name');
        /** @type {HTMLButtonElement} */
        this._profileAddButton = querySelectorNotNull(document, '#profile-add-button');
        /** @type {HTMLButtonElement} */
        this._profileRemoveConfirmButton = querySelectorNotNull(document, '#profile-remove-confirm-button');
        /** @type {HTMLButtonElement} */
        this._profileCopyConfirmButton = querySelectorNotNull(document, '#profile-copy-confirm-button');
        /** @type {HTMLElement} */
        this._profileEntryListContainer = querySelectorNotNull(document, '#profile-entry-list');
        /** @type {HTMLElement} */
        this._profileConditionsProfileName = querySelectorNotNull(document, '#profile-conditions-profile-name');
        /** @type {?import('./modal.js').Modal} */
        this._profileRemoveModal = null;
        /** @type {?import('./modal.js').Modal} */
        this._profileCopyModal = null;
        /** @type {?import('./modal.js').Modal} */
        this._profileConditionsModal = null;
        /** @type {boolean} */
        this._profileEntriesSupported = false;
        /** @type {ProfileEntry[]} */
        this._profileEntryList = [];
        /** @type {import('settings').Profile[]} */
        this._profiles = [];
        /** @type {number} */
        this._profileCurrent = 0;
    }

    /** @type {number} */
    get profileCount() {
        return this._profiles.length;
    }

    /** @type {number} */
    get profileCurrentIndex() {
        return this._profileCurrent;
    }

    /** */
    async prepare() {
        const {platform: {os}} = await this._settingsController.application.api.getEnvironmentInfo();
        this._profileConditionsUI.os = os;

        this._profileRemoveModal = this._modalController.getModal('profile-remove');
        this._profileCopyModal = this._modalController.getModal('profile-copy');
        this._profileConditionsModal = this._modalController.getModal('profile-conditions');

        this._profileEntriesSupported = (this._profileEntryListContainer !== null);

        if (this._profileActiveSelect !== null) { this._profileActiveSelect.addEventListener('change', this._onProfileActiveChange.bind(this), false); }
        if (this._profileTargetSelect !== null) { this._profileTargetSelect.addEventListener('change', this._onProfileTargetChange.bind(this), false); }
        if (this._profileAddButton !== null) { this._profileAddButton.addEventListener('click', this._onAdd.bind(this), false); }
        if (this._profileRemoveConfirmButton !== null) { this._profileRemoveConfirmButton.addEventListener('click', this._onDeleteConfirm.bind(this), false); }
        if (this._profileCopyConfirmButton !== null) { this._profileCopyConfirmButton.addEventListener('click', this._onCopyConfirm.bind(this), false); }

        this._profileConditionsUI.on('conditionGroupCountChanged', this._onConditionGroupCountChanged.bind(this));
        this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this));
        void this._onOptionsChanged();
    }

    /**
     * @param {number} profileIndex
     * @param {number} offset
     */
    async moveProfile(profileIndex, offset) {
        if (this._getProfile(profileIndex) === null) { return; }

        const profileIndexNew = Math.max(0, Math.min(this._profiles.length - 1, profileIndex + offset));
        if (profileIndex === profileIndexNew) { return; }

        await this.swapProfiles(profileIndex, profileIndexNew);
    }

    /**
     * @param {number} profileIndex
     * @param {string} value
     */
    async setProfileName(profileIndex, value) {
        const profile = this._getProfile(profileIndex);
        if (profile === null) { return; }

        profile.name = value;
        this._updateSelectName(profileIndex, value);

        const profileEntry = this._getProfileEntry(profileIndex);
        if (profileEntry !== null) { profileEntry.setName(value); }

        await this._settingsController.setGlobalSetting(`profiles[${profileIndex}].name`, value);
    }

    /**
     * @param {number} profileIndex
     */
    async setDefaultProfile(profileIndex) {
        const profile = this._getProfile(profileIndex);
        if (profile === null) { return; }

        /** @type {HTMLSelectElement} */ (this._profileActiveSelect).value = `${profileIndex}`;
        this._profileCurrent = profileIndex;

        const profileEntry = this._getProfileEntry(profileIndex);
        if (profileEntry !== null) { profileEntry.setIsDefault(true); }

        await this._settingsController.setGlobalSetting('profileCurrent', profileIndex);
    }

    /**
     * @param {number} sourceProfileIndex
     * @param {number} destinationProfileIndex
     */
    async copyProfile(sourceProfileIndex, destinationProfileIndex) {
        const sourceProfile = this._getProfile(sourceProfileIndex);
        if (sourceProfile === null || !this._getProfile(destinationProfileIndex)) { return; }

        const options = clone(sourceProfile.options);
        this._profiles[destinationProfileIndex].options = options;

        this._updateProfileSelectOptions();

        const destinationProfileEntry = this._getProfileEntry(destinationProfileIndex);
        if (destinationProfileEntry !== null) {
            destinationProfileEntry.updateState();
        }

        await this._settingsController.modifyGlobalSettings([{
            action: 'set',
            path: `profiles[${destinationProfileIndex}].options`,
            value: options
        }]);

        await this._settingsController.refresh();
    }

    /**
     * @param {number} profileIndex
     */
    async duplicateProfile(profileIndex) {
        const profile = this._getProfile(profileIndex);
        if (profile === null) { return; }

        // Create new profile
        const newProfile = clone(profile);
        newProfile.name = this._createCopyName(profile.name, this._profiles, 100);

        // Update state
        const index = this._profiles.length;
        this._profiles.push(newProfile);
        if (this._profileEntriesSupported) {
            this._addProfileEntry(index);
        }
        this._updateProfileSelectOptions();

        // Modify settings
        await this._settingsController.modifyGlobalSettings([{
            action: 'splice',
            path: 'profiles',
            start: index,
            deleteCount: 0,
            items: [newProfile]
        }]);

        // Update profile index
        this._settingsController.profileIndex = index;
    }

    /**
     * @param {number} profileIndex
     */
    async deleteProfile(profileIndex) {
        const profile = this._getProfile(profileIndex);
        if (profile === null || this.profileCount <= 1) { return; }

        // Get indices
        let profileCurrentNew = this._profileCurrent;
        const settingsProfileIndex = this._settingsController.profileIndex;

        // Construct settings modifications
        /** @type {import('settings-modifications').Modification[]} */
        const modifications = [{
            action: 'splice',
            path: 'profiles',
            start: profileIndex,
            deleteCount: 1,
            items: []
        }];
        if (profileCurrentNew >= profileIndex) {
            profileCurrentNew = Math.min(profileCurrentNew - 1, this._profiles.length - 1);
            modifications.push({
                action: 'set',
                path: 'profileCurrent',
                value: profileCurrentNew
            });
        }

        // Update state
        this._profileCurrent = profileCurrentNew;

        this._profiles.splice(profileIndex, 1);

        if (profileIndex < this._profileEntryList.length) {
            const profileEntry = this._profileEntryList[profileIndex];
            profileEntry.cleanup();
            this._profileEntryList.splice(profileIndex, 1);

            for (let i = profileIndex, ii = this._profileEntryList.length; i < ii; ++i) {
                this._profileEntryList[i].index = i;
            }
        }

        const profileEntry2 = this._getProfileEntry(profileCurrentNew);
        if (profileEntry2 !== null) {
            profileEntry2.setIsDefault(true);
        }

        this._updateProfileSelectOptions();

        // Update profile index
        if (settingsProfileIndex === profileIndex) {
            this._settingsController.profileIndex = profileCurrentNew;
        }

        // Modify settings
        await this._settingsController.modifyGlobalSettings(modifications);
    }

    /**
     * @param {number} index1
     * @param {number} index2
     */
    async swapProfiles(index1, index2) {
        const profile1 = this._getProfile(index1);
        const profile2 = this._getProfile(index2);
        if (profile1 === null || profile2 === null || index1 === index2) { return; }

        // Get swapped indices
        const profileCurrent = this._profileCurrent;
        const profileCurrentNew = this._getSwappedValue(profileCurrent, index1, index2);

        const settingsProfileIndex = this._settingsController.profileIndex;
        const settingsProfileIndexNew = this._getSwappedValue(settingsProfileIndex, index1, index2);

        // Construct settings modifications
        /** @type {import('settings-modifications').Modification[]} */
        const modifications = [{
            action: 'swap',
            path1: `profiles[${index1}]`,
            path2: `profiles[${index2}]`
        }];
        if (profileCurrentNew !== profileCurrent) {
            modifications.push({
                action: 'set',
                path: 'profileCurrent',
                value: profileCurrentNew
            });
        }

        // Update state
        this._profileCurrent = profileCurrentNew;

        this._profiles[index1] = profile2;
        this._profiles[index2] = profile1;

        const entry1 = this._getProfileEntry(index1);
        const entry2 = this._getProfileEntry(index2);
        if (entry1 !== null && entry2 !== null) {
            entry1.index = index2;
            entry2.index = index1;
            this._swapDomNodes(entry1.node, entry2.node);
            this._profileEntryList[index1] = entry2;
            this._profileEntryList[index2] = entry1;
        }

        this._updateProfileSelectOptions();

        // Modify settings
        await this._settingsController.modifyGlobalSettings(modifications);

        // Update profile index
        if (settingsProfileIndex !== settingsProfileIndexNew) {
            this._settingsController.profileIndex = settingsProfileIndexNew;
        }
    }

    /**
     * @param {number} profileIndex
     */
    openDeleteProfileModal(profileIndex) {
        const profile = this._getProfile(profileIndex);
        if (profile === null || this.profileCount <= 1) { return; }

        /** @type {HTMLElement} */ (this._removeProfileNameElement).textContent = profile.name;
        /** @type {import('./modal.js').Modal} */ (this._profileRemoveModal).node.dataset.profileIndex = `${profileIndex}`;
        /** @type {import('./modal.js').Modal} */ (this._profileRemoveModal).setVisible(true);
    }

    /**
     * @param {number} profileIndex
     */
    openCopyProfileModal(profileIndex) {
        const profile = this._getProfile(profileIndex);
        if (profile === null || this.profileCount <= 1) { return; }

        let copyFromIndex = this._profileCurrent;
        if (copyFromIndex === profileIndex) {
            if (profileIndex !== 0) {
                copyFromIndex = 0;
            } else if (this.profileCount > 1) {
                copyFromIndex = 1;
            }
        }

        const profileIndexString = `${profileIndex}`;
        const select = /** @type {HTMLSelectElement} */ (this._profileCopySourceSelect);
        for (const option of select.querySelectorAll('option')) {
            const {value} = option;
            option.disabled = (value === profileIndexString);
        }
        select.value = `${copyFromIndex}`;

        /** @type {import('./modal.js').Modal} */ (this._profileCopyModal).node.dataset.profileIndex = `${profileIndex}`;
        /** @type {import('./modal.js').Modal} */ (this._profileCopyModal).setVisible(true);
    }

    /**
     * @param {number} profileIndex
     */
    openProfileConditionsModal(profileIndex) {
        const profile = this._getProfile(profileIndex);
        if (profile === null) { return; }

        if (this._profileConditionsModal === null) { return; }
        this._profileConditionsModal.setVisible(true);

        this._profileConditionsUI.cleanup();
        this._profileConditionsIndex = profileIndex;
        void this._profileConditionsUI.prepare(profileIndex);
        if (this._profileConditionsProfileName !== null) {
            this._profileConditionsProfileName.textContent = profile.name;
        }
    }

    // Private

    /** */
    async _onOptionsChanged() {
        // Update state
        const {profiles, profileCurrent} = await this._settingsController.getOptionsFull();
        this._profiles = profiles;
        this._profileCurrent = profileCurrent;

        const settingsProfileIndex = this._settingsController.profileIndex;

        // Udpate UI
        this._updateProfileSelectOptions();

        /** @type {HTMLSelectElement} */ (this._profileActiveSelect).value = `${profileCurrent}`;
        /** @type {HTMLSelectElement} */ (this._profileTargetSelect).value = `${settingsProfileIndex}`;

        // Update profile conditions
        this._profileConditionsUI.cleanup();
        const conditionsProfile = this._getProfile(this._profileConditionsIndex !== null ? this._profileConditionsIndex : settingsProfileIndex);
        if (conditionsProfile !== null) {
            void this._profileConditionsUI.prepare(settingsProfileIndex);
        }

        // Udpate profile entries
        for (const entry of this._profileEntryList) {
            entry.cleanup();
        }
        this._profileEntryList = [];
        if (this._profileEntriesSupported) {
            for (let i = 0, ii = profiles.length; i < ii; ++i) {
                this._addProfileEntry(i);
            }
        }
    }

    /**
     * @param {Event} e
     */
    _onProfileActiveChange(e) {
        const element = /** @type {HTMLSelectElement} */ (e.currentTarget);
        const value = this._tryGetValidProfileIndex(element.value);
        if (value === null) { return; }
        void this.setDefaultProfile(value);
    }

    /**
     * @param {Event} e
     */
    _onProfileTargetChange(e) {
        const element = /** @type {HTMLSelectElement} */ (e.currentTarget);
        const value = this._tryGetValidProfileIndex(element.value);
        if (value === null) { return; }
        this._settingsController.profileIndex = value;
    }

    /** */
    _onAdd() {
        void this.duplicateProfile(this._settingsController.profileIndex);
    }

    /** */
    _onDeleteConfirm() {
        const modal = /** @type {import('./modal.js').Modal} */ (this._profileRemoveModal);
        modal.setVisible(false);
        const {node} = modal;
        const profileIndex = node.dataset.profileIndex;
        delete node.dataset.profileIndex;

        const validProfileIndex = this._tryGetValidProfileIndex(profileIndex);
        if (validProfileIndex === null) { return; }

        void this.deleteProfile(validProfileIndex);
    }

    /** */
    _onCopyConfirm() {
        const modal = /** @type {import('./modal.js').Modal} */ (this._profileCopyModal);
        modal.setVisible(false);
        const {node} = modal;
        const destinationProfileIndex = node.dataset.profileIndex;
        delete node.dataset.profileIndex;

        const validDestinationProfileIndex = this._tryGetValidProfileIndex(destinationProfileIndex);
        if (validDestinationProfileIndex === null) { return; }

        const sourceProfileIndex = this._tryGetValidProfileIndex(/** @type {HTMLSelectElement} */ (this._profileCopySourceSelect).value);
        if (sourceProfileIndex === null) { return; }

        void this.copyProfile(sourceProfileIndex, validDestinationProfileIndex);
    }

    /**
     * @param {import('profile-conditions-ui').EventArgument<'conditionGroupCountChanged'>} details
     */
    _onConditionGroupCountChanged({count, profileIndex}) {
        if (profileIndex >= 0 && profileIndex < this._profileEntryList.length) {
            const profileEntry = this._profileEntryList[profileIndex];
            profileEntry.setConditionGroupsCount(count);
        }
    }

    /**
     * @param {number} profileIndex
     */
    _addProfileEntry(profileIndex) {
        const profile = this._profiles[profileIndex];
        const node = /** @type {HTMLElement} */ (this._settingsController.instantiateTemplate('profile-entry'));
        const entry = new ProfileEntry(this, node, profile, profileIndex);
        this._profileEntryList.push(entry);
        entry.prepare();
        /** @type {HTMLElement} */ (this._profileEntryListContainer).appendChild(node);
    }

    /** */
    _updateProfileSelectOptions() {
        for (const select of this._getAllProfileSelects()) {
            const fragment = document.createDocumentFragment();
            for (let i = 0; i < this._profiles.length; ++i) {
                const profile = this._profiles[i];
                const option = document.createElement('option');
                option.value = `${i}`;
                option.textContent = profile.name;
                fragment.appendChild(option);
            }
            select.textContent = '';
            select.appendChild(fragment);
        }
    }

    /**
     * @param {number} index
     * @param {string} name
     */
    _updateSelectName(index, name) {
        const optionValue = `${index}`;
        for (const select of this._getAllProfileSelects()) {
            for (const option of select.querySelectorAll('option')) {
                if (option.value === optionValue) {
                    option.textContent = name;
                }
            }
        }
    }

    /**
     * @returns {HTMLSelectElement[]}
     */
    _getAllProfileSelects() {
        return [
            /** @type {HTMLSelectElement} */ (this._profileActiveSelect),
            /** @type {HTMLSelectElement} */ (this._profileTargetSelect),
            /** @type {HTMLSelectElement} */ (this._profileCopySourceSelect)
        ];
    }

    /**
     * @param {string|undefined} stringValue
     * @returns {?number}
     */
    _tryGetValidProfileIndex(stringValue) {
        if (typeof stringValue !== 'string') { return null; }
        const intValue = Number.parseInt(stringValue, 10);
        return (
            Number.isFinite(intValue) &&
            intValue >= 0 &&
            intValue < this.profileCount ?
            intValue :
            null
        );
    }

    /**
     * @param {string} name
     * @param {import('settings').Profile[]} profiles
     * @param {number} maxUniqueAttempts
     * @returns {string}
     */
    _createCopyName(name, profiles, maxUniqueAttempts) {
        let space, index, prefix, suffix;
        const match = /^([\w\W]*\(Copy)((\s+)(\d+))?(\)\s*)$/.exec(name);
        if (match === null) {
            prefix = `${name} (Copy`;
            space = '';
            index = '';
            suffix = ')';
        } else {
            prefix = match[1];
            suffix = match[5];
            if (typeof match[2] === 'string') {
                space = match[3];
                index = Number.parseInt(match[4], 10) + 1;
            } else {
                space = ' ';
                index = 2;
            }
        }

        let i = 0;
        while (true) {
            const newName = `${prefix}${space}${index}${suffix}`;
            if (i++ >= maxUniqueAttempts || profiles.findIndex((profile) => profile.name === newName) < 0) {
                return newName;
            }
            if (typeof index !== 'number') {
                index = 2;
                space = ' ';
            } else {
                ++index;
            }
        }
    }

    /**
     * @template [T=unknown]
     * @param {T} currentValue
     * @param {T} value1
     * @param {T} value2
     * @returns {T}
     */
    _getSwappedValue(currentValue, value1, value2) {
        if (currentValue === value1) { return value2; }
        if (currentValue === value2) { return value1; }
        return currentValue;
    }

    /**
     * @param {number} profileIndex
     * @returns {?import('settings').Profile}
     */
    _getProfile(profileIndex) {
        return (profileIndex >= 0 && profileIndex < this._profiles.length ? this._profiles[profileIndex] : null);
    }

    /**
     * @param {number} profileIndex
     * @returns {?ProfileEntry}
     */
    _getProfileEntry(profileIndex) {
        return (profileIndex >= 0 && profileIndex < this._profileEntryList.length ? this._profileEntryList[profileIndex] : null);
    }

    /**
     * @param {Element} node1
     * @param {Element} node2
     */
    _swapDomNodes(node1, node2) {
        const parent1 = node1.parentNode;
        const parent2 = node2.parentNode;
        const next1 = node1.nextSibling;
        const next2 = node2.nextSibling;
        if (node2 !== next1 && parent1 !== null) { parent1.insertBefore(node2, next1); }
        if (node1 !== next2 && parent2 !== null) { parent2.insertBefore(node1, next2); }
    }
}

class ProfileEntry {
    /**
     * @param {ProfileController} profileController
     * @param {HTMLElement} node
     * @param {import('settings').Profile} profile
     * @param {number} index
     */
    constructor(profileController, node, profile, index) {
        /** @type {ProfileController} */
        this._profileController = profileController;
        /** @type {HTMLElement} */
        this._node = node;
        /** @type {import('settings').Profile} */
        this._profile = profile;
        /** @type {number} */
        this._index = index;
        /** @type {HTMLInputElement} */
        this._isDefaultRadio = querySelectorNotNull(node, '.profile-entry-is-default-radio');
        /** @type {HTMLInputElement} */
        this._nameInput = querySelectorNotNull(node, '.profile-entry-name-input');
        /** @type {HTMLElement} */
        this._countLink = querySelectorNotNull(node, '.profile-entry-condition-count-link');
        /** @type {HTMLElement} */
        this._countText = querySelectorNotNull(node, '.profile-entry-condition-count');
        /** @type {HTMLButtonElement} */
        this._menuButton = querySelectorNotNull(node, '.profile-entry-menu-button');
        /** @type {EventListenerCollection} */
        this._eventListeners = new EventListenerCollection();
    }

    /** @type {number} */
    get index() {
        return this._index;
    }

    set index(value) {
        this._index = value;
    }

    /** @type {HTMLElement} */
    get node() {
        return this._node;
    }

    /** */
    prepare() {
        this.updateState();

        this._eventListeners.addEventListener(this._isDefaultRadio, 'change', this._onIsDefaultRadioChange.bind(this), false);
        this._eventListeners.addEventListener(this._nameInput, 'input', this._onNameInputInput.bind(this), false);
        this._eventListeners.addEventListener(this._countLink, 'click', this._onConditionsCountLinkClick.bind(this), false);
        this._eventListeners.addEventListener(this._menuButton, 'menuOpen', this._onMenuOpen.bind(this), false);
        this._eventListeners.addEventListener(this._menuButton, 'menuClose', this._onMenuClose.bind(this), false);
    }

    /** */
    cleanup() {
        this._eventListeners.removeAllEventListeners();
        if (this._node.parentNode !== null) {
            this._node.parentNode.removeChild(this._node);
        }
    }

    /**
     * @param {string} value
     */
    setName(value) {
        if (this._nameInput.value === value) { return; }
        this._nameInput.value = value;
    }

    /**
     * @param {boolean} value
     */
    setIsDefault(value) {
        this._isDefaultRadio.checked = value;
    }

    /** */
    updateState() {
        this._nameInput.value = this._profile.name;
        this._countText.textContent = `${this._profile.conditionGroups.length}`;
        this._isDefaultRadio.checked = (this._index === this._profileController.profileCurrentIndex);
    }

    /**
     * @param {number} count
     */
    setConditionGroupsCount(count) {
        this._countText.textContent = `${count}`;
    }

    // Private

    /**
     * @param {Event} e
     */
    _onIsDefaultRadioChange(e) {
        const element = /** @type {HTMLInputElement} */ (e.currentTarget);
        if (!element.checked) { return; }
        void this._profileController.setDefaultProfile(this._index);
    }

    /**
     * @param {Event} e
     */
    _onNameInputInput(e) {
        const element = /** @type {HTMLInputElement} */ (e.currentTarget);
        const name = element.value;
        void this._profileController.setProfileName(this._index, name);
    }

    /** */
    _onConditionsCountLinkClick() {
        this._profileController.openProfileConditionsModal(this._index);
    }

    /**
     * @param {import('popup-menu').MenuOpenEvent} e
     */
    _onMenuOpen(e) {
        const bodyNode = e.detail.menu.bodyNode;
        const count = this._profileController.profileCount;
        this._setMenuActionEnabled(bodyNode, 'moveUp', this._index > 0);
        this._setMenuActionEnabled(bodyNode, 'moveDown', this._index < count - 1);
        this._setMenuActionEnabled(bodyNode, 'copyFrom', count > 1);
        this._setMenuActionEnabled(bodyNode, 'delete', count > 1);
    }

    /**
     * @param {import('popup-menu').MenuCloseEvent} e
     */
    _onMenuClose(e) {
        switch (e.detail.action) {
            case 'moveUp':
                void this._profileController.moveProfile(this._index, -1);
                break;
            case 'moveDown':
                void this._profileController.moveProfile(this._index, 1);
                break;
            case 'copyFrom':
                this._profileController.openCopyProfileModal(this._index);
                break;
            case 'editConditions':
                this._profileController.openProfileConditionsModal(this._index);
                break;
            case 'duplicate':
                void this._profileController.duplicateProfile(this._index);
                break;
            case 'delete':
                this._profileController.openDeleteProfileModal(this._index);
                break;
        }
    }

    /**
     * @param {Element} menu
     * @param {string} action
     * @param {boolean} enabled
     */
    _setMenuActionEnabled(menu, action, enabled) {
        const element = /** @type {HTMLButtonElement} */ (menu.querySelector(`[data-menu-action="${action}"]`));
        if (element === null) { return; }
        element.disabled = !enabled;
    }
}