<style>
    #rssdownloaderpage_content {
        height: calc(100% - 30px);
    }

    #RssDownloader {
        height: 100%;
    }

    #leftRssDownloaderColumn {
        width: 25%;
        float: left;
    }

    #rulesTable,
    #rssDownloaderArticlesTable {
        overflow: auto;
        width: 100%;
        height: 100%;
    }

    #centerRssDownloaderColumn {
        width: 50%;
        float: left;
    }

    #rightRssDownloaderColumn {
        width: 25%;
        float: left;
    }

    .fullWidth {
        width: 100%;
        max-width: none;
    }

    .noWrap {
        white-space: nowrap;
    }

    #rssDownloaderFeeds {
        height: calc(100% - 355px);
        overflow: hidden;
    }

    #rssDownloaderFeedsTable {
        height: calc(100% - 21px);
        width: 100%;
        overflow: auto;
    }

    #saveButton {
        margin-top: 5px;
        width: 100%;
    }

    .articleTableFeed td {
        font-weight: bold;
    }

    .articleTableArticle td {
        padding-left: 22px;
    }

    #rssDownloaderDisabled {
        color: var(--color-text-red);
        font-style: italic;
        margin-bottom: 10px;
    }

    #rulesTable table,
    #rssDownloaderFeedsTable table,
    #rssDownloaderArticlesTable table {
        width: 100%;
    }

    #ignoreDaysValue {
        width: 5em;
    }

    #lastMatchDiv {
        float: right;
    }

    #ruleSettings {
        padding: 4px 10px 4px 10px
    }

    #topMenuBar {
        height: 26px;
    }

    #rulesTableDesc {
        vertical-align: middle;
    }

    .imageButton {
        height: 22px;
        width: 24px;
        margin: 1px 2px 1px 2px;
        border: 1px solid;
        border-radius: 2px;
        background-color: #efefef;
        border-color: #767676;
        background-position: center center;
        vertical-align: middle;
    }

    .imageButton:hover {
        background-color: #e5e5e5;
        border-color: #4f4f4f;
    }

    .imageButton:active {
        background-color: #f5f5f5;
        border-color: #8d8d8d;
    }

    #newRuleButton {
        float: right;
        background-image: url('images/list-add.svg');
    }

    #deleteRuleButton {
        float: right;
        background-image: url('images/edit-clear.svg');
    }

</style>

<div id="RssDownloader" class="RssDownloader">
    <div id="rssDownloaderDisabled" class="invisible">
        QBT_TR(Auto downloading of RSS torrents is disabled now! You can enable it in application settings.)QBT_TR[CONTEXT=AutomatedRssDownloader]
    </div>
    <div id="leftRssDownloaderColumn">
        <div id="topMenuBar">
            <b id="rulesTableDesc">QBT_TR(Download Rules)QBT_TR[CONTEXT=AutomatedRssDownloader]</b>
            <button type="button" id="newRuleButton" aria-label="QBT_TR(Add rule)QBT_TR[CONTEXT=AutomatedRssDownloader]" class="imageButton" onclick="qBittorrent.RssDownloader.addRule()"></button>
            <button type="button" id="deleteRuleButton" aria-label="QBT_TR(Remove rule)QBT_TR[CONTEXT=AutomatedRssDownloader]" class="imageButton" onclick="qBittorrent.RssDownloader.removeSelectedRule()"></button>
        </div>
        <div id="rulesTable">
            <div id="rulesSelectionCheckBoxList">
                <div id="rssDownloaderRuleFixedHeaderDiv" class="dynamicTableFixedHeaderDiv invisible">
                    <table class="dynamicTable unselectable">
                        <thead>
                            <tr class="dynamicTableHeader"></tr>
                        </thead>
                    </table>
                </div>
                <div id="rssDownloaderRuleTableDiv" class="dynamicTableDiv">
                    <table class="dynamicTable unselectable">
                        <tbody></tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
    <div id="centerRssDownloaderColumn">
        <fieldset class="settings" id="ruleSettings">
            <legend>QBT_TR(Rule Definition)QBT_TR[CONTEXT=AutomatedRssDownloader]</legend>
            <div class="formRow">
                <input disabled type="checkbox" id="useRegEx" onclick="qBittorrent.RssDownloader.setElementTitles()">
                <label for="useRegEx">QBT_TR(Use Regular Expressions)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
            </div>
            <table class="fullWidth">
                <tbody>
                    <tr>
                        <td>
                            <label for="mustContainText" class="noWrap">QBT_TR(Must Contain:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
                        </td>
                        <td class="fullWidth">
                            <input disabled type="text" id="mustContainText" class="fullWidth" autocorrect="off" autocapitalize="none">
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <label for="mustNotContainText" class="noWrap">QBT_TR(Must Not Contain:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
                        </td>
                        <td class="fullWidth">
                            <input disabled type="text" id="mustNotContainText" class="fullWidth" autocorrect="off" autocapitalize="none">
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <label for="episodeFilterText" class="noWrap">QBT_TR(Episode Filter:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
                        </td>
                        <td class="fullWidth">
                            <input disabled type="text" id="episodeFilterText" class="fullWidth" autocorrect="off" autocapitalize="none">
                        </td>
                    </tr>
                </tbody>
            </table>
            <div class="formRow" title="QBT_TR(Smart Episode Filter will check the episode number to prevent downloading of duplicates.
Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also support - as a separator))QBT_TR[CONTEXT=AutomatedRssDownloader]">
                <input disabled type="checkbox" id="useSmartFilter">
                <label for="useSmartFilter">QBT_TR(Use Smart Episode Filter)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
            </div>

            <hr>

            <table class="fullWidth">
                <tbody>
                    <tr>
                        <td>
                            <label class="noWrap" for="assignCategoryCombobox">QBT_TR(Assign Category:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
                        </td>
                        <td class="fullWidth">
                            <select disabled id="assignCategoryCombobox" class="fullWidth">
                                <option value=""></option>
                            </select>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <label class="noWrap" for="ruleAddTags">QBT_TR(Add Tags:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
                        </td>
                        <td class="fullWidth">
                            <input type="text" id="ruleAddTags" class="fullWidth" autocapitalize="none" autocorrect="off">
                        </td>
                    </tr>
                </tbody>
            </table>

            <div class="formRow">
                <input disabled type="checkbox" id="savetoDifferentDir">
                <label for="savetoDifferentDir">QBT_TR(Save to a Different Directory)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
            </div>
            <table class="fullWidth">
                <tbody>
                    <tr>
                        <td>
                            <label class="noWrap" for="saveToText">QBT_TR(Save to:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
                        </td>
                        <td class="fullWidth">
                            <input disabled type="text" class="fullWidth" id="saveToText" autocorrect="off" autocapitalize="none">
                        </td>
                    </tr>
                </tbody>
            </table>
            <table>
                <tbody>
                    <tr>
                        <td><label for="ignoreDaysValue">QBT_TR(Ignore Subsequent Matches for (0 to Disable))QBT_TR[CONTEXT=AutomatedRssDownloader]</label></td>
                        <td><input type="number" id="ignoreDaysValue" min="0">QBT_TR( days)QBT_TR[CONTEXT=AutomatedRssDownloader]</td>
                    </tr>
                </tbody>
            </table>
            <div id="lastMatchDiv">
                <span id="lastMatchText">QBT_TR(Last Match: Unknown)QBT_TR[CONTEXT=AutomatedRssDownloader]</span>
            </div>
            <table class="fullWidth">
                <tbody>
                    <tr>
                        <td>
                            <label class="noWrap" for="addStoppedCombobox">QBT_TR(Add Stopped:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
                        </td>
                        <td class="fullWidth">
                            <select disabled id="addStoppedCombobox" class="fullWidth">
                                <option value="default">QBT_TR(Use global settings)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
                                <option value="always">QBT_TR(Always)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
                                <option value="never">QBT_TR(Never)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
                            </select>
                        </td>
                    </tr>
                </tbody>
            </table>
            <table class="fullWidth">
                <tbody>
                    <tr>
                        <td>
                            <label class="noWrap" for="contentLayoutCombobox">QBT_TR(Torrent content layout:)QBT_TR[CONTEXT=AutomatedRssDownloader]</label>
                        </td>
                        <td class="fullWidth">
                            <select disabled id="contentLayoutCombobox" class="fullWidth">
                                <option value="Default">QBT_TR(Use global settings)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
                                <option value="Original">QBT_TR(Original)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
                                <option value="Subfolder">QBT_TR(Create subfolder)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
                                <option value="NoSubfolder">QBT_TR(Don't create subfolder)QBT_TR[CONTEXT=AutomatedRssDownloader]</option>
                            </select>
                        </td>
                    </tr>
                </tbody>
            </table>
        </fieldset>
        <fieldset class="settings" id="rssDownloaderFeeds">
            <legend>QBT_TR(Apply Rule to Feeds:)QBT_TR[CONTEXT=AutomatedRssDownloader]</legend>
            <div id="rssDownloaderFeedsTable">
                <div id="rssDownloaderFeedSelectionFixedHeaderDiv" class="dynamicTableFixedHeaderDiv invisible">
                    <table class="dynamicTable unselectable">
                        <thead>
                            <tr class="dynamicTableHeader"></tr>
                        </thead>
                    </table>
                </div>
                <div id="rssDownloaderFeedSelectionTableDiv" class="dynamicTableDiv">
                    <table class="dynamicTable unselectable">
                        <tbody></tbody>
                    </table>
                </div>
            </div>
        </fieldset>
        <button disabled type="button" id="saveButton" onclick="qBittorrent.RssDownloader.saveSettings()">
            QBT_TR(Save)QBT_TR[CONTEXT=HttpServer]
        </button>
    </div>
    <div id="rightRssDownloaderColumn">
        <b id="articleTableDesc">QBT_TR(Matching RSS Articles)QBT_TR[CONTEXT=AutomatedRssDownloader]</b>
        <div id="rssDownloaderArticlesTable">
            <div id="rssDownloaderArticlesFixedHeaderDiv" class="dynamicTableFixedHeaderDiv invisible">
                <table class="dynamicTable unselectable">
                    <thead>
                        <tr class="dynamicTableHeader"></tr>
                    </thead>
                </table>
            </div>
            <div id="rssDownloaderArticlesTableDiv" class="dynamicTableDiv">
                <table class="dynamicTable unselectable">
                    <tbody></tbody>
                </table>
            </div>
        </div>
    </div>
</div>

<ul id="rssDownloaderRuleMenu" class="contextMenu">
    <li><a href="#addRule"><img src="images/list-add.svg" alt="QBT_TR(Add new rule...)QBT_TR[CONTEXT=AutomatedRssDownloader]"> QBT_TR(Add new rule...)QBT_TR[CONTEXT=AutomatedRssDownloader]</a></li>
    <li><a href="#deleteRule"><img src="images/edit-clear.svg" alt="QBT_TR(Delete rule)QBT_TR[CONTEXT=AutomatedRssDownloader]"> QBT_TR(Delete rule)QBT_TR[CONTEXT=AutomatedRssDownloader]</a></li>
    <li class="separator"><a href="#renameRule"><img src="images/edit-rename.svg" alt="QBT_TR(Rename rule...)QBT_TR[CONTEXT=AutomatedRssDownloader]"> QBT_TR(Rename rule...)QBT_TR[CONTEXT=AutomatedRssDownloader]</a></li>
    <li class="separator"><a href="#clearDownloadedEpisodes"><img src="images/edit-clear.svg" alt="QBT_TR(Clear downloaded episodes...)QBT_TR[CONTEXT=AutomatedRssDownloader]"> QBT_TR(Clear downloaded episodes...)QBT_TR[CONTEXT=AutomatedRssDownloader]</a></li>
</ul>

<script>
    "use strict";

    window.qBittorrent ??= {};
    window.qBittorrent.RssDownloader ??= (() => {
        const exports = () => {
            return {
                setup: setup,
                updateRulesList: updateRulesList,
                showRule: showRule,
                renameRule: renameRule,
                modifyRuleState: modifyRuleState,
                saveSettings: saveSettings,
                rssDownloaderRulesTable: rssDownloaderRulesTable,
                rssDownloaderFeedSelectionTable: rssDownloaderFeedSelectionTable,
                setElementTitles: setElementTitles,
                addRule: addRule,
                removeSelectedRule: removeSelectedRule
            };
        };

        const rssDownloaderRulesTable = new window.qBittorrent.DynamicTable.RssDownloaderRulesTable();
        const rssDownloaderFeedSelectionTable = new window.qBittorrent.DynamicTable.RssDownloaderFeedSelectionTable();
        const rssDownloaderArticlesTable = new window.qBittorrent.DynamicTable.RssDownloaderArticlesTable();

        let rulesList = {};
        let feedList = [];

        const setup = () => {
            const pref = window.parent.qBittorrent.Cache.preferences.get();

            if (!pref.rss_auto_downloading_enabled)
                document.getElementById("rssDownloaderDisabled").classList.remove("invisible");

            // recalculate height
            const warningHeight = document.getElementById("rssDownloaderDisabled").getBoundingClientRect().height;

            document.getElementById("leftRssDownloaderColumn").style.height = `calc(100% - ${warningHeight}px)`;
            document.getElementById("centerRssDownloaderColumn").style.height = `calc(100% - ${warningHeight}px)`;
            document.getElementById("rightRssDownloaderColumn").style.height = `calc(100% - ${warningHeight}px)`;

            document.getElementById("rulesTable").style.height = `calc(100% - ${document.getElementById("rulesTableDesc").getBoundingClientRect().height}px)`;
            document.getElementById("rssDownloaderArticlesTable").style.height = `calc(100% - ${document.getElementById("articleTableDesc").getBoundingClientRect().height}px)`;

            const centerRowNotTableHeight = document.getElementById("saveButton").getBoundingClientRect().height
                + document.getElementById("ruleSettings").getBoundingClientRect().height + 15;

            document.getElementById("rssDownloaderFeeds").style.height = `calc(100% - ${centerRowNotTableHeight}px)`;

            // firefox calculates the height of the table inside fieldset differently and thus doesn't need the offset
            if (navigator.userAgent.toLowerCase().includes("firefox")) {
                document.getElementById("rssDownloaderFeedsTable").style.height = "100%";
            }
            else {
                const outsideTableHeight = (document.getElementById("rssDownloaderFeedsTable").getBoundingClientRect().top - document.getElementById("rssDownloaderFeeds").getBoundingClientRect().top) - 10;
                document.getElementById("rssDownloaderFeedsTable").style.height = `calc(100% - ${outsideTableHeight}px)`;
            }

            const rssDownloaderRuleContextMenu = new window.qBittorrent.ContextMenu.RssDownloaderRuleContextMenu({
                targets: "",
                menu: "rssDownloaderRuleMenu",
                actions: {
                    addRule: addRule,
                    deleteRule: removeSelectedRule,
                    renameRule: (el) => {
                        renameRule(rssDownloaderRulesTable.getRow(rssDownloaderRulesTable.selectedRows[0]).full_data.name);
                    },
                    clearDownloadedEpisodes: (el) => {
                        clearDownloadedEpisodes(rssDownloaderRulesTable.selectedRows
                            .map((sRow) => rssDownloaderRulesTable.getRow(sRow).full_data.name));
                    }
                },
                offsets: {
                    x: 0,
                    y: -3
                }
            });
            rssDownloaderRulesTable.setup("rssDownloaderRuleTableDiv", "rssDownloaderRuleFixedHeaderDiv", rssDownloaderRuleContextMenu);
            rssDownloaderFeedSelectionTable.setup("rssDownloaderFeedSelectionTableDiv", "rssDownloaderFeedSelectionFixedHeaderDiv");
            rssDownloaderArticlesTable.setup("rssDownloaderArticlesTableDiv", "rssDownloaderArticlesFixedHeaderDiv");

            rssDownloaderRuleContextMenu.addTarget(document.getElementById("rulesTable"));
            // deselect feed when clicking on empty part of table
            document.getElementById("rulesTable").addEventListener("click", (e) => {
                if (e.target.nodeName === "DIV") {
                    rssDownloaderRulesTable.deselectAll();
                    rssDownloaderRulesTable.deselectRow();
                    showRule("");
                }
            });
            // get all categories and add to combobox
            fetch("api/v2/torrents/categories", {
                    method: "GET",
                    cache: "no-store"
                })
                .then(async (response) => {
                    if (!response.ok)
                        return;

                    const responseJSON = await response.json();

                    const combobox = document.getElementById("assignCategoryCombobox");
                    for (const cat in responseJSON) {
                        if (!Object.hasOwn(responseJSON, cat))
                            continue;

                        const option = document.createElement("option");
                        option.text = option.value = cat;
                        combobox.add(option);
                    }
                });
            // get all rss feed
            const url = new URL("api/v2/rss/items", window.location);
            url.search = new URLSearchParams({
                withData: false
            });
            fetch(url, {
                    method: "GET",
                    cache: "no-store"
                })
                .then(async (response) => {
                    if (!response.ok)
                        return;

                    const responseJSON = await response.json();

                    feedList = [];
                    const flatten = (root) => {
                        for (const child in root) {
                            if (root[child].uid !== undefined)
                                feedList.push({ name: child, url: root[child].url });
                            else
                                flatten(root[child]);
                        }
                    };
                    flatten(responseJSON);
                });
            document.getElementById("savetoDifferentDir").addEventListener("click", (event) => {
                document.getElementById("saveToText").disabled = !event.target.checked;
            });
            updateRulesList();
        };

        const updateRulesList = () => {
            // get all rules
            fetch("api/v2/rss/rules", {
                    method: "GET",
                    cache: "no-store"
                })
                .then(async (response) => {
                    if (!response.ok)
                        return;

                    const responseJSON = await response.json();

                    rssDownloaderRulesTable.clear();
                    let rowID = -1;
                    for (const rule in responseJSON) {
                        if (!Object.hasOwn(responseJSON, rule))
                            continue;
                        rssDownloaderRulesTable.updateRowData({
                            rowId: ++rowID,
                            checked: responseJSON[rule].enabled,
                            name: rule
                        });
                    }
                    rssDownloaderRulesTable.updateTable(false);
                    rulesList = responseJSON;
                });
        };

        const modifyRuleState = (rule, setting, newState, callback = () => {}) => {
            rulesList[rule][setting] = newState;
            fetch("api/v2/rss/setRule", {
                    method: "POST",
                    body: new URLSearchParams({
                        ruleName: rule,
                        ruleDef: JSON.stringify(rulesList[rule])
                    })
                })
                .then((response) => {
                    if (!response.ok)
                        return;

                    callback();
                });
        };

        const addRule = () => {
            new MochaUI.Window({
                id: "newRulePage",
                icon: "images/qbittorrent-tray.svg",
                title: "QBT_TR(New rule name)QBT_TR[CONTEXT=AutomatedRssDownloader]",
                loadMethod: "iframe",
                contentURL: "newrule.html?v=${CACHEID}",
                scrollbars: false,
                resizable: false,
                maximizable: false,
                width: window.qBittorrent.Dialog.limitWidthToViewport(350),
                height: 100
            });
        };

        const renameRule = (rule) => {
            new MochaUI.Window({
                id: "renameRulePage",
                icon: "images/qbittorrent-tray.svg",
                title: "QBT_TR(Rule renaming)QBT_TR[CONTEXT=AutomatedRssDownloader]",
                loadMethod: "iframe",
                contentURL: `rename_rule.html?v=${CACHEID}&rule=${encodeURIComponent(rule)}`,
                scrollbars: false,
                resizable: false,
                maximizable: false,
                width: window.qBittorrent.Dialog.limitWidthToViewport(350),
                height: 100
            });
        };

        const removeSelectedRule = () => {
            if (rssDownloaderRulesTable.selectedRows.length === 0)
                return;
            removeRules(rssDownloaderRulesTable.selectedRows.map((sRow) =>
                rssDownloaderRulesTable.getRow(sRow).full_data.name));
        };

        const removeRules = (rules) => {
            const encodedRules = rules.map((rule) => encodeURIComponent(rule));
            new MochaUI.Window({
                id: "removeRulePage",
                icon: "images/qbittorrent-tray.svg",
                title: "QBT_TR(Rule deletion confirmation)QBT_TR[CONTEXT=AutomatedRssDownloader]",
                loadMethod: "iframe",
                contentURL: `confirmruledeletion.html?v=${CACHEID}&rules=${encodeURIComponent(encodedRules.join("|"))}`,
                scrollbars: false,
                resizable: false,
                maximizable: false,
                width: window.qBittorrent.Dialog.limitWidthToViewport(360),
                height: 90
            });
        };

        const clearDownloadedEpisodes = (rules) => {
            const encodedRules = rules.map((rule) => encodeURIComponent(rule));
            new MochaUI.Window({
                id: "clearRulesPage",
                icon: "images/qbittorrent-tray.svg",
                title: "QBT_TR(Clear downloaded episodes confirmation)QBT_TR[CONTEXT=AutomatedRssDownloader]",
                loadMethod: "iframe",
                contentURL: `confirmruleclear.html?v=${CACHEID}&rules=${encodeURIComponent(encodedRules.join("|"))}`,
                scrollbars: false,
                resizable: false,
                maximizable: false,
                width: window.qBittorrent.Dialog.limitWidthToViewport(350),
                height: 85
            });
        };

        const saveSettings = () => {
            const lastSelectedRow = rssDownloaderRulesTable.selectedRows.at(-1);
            const rule = rssDownloaderRulesTable.getRow(lastSelectedRow).full_data.name;

            rulesList[rule].useRegex = document.getElementById("useRegEx").checked;
            rulesList[rule].mustContain = document.getElementById("mustContainText").value;
            rulesList[rule].mustNotContain = document.getElementById("mustNotContainText").value;
            rulesList[rule].episodeFilter = document.getElementById("episodeFilterText").value;
            rulesList[rule].smartFilter = document.getElementById("useSmartFilter").checked;
            rulesList[rule].ignoreDays = Number(document.getElementById("ignoreDaysValue").value);
            rulesList[rule].affectedFeeds = [...rssDownloaderFeedSelectionTable.getRowValues()]
                .filter((row) => row.full_data.checked)
                .map((row) => row.full_data.url);

            rulesList[rule].torrentParams.category = document.getElementById("assignCategoryCombobox").value;
            rulesList[rule].torrentParams.tags = document.getElementById("ruleAddTags").value.split(",");
            if (document.getElementById("savetoDifferentDir").checked) {
                rulesList[rule].torrentParams.save_path = document.getElementById("saveToText").value;
                rulesList[rule].torrentParams.use_auto_tmm = false;
            }
            else {
                rulesList[rule].torrentParams.save_path = "";
            }

            switch (document.getElementById("addStoppedCombobox").value) {
                case "default":
                    rulesList[rule].torrentParams.stopped = null;
                    break;
                case "always":
                    rulesList[rule].torrentParams.stopped = true;
                    break;
                case "never":
                    rulesList[rule].torrentParams.stopped = false;
                    break;
            }

            switch (document.getElementById("contentLayoutCombobox").value) {
                case "Default":
                    rulesList[rule].torrentParams.content_layout = null;
                    break;
                case "Original":
                    rulesList[rule].torrentParams.content_layout = "Original";
                    break;
                case "Subfolder":
                    rulesList[rule].torrentParams.content_layout = "Subfolder";
                    break;
                case "NoSubfolder":
                    rulesList[rule].torrentParams.content_layout = "NoSubfolder";
                    break;
            }

            fetch("api/v2/rss/setRule", {
                    method: "POST",
                    body: new URLSearchParams({
                        ruleName: rule,
                        ruleDef: JSON.stringify(rulesList[rule])
                    })
                })
                .then((response) => {
                    if (!response.ok)
                        return;

                    updateMatchingArticles(rule);
                });
        };

        const updateMatchingArticles = (ruleName) => {
            const url = new URL("api/v2/rss/matchingArticles", window.location);
            url.search = new URLSearchParams({
                ruleName: ruleName
            });
            fetch(url, {
                    method: "GET",
                    cache: "no-store"
                })
                .then(async (response) => {
                    if (!response.ok)
                        return;

                    const responseJSON = await response.json();

                    rssDownloaderArticlesTable.clear();
                    let rowID = -1;
                    for (const feed in responseJSON) {
                        if (!Object.hasOwn(responseJSON, feed))
                            continue;
                        rssDownloaderArticlesTable.updateRowData({
                            rowId: ++rowID,
                            name: feed,
                            isFeed: true
                        });
                        responseJSON[feed].each((article) => {
                            rssDownloaderArticlesTable.updateRowData({
                                rowId: ++rowID,
                                name: article,
                                isFeed: false
                            });
                        });
                    }
                    rssDownloaderArticlesTable.updateTable(false);
                });
        };

        const showRule = (ruleName) => {
            if (ruleName === "") {
                // disable all
                document.getElementById("saveButton").disabled = true;
                document.getElementById("useRegEx").disabled = true;
                document.getElementById("mustContainText").disabled = true;
                document.getElementById("mustNotContainText").disabled = true;
                document.getElementById("episodeFilterText").disabled = true;
                document.getElementById("useSmartFilter").disabled = true;
                document.getElementById("assignCategoryCombobox").disabled = true;
                document.getElementById("ruleAddTags").disabled = true;
                document.getElementById("savetoDifferentDir").disabled = true;
                document.getElementById("saveToText").disabled = true;
                document.getElementById("ignoreDaysValue").disabled = true;
                document.getElementById("addStoppedCombobox").disabled = true;
                document.getElementById("contentLayoutCombobox").disabled = true;

                // reset all boxes
                document.getElementById("useRegEx").checked = false;
                document.getElementById("mustContainText").value = "";
                document.getElementById("mustNotContainText").value = "";
                document.getElementById("episodeFilterText").value = "";
                document.getElementById("useSmartFilter").checked = false;
                document.getElementById("assignCategoryCombobox").value = "default";
                document.getElementById("ruleAddTags").value = "";
                document.getElementById("savetoDifferentDir").checked = false;
                document.getElementById("saveToText").value = "";
                document.getElementById("ignoreDaysValue").value = 0;
                document.getElementById("lastMatchText").textContent = "QBT_TR(Last Match: Unknown)QBT_TR[CONTEXT=AutomatedRssDownloader]";
                document.getElementById("addStoppedCombobox").value = "default";
                document.getElementById("contentLayoutCombobox").value = "Default";
                rssDownloaderFeedSelectionTable.clear();
                rssDownloaderArticlesTable.clear();

                document.getElementById("mustContainText").title = "";
                document.getElementById("mustNotContainText").title = "";
                document.getElementById("episodeFilterText").title = "";
            }
            else {
                // enable all
                document.getElementById("saveButton").disabled = false;
                document.getElementById("useRegEx").disabled = false;
                document.getElementById("mustContainText").disabled = false;
                document.getElementById("mustNotContainText").disabled = false;
                document.getElementById("episodeFilterText").disabled = false;
                document.getElementById("useSmartFilter").disabled = false;
                document.getElementById("assignCategoryCombobox").disabled = false;
                document.getElementById("ruleAddTags").disabled = false;
                document.getElementById("savetoDifferentDir").disabled = false;
                document.getElementById("ignoreDaysValue").disabled = false;
                document.getElementById("addStoppedCombobox").disabled = false;
                document.getElementById("contentLayoutCombobox").disabled = false;

                // load rule settings
                document.getElementById("useRegEx").checked = rulesList[ruleName].useRegex;
                document.getElementById("mustContainText").value = rulesList[ruleName].mustContain;
                document.getElementById("mustNotContainText").value = rulesList[ruleName].mustNotContain;
                document.getElementById("episodeFilterText").value = rulesList[ruleName].episodeFilter;
                document.getElementById("useSmartFilter").checked = rulesList[ruleName].smartFilter;

                document.getElementById("assignCategoryCombobox").value = rulesList[ruleName].torrentParams.category ? rulesList[ruleName].torrentParams.category : "default";
                document.getElementById("ruleAddTags").value = rulesList[ruleName].torrentParams.tags.join(",");
                document.getElementById("savetoDifferentDir").checked = rulesList[ruleName].torrentParams.save_path !== "";
                document.getElementById("saveToText").disabled = !document.getElementById("savetoDifferentDir").checked;
                document.getElementById("saveToText").value = rulesList[ruleName].torrentParams.save_path;
                document.getElementById("ignoreDaysValue").value = rulesList[ruleName].ignoreDays;

                // calculate days since last match
                if (rulesList[ruleName].lastMatch !== "") {
                    const timeDiffInMs = Date.now() - new Date(rulesList[ruleName].lastMatch).getTime();
                    const daysAgo = Math.floor(timeDiffInMs / (1000 * 60 * 60 * 24)).toString();
                    document.getElementById("lastMatchText").textContent = " QBT_TR(Last Match: %1 days ago)QBT_TR[CONTEXT=AutomatedRssDownloader]".replace("%1", daysAgo);
                }
                else {
                    document.getElementById("lastMatchText").textContent = "QBT_TR(Last Match: Unknown)QBT_TR[CONTEXT=AutomatedRssDownloader]";
                }

                if ((rulesList[ruleName].torrentParams.stopped === undefined) || (rulesList[ruleName].torrentParams.stopped === null))
                    document.getElementById("addStoppedCombobox").value = "default";
                else
                    document.getElementById("addStoppedCombobox").value = rulesList[ruleName].torrentParams.stopped ? "always" : "never";

                if ((rulesList[ruleName].torrentParams.content_layout === undefined) || (rulesList[ruleName].torrentParams.content_layout === null))
                    document.getElementById("contentLayoutCombobox").value = "Default";
                else
                    document.getElementById("contentLayoutCombobox").value = rulesList[ruleName].torrentParams.content_layout;

                setElementTitles();

                rssDownloaderFeedSelectionTable.clear();
                let rowID = -1;
                for (const feed of feedList) {
                    rssDownloaderFeedSelectionTable.updateRowData({
                        rowId: ++rowID,
                        checked: rulesList[ruleName].affectedFeeds.contains(feed.url),
                        name: feed.name,
                        url: feed.url
                    });
                }
                rssDownloaderFeedSelectionTable.updateTable(false);
                updateMatchingArticles(ruleName);
            }
        };

        const setElementTitles = () => {
            let mainPart;
            if (document.getElementById("useRegEx").checked) {
                mainPart = "QBT_TR(Regex mode: use Perl-compatible regular expressions)QBT_TR[CONTEXT=AutomatedRssDownloader]\n\n";
            }
            else {
                mainPart = "QBT_TR(Wildcard mode: you can use)QBT_TR[CONTEXT=AutomatedRssDownloader]\n\n"
                    + " ● QBT_TR(? to match any single character)QBT_TR[CONTEXT=AutomatedRssDownloader]\n"
                    + " ● QBT_TR(* to match zero or more of any characters)QBT_TR[CONTEXT=AutomatedRssDownloader]\n"
                    + " ● QBT_TR(Whitespaces count as AND operators (all words, any order))QBT_TR[CONTEXT=AutomatedRssDownloader]\n"
                    + " ● QBT_TR(| is used as OR operator)QBT_TR[CONTEXT=AutomatedRssDownloader]\n\n"
                    + "QBT_TR(If word order is important use * instead of whitespace.)QBT_TR[CONTEXT=AutomatedRssDownloader]\n\n";
            }
            const secondPart = "QBT_TR(An expression with an empty %1 clause (e.g. %2))QBT_TR[CONTEXT=AutomatedRssDownloader]"
                .replace("%1", "|").replace("%2", "expr|");

            document.getElementById("mustContainText").title = `${mainPart}${secondPart}QBT_TR( will match all articles.)QBT_TR[CONTEXT=AutomatedRssDownloader]`;
            document.getElementById("mustNotContainText").title = `${mainPart}${secondPart}QBT_TR( will exclude all articles.)QBT_TR[CONTEXT=AutomatedRssDownloader]`;

            let episodeFilterTitle = "QBT_TR(Matches articles based on episode filter.)QBT_TR[CONTEXT=AutomatedRssDownloader]\n\n"
                + "QBT_TR(Example: )QBT_TR[CONTEXT=AutomatedRssDownloader]"
                + "1x2;8-15;5;30-;"
                + "QBT_TR( will match 2, 5, 8 through 15, 30 and onward episodes of season one)QBT_TR[CONTEXT=AutomatedRssDownloader]\n\n"
                + "QBT_TR(Episode filter rules: )QBT_TR[CONTEXT=AutomatedRssDownloader]\n\n"
                + " ● QBT_TR(Season number is a mandatory non-zero value)QBT_TR[CONTEXT=AutomatedRssDownloader]\n"
                + " ● QBT_TR(Episode number is a mandatory positive value)QBT_TR[CONTEXT=AutomatedRssDownloader]\n"
                + " ● QBT_TR(Filter must end with semicolon)QBT_TR[CONTEXT=AutomatedRssDownloader]\n"
                + " ● QBT_TR(Three range types for episodes are supported: )QBT_TR[CONTEXT=AutomatedRssDownloader]\n"
                + "    ● QBT_TR(Single number: <b>1x25;</b> matches episode 25 of season one)QBT_TR[CONTEXT=AutomatedRssDownloader]\n"
                + "    ● QBT_TR(Normal range: <b>1x25-40;</b> matches episodes 25 through 40 of season one)QBT_TR[CONTEXT=AutomatedRssDownloader]\n"
                + "    ● QBT_TR(Infinite range: <b>1x25-;</b> matches episodes 25 and upward of season one, and all episodes of later seasons)QBT_TR[CONTEXT=AutomatedRssDownloader]";

            episodeFilterTitle = episodeFilterTitle.replace(/<b>/g, "").replace(/<\/b>/g, "");
            document.getElementById("episodeFilterText").title = episodeFilterTitle;
        };

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

    window.qBittorrent.RssDownloader.setup();
</script>
