/*
 * Bittorrent Client using Qt and libtorrent.
 * Copyright (C) 2009  Christophe Dumez <chris@qbittorrent.org>
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * In addition, as a special exception, the copyright holders give permission to
 * link this program with the OpenSSL project's "OpenSSL" library (or with
 * modified versions of it that use the same license as the "OpenSSL" library),
 * and distribute the linked executables. You must obey the GNU General Public
 * License in all respects for all of the code used other than "OpenSSL".  If you
 * modify file(s), you may extend this exception to your version of the file(s),
 * but you are not obligated to do so. If you do not wish to do so, delete this
 * exception statement from your version.
 */

"use strict";

window.qBittorrent ??= {};
window.qBittorrent.ContextMenu ??= (() => {
    const exports = () => {
        return {
            ContextMenu: ContextMenu,
            TorrentsTableContextMenu: TorrentsTableContextMenu,
            StatusesFilterContextMenu: StatusesFilterContextMenu,
            CategoriesFilterContextMenu: CategoriesFilterContextMenu,
            TagsFilterContextMenu: TagsFilterContextMenu,
            TrackersFilterContextMenu: TrackersFilterContextMenu,
            SearchPluginsTableContextMenu: SearchPluginsTableContextMenu,
            RssFeedContextMenu: RssFeedContextMenu,
            RssArticleContextMenu: RssArticleContextMenu,
            RssDownloaderRuleContextMenu: RssDownloaderRuleContextMenu
        };
    };

    let lastShownContextMenu = null;
    class ContextMenu {
        constructor(options) {
            this.options = {
                actions: {},
                menu: "menu_id",
                stopEvent: true,
                targets: "body",
                offsets: {
                    x: 0,
                    y: 0
                },
                onShow: () => {},
                onHide: () => {},
                onClick: () => {},
                touchTimer: 600,
                ...options
            };

            // option diffs menu
            this.menu = document.getElementById(this.options.menu);

            // hide and begin the listener
            this.hide().startListener();

            // hide the menu
            this.menu.style.position = "absolute";
            this.menu.style.top = "-900000px";
            this.menu.style.display = "block";
        }

        adjustMenuPosition(e) {
            this.updateMenuItems();

            const scrollableMenuMaxHeight = document.documentElement.clientHeight * 0.75;

            if (this.menu.classList.contains("scrollableMenu"))
                this.menu.style.maxHeight = `${scrollableMenuMaxHeight}px`;

            // draw the menu off-screen to know the menu dimensions
            this.menu.style.left = "-999em";
            this.menu.style.top = "-999em";

            // position the menu
            let xPosMenu = e.pageX + this.options.offsets.x;
            let yPosMenu = e.pageY + this.options.offsets.y;
            if ((xPosMenu + this.menu.offsetWidth) > document.documentElement.clientWidth)
                xPosMenu -= this.menu.offsetWidth;
            if ((yPosMenu + this.menu.offsetHeight) > document.documentElement.clientHeight)
                yPosMenu = document.documentElement.clientHeight - this.menu.offsetHeight;
            if (xPosMenu < 0)
                xPosMenu = 0;
            if (yPosMenu < 0)
                yPosMenu = 0;
            this.menu.style.left = `${xPosMenu}px`;
            this.menu.style.top = `${yPosMenu}px`;
            this.menu.style.position = "absolute";
            this.menu.style.zIndex = "2000";

            // position the sub-menu
            const uls = this.menu.getElementsByTagName("ul");
            for (const ul of uls) {
                if (ul.classList.contains("scrollableMenu"))
                    ul.style.maxHeight = `${scrollableMenuMaxHeight}px`;
                const rectParent = ul.parentNode.getBoundingClientRect();
                const xPosOrigin = rectParent.left;
                const yPosOrigin = rectParent.bottom;
                let xPos = xPosOrigin + rectParent.width - 1;
                let yPos = yPosOrigin - rectParent.height - 1;
                if ((xPos + ul.offsetWidth) > document.documentElement.clientWidth)
                    xPos -= (ul.offsetWidth + rectParent.width - 2);
                if ((yPos + ul.offsetHeight) > document.documentElement.clientHeight)
                    yPos = document.documentElement.clientHeight - ul.offsetHeight;
                if (xPos < 0)
                    xPos = 0;
                if (yPos < 0)
                    yPos = 0;
                ul.style.marginLeft = `${xPos - xPosOrigin}px`;
                ul.style.marginTop = `${yPos - yPosOrigin}px`;
            }
        }

        setupEventListeners(elem) {
            elem.addEventListener("contextmenu", (e) => {
                this.triggerMenu(e, elem);
            });
            elem.addEventListener("click", (e) => {
                this.hide();
            });

            elem.addEventListener("touchstart", (e) => {
                this.hide();
                this.touchStartAt = performance.now();
                this.touchStartEvent = e;
            }, { passive: true });
            elem.addEventListener("touchend", (e) => {
                const now = performance.now();
                const touchStartAt = this.touchStartAt;
                const touchStartEvent = this.touchStartEvent;

                this.touchStartAt = null;
                this.touchStartEvent = null;

                const isTargetUnchanged = (Math.abs(e.changedTouches[0].pageX - touchStartEvent.changedTouches[0].pageX) <= 10) && (Math.abs(e.changedTouches[0].pageY - touchStartEvent.changedTouches[0].pageY) <= 10);
                if (((now - touchStartAt) >= this.options.touchTimer) && isTargetUnchanged)
                    this.triggerMenu(touchStartEvent, elem);
            }, { passive: true });
        }

        addTarget(t) {
            if (t.hasEventListeners)
                return;

            // prevent long press from selecting this text
            t.style.userSelect = "none";
            t.hasEventListeners = true;
            this.setupEventListeners(t);
        }

        searchAndAddTargets() {
            if (this.options.targets.length > 0) {
                for (const target of document.querySelectorAll(this.options.targets))
                    this.addTarget(target);
            }
        }

        triggerMenu(e, el) {
            if (this.options.disabled)
                return;

            // prevent default, if told to
            if (this.options.stopEvent) {
                e.preventDefault();
                e.stopPropagation();
            }
            // record this as the trigger
            this.options.element = el;
            this.adjustMenuPosition(e);
            // show the menu
            this.show();
        }

        // get things started
        startListener() {
            /* all elements */
            this.searchAndAddTargets();

            /* menu items */
            this.menu.addEventListener("click", (e) => {
                const menuItem = e.target.closest("li");
                if (!menuItem)
                    return;

                e.preventDefault();
                if (!menuItem.classList.contains("disabled")) {
                    const anchor = menuItem.firstElementChild;
                    this.execute(anchor.href.split("#")[1], this.options.element);
                    this.options.onClick.call(this, anchor, e);
                }
                else {
                    e.stopPropagation();
                }
            });

            // hide on body click
            document.body.addEventListener("click", (event) => {
                const parentNode = event.target.parentNode;

                // make sure the click was on a context menu item
                if ((parentNode !== null) && (parentNode.tagName.toLowerCase() === "li")) {
                    const grandParentNode = parentNode.parentNode;
                    if ((grandParentNode !== null) && (grandParentNode.classList.contains("contextMenu"))) {
                        const submenuNodes = parentNode.getElementsByTagName("ul");
                        if (submenuNodes.length > 0)
                            return;
                    }
                }

                this.hide();
                this.options.element = null;
            });
        }

        updateMenuItems() {}

        // show menu
        show(trigger) {
            if (lastShownContextMenu && (lastShownContextMenu !== this))
                lastShownContextMenu.hide();
            this.menu.classList.add("visible");
            this.options.onShow.call(this);
            lastShownContextMenu = this;
            return this;
        }

        // hide the menu
        hide(trigger) {
            if (lastShownContextMenu && (lastShownContextMenu.menu.style.visibility !== "hidden")) {
                this.menu.classList.remove("visible");
                this.options.onHide.call(this);
            }
            return this;
        }

        setItemChecked(item, checked) {
            this.menu.querySelector(`a[href$="${item}"]`).firstElementChild.style.opacity =
                checked ? "1" : "0";
            return this;
        }

        getItemChecked(item) {
            return this.menu.querySelector(`a[href$="${item}"]`).firstElementChild.style.opacity !== "0";
        }

        // hide an item
        hideItem(item) {
            this.menu.querySelector(`a[href$="${item}"]`).parentNode.classList.add("invisible");
            return this;
        }

        // show an item
        showItem(item) {
            this.menu.querySelector(`a[href$="${item}"]`).parentNode.classList.remove("invisible");
            return this;
        }

        // enable/disable an item
        setEnabled(item, enabled) {
            this.menu.querySelector(`:scope a[href$="${item}"]`).parentElement.classList.toggle("disabled", !enabled);
            return this;
        }

        // disable the entire menu
        disable() {
            this.options.disabled = true;
            return this;
        }

        // enable the entire menu
        enable() {
            this.options.disabled = false;
            return this;
        }

        // execute an action
        execute(action, element) {
            if (this.options.actions[action])
                this.options.actions[action](element, this, action);
            return this;
        }
    }

    class FilterListContextMenu extends ContextMenu {
        constructor(options) {
            super(options);
            this.torrentObserver = new MutationObserver((records, observer) => {
                this.updateTorrentActions();
            });
        }

        startTorrentObserver() {
            this.torrentObserver.observe(torrentsTable.tableBody, { childList: true });
        }

        stopTorrentObserver() {
            this.torrentObserver.disconnect();
        }

        updateTorrentActions() {
            const torrentsVisible = torrentsTable.tableBody.children.length > 0;
            this.setEnabled("startTorrents", torrentsVisible)
                .setEnabled("stopTorrents", torrentsVisible)
                .setEnabled("deleteTorrents", torrentsVisible);
        }
    }

    class TorrentsTableContextMenu extends ContextMenu {
        updateMenuItems() {
            let all_are_seq_dl = true;
            let there_are_seq_dl = false;
            let all_are_f_l_piece_prio = true;
            let there_are_f_l_piece_prio = false;
            let all_are_downloaded = true;
            let all_are_stopped = true;
            let there_are_stopped = false;
            let all_are_force_start = true;
            let there_are_force_start = false;
            let all_are_super_seeding = true;
            let all_are_auto_tmm = true;
            let thereAreV1Hashes = false;
            let thereAreV2Hashes = false;
            const tagCount = new Map();
            const categoryCount = new Map();

            const selectedRows = torrentsTable.selectedRowsIds();
            for (const item of selectedRows) {
                const data = torrentsTable.getRow(item).full_data;

                if (data["seq_dl"] !== true)
                    all_are_seq_dl = false;
                else
                    there_are_seq_dl = true;

                if (data["f_l_piece_prio"] !== true)
                    all_are_f_l_piece_prio = false;
                else
                    there_are_f_l_piece_prio = true;

                if (data["progress"] !== 1) // not downloaded
                    all_are_downloaded = false;
                else if (data["super_seeding"] !== true)
                    all_are_super_seeding = false;

                if ((data["state"] !== "stoppedUP") && (data["state"] !== "stoppedDL"))
                    all_are_stopped = false;
                else
                    there_are_stopped = true;

                if (data["force_start"] !== true)
                    all_are_force_start = false;
                else
                    there_are_force_start = true;

                if (data["auto_tmm"] !== true)
                    all_are_auto_tmm = false;

                if (data["infohash_v1"] !== "")
                    thereAreV1Hashes = true;

                if (data["infohash_v2"] !== "")
                    thereAreV2Hashes = true;

                const torrentTags = data["tags"].split(", ");
                for (const tag of torrentTags) {
                    const count = tagCount.get(tag);
                    tagCount.set(tag, ((count !== undefined) ? (count + 1) : 1));
                }

                const torrentCategory = data["category"];
                const count = categoryCount.get(torrentCategory);
                categoryCount.set(torrentCategory, ((count !== undefined) ? (count + 1) : 1));
            }

            // hide renameFiles when more than 1 torrent is selected
            if (selectedRows.length === 1) {
                const data = torrentsTable.getRow(selectedRows[0]).full_data;
                const metadata_downloaded = !((data["state"] === "metaDL") || (data["state"] === "forcedMetaDL") || (data["total_size"] === -1));

                this.showItem("rename");
                // hide renameFiles when metadata hasn't been downloaded yet
                metadata_downloaded
                    ? this.showItem("renameFiles")
                    : this.hideItem("renameFiles");
            }
            else {
                this.hideItem("renameFiles");
                this.hideItem("rename");
            }

            if (all_are_downloaded) {
                this.hideItem("downloadLimit");
                this.menu.querySelector("a[href$=uploadLimit]").parentNode.classList.add("separator");
                this.hideItem("sequentialDownload");
                this.hideItem("firstLastPiecePrio");
                this.showItem("superSeeding");
                this.setItemChecked("superSeeding", all_are_super_seeding);
            }
            else {
                const show_seq_dl = (all_are_seq_dl || !there_are_seq_dl);
                const show_f_l_piece_prio = (all_are_f_l_piece_prio || !there_are_f_l_piece_prio);

                this.menu.querySelector("a[href$=firstLastPiecePrio]").parentNode.classList.toggle("separator", (!show_seq_dl && show_f_l_piece_prio));

                if (show_seq_dl)
                    this.showItem("sequentialDownload");
                else
                    this.hideItem("sequentialDownload");

                if (show_f_l_piece_prio)
                    this.showItem("firstLastPiecePrio");
                else
                    this.hideItem("firstLastPiecePrio");

                this.setItemChecked("sequentialDownload", all_are_seq_dl);
                this.setItemChecked("firstLastPiecePrio", all_are_f_l_piece_prio);

                this.showItem("downloadLimit");
                this.menu.querySelector("a[href$=uploadLimit]").parentNode.classList.remove("separator");
                this.hideItem("superSeeding");
            }

            this.showItem("start");
            this.showItem("stop");
            this.showItem("forceStart");
            if (all_are_stopped)
                this.hideItem("stop");
            else if (all_are_force_start)
                this.hideItem("forceStart");
            else if (!there_are_stopped && !there_are_force_start)
                this.hideItem("start");

            this.setItemChecked("autoTorrentManagement", all_are_auto_tmm);

            this.setEnabled("copyInfohash1", thereAreV1Hashes);
            this.setEnabled("copyInfohash2", thereAreV2Hashes);

            const contextTagList = document.getElementById("contextTagList");
            for (const tag of window.qBittorrent.Client.tagMap.keys()) {
                const checkbox = contextTagList.querySelector(`a[href="#Tag/${tag}"] input[type="checkbox"]`);
                const count = tagCount.get(tag);
                const hasCount = (count !== undefined);
                const isLesser = (count < selectedRows.length);
                checkbox.indeterminate = (hasCount ? isLesser : false);
                checkbox.checked = (hasCount ? !isLesser : false);
            }

            const contextCategoryList = document.getElementById("contextCategoryList");
            for (const category of window.qBittorrent.Client.categoryMap.keys()) {
                const categoryIcon = contextCategoryList.querySelector(`a[href$="#Category/${category}"] img`);
                const count = categoryCount.get(category);
                const isEqual = ((count !== undefined) && (count === selectedRows.length));
                categoryIcon.classList.toggle("highlightedCategoryIcon", isEqual);
            }
        }

        updateCategoriesSubMenu(categories) {
            const contextCategoryList = document.getElementById("contextCategoryList");

            for (const el of [...contextCategoryList.children])
                el.remove();

            const createMenuItem = (text, imgURL, clickFn) => {
                const anchor = document.createElement("a");
                anchor.textContent = text;
                anchor.addEventListener("click", clickFn);

                const img = document.createElement("img");
                img.src = imgURL;
                img.alt = text;
                anchor.prepend(img);

                const item = document.createElement("li");
                item.appendChild(anchor);

                return item;
            };
            contextCategoryList.appendChild(createMenuItem("QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget]", "images/list-add.svg", (event) => { torrentNewCategoryFN(); }));
            contextCategoryList.appendChild(createMenuItem("QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget]", "images/edit-clear.svg", (event) => { torrentSetCategoryFN(""); }));

            const sortedCategories = [...categories.keys()];
            sortedCategories.sort(window.qBittorrent.Misc.naturalSortCollator.compare);

            let first = true;
            for (const categoryName of sortedCategories) {
                const anchor = document.createElement("a");
                anchor.href = `#Category/${categoryName}`;
                anchor.textContent = categoryName;
                anchor.addEventListener("click", (event) => {
                    event.preventDefault();
                    torrentSetCategoryFN(categoryName);
                });

                const img = document.createElement("img");
                img.src = "images/view-categories.svg";
                anchor.prepend(img);

                const setCategoryItem = document.createElement("li");
                setCategoryItem.appendChild(anchor);
                if (first) {
                    setCategoryItem.classList.add("separator");
                    first = false;
                }

                contextCategoryList.appendChild(setCategoryItem);
            }
        }

        updateTagsSubMenu(tags) {
            const contextTagList = document.getElementById("contextTagList");
            contextTagList.replaceChildren();

            const createMenuItem = (text, imgURL, clickFn) => {
                const anchor = document.createElement("a");
                anchor.textContent = text;
                anchor.addEventListener("click", clickFn);

                const img = document.createElement("img");
                img.src = imgURL;
                img.alt = text;
                anchor.prepend(img);

                const item = document.createElement("li");
                item.appendChild(anchor);

                return item;
            };
            contextTagList.appendChild(createMenuItem("QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]", "images/list-add.svg", (event) => { torrentAddTagsFN(); }));
            contextTagList.appendChild(createMenuItem("QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]", "images/edit-clear.svg", (event) => { torrentRemoveAllTagsFN(); }));

            const sortedTags = [...tags.keys()];
            sortedTags.sort(window.qBittorrent.Misc.naturalSortCollator.compare);

            for (const [i, tagName] of sortedTags.entries()) {
                const input = document.createElement("input");
                input.type = "checkbox";
                input.addEventListener("click", (event) => {
                    input.checked = !input.checked;
                });

                const anchor = document.createElement("a");
                anchor.href = `#Tag/${tagName}`;
                anchor.textContent = tagName;
                anchor.addEventListener("click", (event) => {
                    event.preventDefault();
                    torrentSetTagsFN(tagName, !input.checked);
                });
                anchor.prepend(input);

                const setTagItem = document.createElement("li");
                setTagItem.appendChild(anchor);
                if (i === 0)
                    setTagItem.classList.add("separator");

                contextTagList.appendChild(setTagItem);
            }
        }
    }

    class StatusesFilterContextMenu extends FilterListContextMenu {
        updateMenuItems() {
            this.updateTorrentActions();
        }
    }

    class CategoriesFilterContextMenu extends FilterListContextMenu {
        updateMenuItems() {
            const id = this.options.element.id;
            if ((id !== CATEGORIES_ALL) && (id !== CATEGORIES_UNCATEGORIZED)) {
                this.showItem("editCategory");
                this.showItem("deleteCategory");
                this.showItem("createSubcategory");
            }
            else {
                this.hideItem("editCategory");
                this.hideItem("deleteCategory");
                this.hideItem("createSubcategory");
            }

            this.updateTorrentActions();
        }
    }

    class TagsFilterContextMenu extends FilterListContextMenu {
        updateMenuItems() {
            const id = this.options.element.id;
            if ((id !== TAGS_ALL) && (id !== TAGS_UNTAGGED))
                this.showItem("deleteTag");
            else
                this.hideItem("deleteTag");

            this.updateTorrentActions();
        }
    }

    class TrackersFilterContextMenu extends FilterListContextMenu {
        updateMenuItems() {
            switch (this.options.element.id) {
                case TRACKERS_ALL:
                case TRACKERS_ANNOUNCE_ERROR:
                case TRACKERS_ERROR:
                case TRACKERS_TRACKERLESS:
                case TRACKERS_WARNING:
                    this.hideItem("deleteTracker");
                    break;
                default:
                    this.showItem("deleteTracker");
                    break;
            }

            this.updateTorrentActions();
        }
    }

    class SearchPluginsTableContextMenu extends ContextMenu {
        updateMenuItems() {
            const enabledColumnIndex = (text) => {
                const columns = document.querySelectorAll("#searchPluginsTableFixedHeaderRow th");
                return Array.prototype.findIndex.call(columns, (column => column.textContent === "Enabled"));
            };

            this.showItem("Enabled");
            this.setItemChecked("Enabled", (this.options.element.children[enabledColumnIndex()].textContent === "Yes"));

            this.showItem("Uninstall");
        }
    }

    class RssFeedContextMenu extends ContextMenu {
        updateMenuItems() {
            const selectedRows = window.qBittorrent.Rss.rssFeedTable.selectedRowsIds();
            this.menu.querySelector("a[href$=newSubscription]").parentNode.classList.add("separator");
            switch (selectedRows.length) {
                case 0:
                    // remove separator on top of newSubscription entry to avoid double line
                    this.menu.querySelector("a[href$=newSubscription]").parentNode.classList.remove("separator");
                    // menu when nothing selected
                    this.hideItem("update");
                    this.hideItem("markRead");
                    this.hideItem("rename");
                    this.hideItem("edit");
                    this.hideItem("delete");
                    this.showItem("newSubscription");
                    this.showItem("newFolder");
                    this.showItem("updateAll");
                    this.hideItem("copyFeedURL");
                    break;
                case 1:
                    if (selectedRows[0] === "0") {
                        // menu when "unread" feed selected
                        this.showItem("update");
                        this.showItem("markRead");
                        this.hideItem("rename");
                        this.hideItem("edit");
                        this.hideItem("delete");
                        this.showItem("newSubscription");
                        this.hideItem("newFolder");
                        this.hideItem("updateAll");
                        this.hideItem("copyFeedURL");
                    }
                    else if (window.qBittorrent.Rss.rssFeedTable.getRow(selectedRows[0]).full_data.dataUid === "") {
                        // menu when single folder selected
                        this.showItem("update");
                        this.showItem("markRead");
                        this.showItem("rename");
                        this.hideItem("edit");
                        this.showItem("delete");
                        this.showItem("newSubscription");
                        this.showItem("newFolder");
                        this.hideItem("updateAll");
                        this.hideItem("copyFeedURL");
                    }
                    else {
                        // menu when single feed selected
                        this.showItem("update");
                        this.showItem("markRead");
                        this.showItem("rename");
                        this.showItem("edit");
                        this.showItem("delete");
                        this.showItem("newSubscription");
                        this.hideItem("newFolder");
                        this.hideItem("updateAll");
                        this.showItem("copyFeedURL");
                    }
                    break;
                default:
                    // menu when multiple items selected
                    this.showItem("update");
                    this.showItem("markRead");
                    this.hideItem("rename");
                    this.hideItem("edit");
                    this.showItem("delete");
                    this.hideItem("newSubscription");
                    this.hideItem("newFolder");
                    this.hideItem("updateAll");
                    this.showItem("copyFeedURL");
                    break;
            }
        }
    }

    class RssArticleContextMenu extends ContextMenu {}

    class RssDownloaderRuleContextMenu extends ContextMenu {
        adjustMenuPosition(e) {
            this.updateMenuItems();

            // draw the menu off-screen to know the menu dimensions
            this.menu.style.left = "-999em";
            this.menu.style.top = "-999em";
            // position the menu
            let xPosMenu = e.pageX + this.options.offsets.x - document.getElementById("rssdownloaderpage").offsetLeft;
            let yPosMenu = e.pageY + this.options.offsets.y - document.getElementById("rssdownloaderpage").offsetTop;
            if ((xPosMenu + this.menu.offsetWidth) > document.documentElement.clientWidth)
                xPosMenu -= this.menu.offsetWidth;
            if ((yPosMenu + this.menu.offsetHeight) > document.documentElement.clientHeight)
                yPosMenu = document.documentElement.clientHeight - this.menu.offsetHeight;
            xPosMenu = Math.max(xPosMenu, 0);
            yPosMenu = Math.max(yPosMenu, 0);

            this.menu.style.left = `${xPosMenu}px`;
            this.menu.style.top = `${yPosMenu}px`;
            this.menu.style.position = "absolute";
            this.menu.style.zIndex = "2000";
        }
        updateMenuItems() {
            const selectedRows = window.qBittorrent.RssDownloader.rssDownloaderRulesTable.selectedRowsIds();
            this.showItem("addRule");
            switch (selectedRows.length) {
                case 0:
                    // menu when nothing selected
                    this.hideItem("deleteRule");
                    this.hideItem("renameRule");
                    this.hideItem("clearDownloadedEpisodes");
                    break;
                case 1:
                    // menu when single item selected
                    this.showItem("deleteRule");
                    this.showItem("renameRule");
                    this.showItem("clearDownloadedEpisodes");
                    break;
                default:
                    // menu when multiple items selected
                    this.showItem("deleteRule");
                    this.hideItem("renameRule");
                    this.showItem("clearDownloadedEpisodes");
                    break;
            }
        }
    }

    return exports();
})();
Object.freeze(window.qBittorrent.ContextMenu);
