var subTable = null;
    discordChannels = [];

// Create subscription table
async function initSubscriptionTable() {
    await initTable("#subscriptionsTabPane", "subTable", loadSubscriptions);

    subTable = $("#subTable").DataTable({
        info: false,
        paging: false,
        searching: false,
        autoWidth: false,
        order: [],
        select: {
            style: "multi+shift",
            selector: 'th:first-child input[type="checkbox"]'
        },
        columnDefs: [
            { orderable: false, targets: "no-sort" },
            { 
                targets: 0,
                checkboxes: { selectRow: true }
            },
        ],
        columns: [
            {
                // Select row checkbox column
                title: '<input type="checkbox" class="form-check-input table-select-all" />',
                data: null,
                orderable: false,
                className: "text-center col-switch-width",
                render: function() {
                    return '<input type="checkbox" class="form-check-input table-select-row" />'
                }
            },
            { title: "ID", data: "id", visible: false },
            {
                title: "Name",
                data: "name",
                className: "text-truncate",
                render: function(data, type, row) {
                    return `<a href="#" onclick="showEditSubModal(${row.id})" class="text-decoration-none">${data}</a>`;
                }
            },
            {
                title: "URL",
                data: "url",
                className: "text-truncate",
                render: function(data, type) {
                    return `<a href="${data}" class="text-decoration-none" target="_blank">${data}</a>`;
                }
            },
            { 
                title: "Channels",
                data: "channels_count",
                render: function(data) {
                    return `<span class="badge text-bg-secondary">${data}</span>`;
                }
            },
            // {
            //     title: "Content",
            //     data: "channels_count",
            //     render: function(data, type) {

            //     }
            // },
            {
                title: "Created",
                data: "creation_datetime",
                render: function(data, type) {
                    // return new Date(data).toISOString().split("T")[0];
                    let dateTime = new Date(data);
                    let dateTimeString = formatDate(dateTime);
                    return $(`
                        <span data-bs-trigger="hover focus"
                            data-bs-html="true"
                            data-bs-custom-class="text-center"
                            data-bs-toggle="popover"
                            data-bs-content="${dateTimeString}">
                            ${dateTime.toISOString().split("T")[0]}
                        </span>
                    `).popover()[0];
                }
            },
            {
                title: "Notes",
                data: "extra_notes",
                orderable: false,
                className: "text-center",
                render: function(data, type) {
                    if (!data) return "";
                    return $(`
                        <i class="bi bi-chat-left-text"
                            data-bs-trigger="hover focus"
                            data-bs-toggle="popover"
                            data-bs-title="Extra Notes"
                            data-bs-content="${data}">
                        </i>
                    `).popover()[0];
                }
            },
            { 
                title: "Active",
                data: "active",
                orderable: false,
                className: "text-center form-switch",
                render: function(data, type) {
                    return `<input type="checkbox" class="sub-toggle-active form-check-input ms-0" ${data ? "checked" : ""} />`
                }
            },
            {
                orderable: false,
                className: "p-0",
                render: function(data, type, row) {
                    return `<div class="h-100" style="background-color: #${row.embed_colour}; width: .25rem;">&nbsp;</div>`
                }
            }
        ]
    });

    bindTableCheckboxes("#subTable", subTable, "#deleteSelectedSubscriptionsBtn");
}

$("#subTable").on("change", ".sub-toggle-active", async function () {

    /*
        Lock all toggles to soft-prevent spam.
        There is a rate limit, but allowing the user to
        reach it from this toggle would be bad.
    */
    $(".sub-toggle-active").prop("disabled", true);

    try {
        const active = $(this).prop("checked");
        const sub = subTable.row($(this).closest("tr")).data();
        
        // Update the table row
        sub.active = active;
        subTable.data(sub).draw();

        // Update the database
        const subPrimaryKey = await saveSubscription(
            sub.id,
            sub.name,
            sub.url,
            sub.guild_id,
            sub.extra_notes,
            sub.filters,
            {
                title: sub.article_title_mutators.map(mutator => mutator.id),
                desc: sub.article_desc_mutators.map(mutator => mutator.id)
            },
            sub.embed_colour,
            sub.article_fetch_image,
            sub.published_threshold,
            active,
            handleErrorMsg=false
        );
        if (!subPrimaryKey)
            throw Error("This subscription no longer exists.");

        showToast(
            active ? "success" : "danger",
            "Subscription " + (active ? "Activated" : "Deactivated"),
            "Subscription ID: " + subPrimaryKey
        );
    }
    catch (error) {
        console.error(error);   
        showToast(
            "danger",
            "Error Updating Subscription",
            `Tried to toggle activeness, but encountered a problem. <br><code>${error}</code>`
        );
    }
    finally {
        // Re-enable toggles after 500ms
        setTimeout(() => { $(".sub-toggle-active").prop("disabled", false); }, 500)
    }    
});

// Open new subscription modal
$("#addSubscriptionBtn").on("click", async function() {
    await showEditSubModal(-1);
});

async function showEditSubModal(subId) {

    if (subId === -1) {
        $("#subFormModal .form-create, #subAdvancedModal .form-create").show();
        $("#subFormModal .form-edit, #subAdvancedModal .form-edit").hide();

        $("#subFormModal input, #subFormModal textarea").val("");
        $("#subChannels").val("").change();
        $("#subFilters").val("").change();
        $("#subTitleMutators").val("").change();
        $("#subDescMutators").val("").change();
        $("#subActive").prop("checked", true);

        $("#subResetEmbedColour").click();
        $("#subArticleFetchImage").prop("checked", true);

        $("#subPubThreshold").val(getCurrentDateTime());
    }
    else {
        $("#subFormModal .form-create, #subAdvancedModal .form-create").hide();
        $("#subFormModal .form-edit, #subAdvancedModal .form-edit").show();

        const subscription = subTable.row(function(idx, data, node) {
            return data.id === subId;
        }).data();

        $("#subName").val(subscription.name);
        $("#subUrl").val(subscription.url);
        $("#subExtraNotes").val(subscription.extra_notes);
        $("#subActive").prop("checked", subscription.active);

        $("#subTitleMutators").val("").change();
        $("#subTitleMutators").val(subscription.article_title_mutators.map(mutator => mutator.id)).change();

        $("#subDescMutators").val("").change();
        $("#subDescMutators").val(subscription.article_desc_mutators.map(mutator => mutator.id)).change();

        const channels = await getSubChannels(subscription.id);
        $("#subChannels").val("").change();
        $("#subChannels").val(channels.results.map(channel => channel.channel_id)).change();

        $("#subFilters").val("").change();
        $("#subFilters").val(subscription.filters).change();

        subSetHexColour(`#${subscription.embed_colour}`);
        $("#subArticleFetchImage").prop("checked", subscription.article_fetch_image);

        $("#subPubThreshold").val(subscription.published_threshold.split('+')[0]);
    }

    $("#subId").val(subId);
    $("#subFormModal").modal("show");
}

$("#subForm").on("submit", async function(event) {
    event.preventDefault();

    var id = $("#subId").val();
        name = $("#subName").val();
        url = $("#subUrl").val();
        guildId = getCurrentlyActiveServer().guild_id;
        extraNotes = $("#subExtraNotes").val();
        subChannels = $("#subChannels option:selected").toArray().map(channel => channel.value);
        subFilters = $("#subFilters option:selected").toArray().map(filter => parseInt(filter.value));
        subMutators = {
            title: $("#subTitleMutators option:selected").toArray().map(mutator => parseInt(mutator.value)),
            desc: $("#subDescMutators option:selected").toArray().map(mutator => parseInt(mutator.value))
        }
        subEmbedColour = $("#subEmbedColour").val().split("#")[1];
        articleFetchImage = $("#subArticleFetchImage").prop("checked")
        publishedThreshold = $("#subPubThreshold").val();
        active = $("#subActive").prop("checked");

    var subPrimaryKey = await saveSubscription(id, name, url, guildId, extraNotes, subFilters, subMutators, subEmbedColour, articleFetchImage, publishedThreshold, active);

    if (!subPrimaryKey) {
        alert("prevented /subscriptions/false/subchannels");
        return
    }

    await deleteSubChannels(subPrimaryKey);
    subChannels.forEach(async channelId => {
        await saveSubChannel(channelId, subPrimaryKey);
    });

    if (subPrimaryKey)
        showToast("success", "Subscription Saved", "Subscription ID: " + subPrimaryKey);
        await loadSubscriptions(guildId);

    $("#subFormModal").modal("hide");
});

async function saveSubscription(id, name, url, guildId, extraNotes, filters, mutators, embedColour, articleFetchImage, publishedTheshold, active, handleErrorMsg=true) {
    var formData = new FormData();
    formData.append("name", name);
    formData.append("url", url);
    formData.append("guild_id", guildId);
    formData.append("extra_notes", extraNotes);
    filters.forEach(filter => formData.append("filters", filter));
    mutators.title.forEach(mutator => formData.append("article_title_mutators", mutator));
    mutators.desc.forEach(mutator => formData.append("article_desc_mutators", mutator));
    formData.append("embed_colour", embedColour);
    formData.append("article_fetch_image", articleFetchImage);
    formData.append("published_threshold", publishedTheshold);
    formData.append("active", active);

    var response;

    try {
        if (id === "-1") response = await newSubscription(formData);
        else response = await editSubscription(id, formData); 
    }
    catch (err) {
        console.error(err)

        if (handleErrorMsg)
            showToast("danger", "Subscription Error", err.responseText, 18000);

        return false;
    }

    return response.id;
}

async function saveSubChannel(channelId, subscriptionId) {
    var formData = new FormData();
    formData.append("channel_id", channelId);
    formData.append("subscription", subscriptionId);

    var response

    try {
        response = await newSubChannel(formData);
    }
    catch (error) {
        console.log(error);
        showToast("danger", "Failed to save subchannel", error, 18000);
        return false
    }

    return response.id
}

function clearExistingSubRows() {
    $("#subTable thead .table-select-all").prop("checked", false).prop("indeterminate", false);
    subTable.clear().draw(false);
}

$("#refreshSubscriptionBtn").on("click", async function() {
    loadSubscriptions(getCurrentlyActiveServer().guild_id);
});

async function loadSubscriptions(guildId, page=1, pageSize=null) {

    if (!guildId) 
        return;

    if (!pageSize)
        pageSize = $("#subTablePageSize").val();

    $("#deleteSelectedSubscriptionsBtn").prop("disabled", true);
    clearExistingSubRows();

    try {
        const subscriptions = await getSubscriptions(guildId, page, pageSize);
        subTable.rows.add(subscriptions.results).draw(false);

        updateTablePagination("#subscriptionsTabPane .table-pagination", page, pageSize, subscriptions.count, subscriptions.next, subscriptions.previous);
        updateTablePaginationInfo("#subscriptionsTabPane .table-page-info", subscriptions.results.length, subscriptions.count);

        $("#subTable thead .table-select-all").prop("disabled", subscriptions.results.length === 0);
        console.debug("loading subs, " + subscriptions.results.length + " found");
    }
    catch (err) {
        console.error(err)
        console.error(JSON.stringify(err, null, 4));
        showToast("danger", `Error Loading Subscriptions: HTTP ${err.status}`, err.responseJSON.message, 15000);
    }
}

// #region Server Change Event Handler

$(document).on("selectedServerChange", async function() {

    // Hide alerts
    $("#serverJoinAlert").attr("style", "display: none !important");

    updateBotInviteLink();

    const activeServer = getCurrentlyActiveServer();
    await loadSubscriptions(activeServer.guild_id);
    await loadChannelOptions(activeServer.guild_id);
    await loadFilterOptions(activeServer.guild_id);
    await loadMutatorOptions();
})

// #endregion


// Dev button (to be removed)
// auto fills dummy data into form fields to quickly create test subs
$("#devGenerateSub").on("click", function() {
    const items = [
        ["BBC News · Top Stories", "http://feeds.bbci.co.uk/news/rss.xml"],
        ["BBC News · World", "http://feeds.bbci.co.uk/news/world/rss.xml"],
        ["BBC News · Entertainment", "http://feeds.bbci.co.uk/news/entertainment_and_arts/rss.xml"],
        ["BBC News · UK", "http://feeds.bbci.co.uk/news/uk/rss.xml"],
        ["BBC News · Business", "http://feeds.bbci.co.uk/news/business/rss.xml"],
        ["BBC News · Politics", "http://feeds.bbci.co.uk/news/politics/rss.xml"],
        ["BBC News · Health", "http://feeds.bbci.co.uk/news/health/rss.xml"],
        ["BBC News · Science", "http://feeds.bbci.co.uk/news/science_and_environment/rss.xml"],
        ["BBC News · Technology", "http://feeds.bbci.co.uk/news/technology/rss.xml"],
        ["BBC News · Education", "http://feeds.bbci.co.uk/news/education/rss.xml"],
        ["BBC News · England", "http://feeds.bbci.co.uk/news/england/rss.xml"],
        ["BBC News · Scotland", "http://feeds.bbci.co.uk/news/scotland/rss.xml"],
        ["BBC News · Wales", "http://feeds.bbci.co.uk/news/wales/rss.xml"],
        ["BBC News · Northern Ireland", "http://feeds.bbci.co.uk/news/northern_ireland/rss.xml"],
        ["BBC News · Europe", "http://feeds.bbci.co.uk/news/europe/rss.xml"],
        ["BBC News · Latin America", "http://feeds.bbci.co.uk/news/latin_america/rss.xml"],
        ["BBC News · US & Canada", "http://feeds.bbci.co.uk/news/us_and_canada/rss.xml"],
        ["BBC News · Middle East", "http://feeds.bbci.co.uk/news/middle_east/rss.xml"],
        ["BBC News · Asia", "http://feeds.bbci.co.uk/news/asia/rss.xml"],
        ["BBC News · Africa", "http://feeds.bbci.co.uk/news/africa/rss.xml"],
        ["BBC Sports", "https://news.bbc.co.uk/sport1/hi/help/rss/default.stm"],
        ["This Week in Self-Hosted", "https://selfh.st/rss/"],
        ["The Babylon Bee", "https://babylonbee.com/feed"],
        ["Reddit · r/memes", "https://reddit.com/r/memes/.rss"],
        ["Reddit · r/selfhosted", "https://reddit.com/r/selfhosted/.rss"],
        ["Sky News", "http://feeds.skynews.com/feeds/rss/home.xml"],
        ["Sky News · UK", "http://feeds.skynews.com/feeds/rss/uk.xml"],
        ["Sky News · Entertainment", "http://feeds.skynews.com/feeds/rss/entertainment.xml"],
        ["Sky News · Strange News", "http://feeds.skynews.com/feeds/rss/strange.xml"]
    ];

    const selected = items[Math.floor(Math.random()*items.length)]

    $("#subName").val(selected[0]);
    $("#subUrl").val(selected[1]);

    showToast("info", "Generated", "Picked random sub data");
});


// #region Delete Subscription Buttons

// Delete button on the 'edit subscription' modal
$("#deleteEditSub").on("click", async function() {
    const subId = $("#subId").val();
    await deleteSubscription(subId);
    await loadSubscriptions(getCurrentlyActiveServer().guild_id);
    $("#subFormModal").modal("hide");
    showToast("danger", "Deleted Subscription", "Subscription ID: " + subId);
});

$("#deleteSelectedSubscriptionsBtn").on("click", async function() {
    // showToast("danger", "Not Implemented", "This feature isn't implemented");

    var rows = subTable.rows(".selected").data();
    $.each(rows, async function() {
        // alert(JSON.stringify(this, null, 4));
        await deleteSubscription(this.id);
        showToast("danger", "Deleted Subscription", "Subscription ID: " + this.id);
    });

    await loadSubscriptions(getCurrentlyActiveServer().guild_id);
})

// #endregion



// #region Load Modal Options

async function loadChannelOptions(guildId) {

    // Disable input while options are loading
    $("#subChannels").prop("disabled", true);

    // Delete existing options
    $("#subChannels option").each(function() {
        if ($(this).val())
            $(this).remove();
    });

    // Clear select2 input
    $("#subChannels").val("").change();
    try {
        const channels = await loadChannels(guildId);
        channels.sort((a, b) => a.position - b.position);

        // If we have reached the discord API rate limit
        if (channels.message && channels.message.includes("rate limit")) {
            throw new Error(
                `${channels.message} Retry after ${channels.retry_after} seconds.`
            )
        }

        // If we can't fetch channels due to error
        if (channels.code === 50001) {
            
            // Also check that the user hasn't changed the currently active guild, otherwise
            // the alert will show under the wrong server.
            if (getCurrentlyActiveServer().guild_id === guildId)
                showServerJoinAlert();

            const guildName = getServerFromSnowflake(guildId).name;

            throw new Error(
                `Unable to retrieve channels from Guild <b>${guildName}</b>.
                Ensure that @PYRSS is a member with permissions
                to view channels.`
            );
        }

        discordChannels = [];
        channels.forEach(channel => {

            // We only want TextChannels, which have a type of 0
            if (channel.type !== 0)
                return;

            let channelObj = {text: `#${channel.name}`, value: channel.id}
            $("#subChannels").append($("<option>", channelObj));
            discordChannels.push(channelObj);
        });
    }
    catch(error) {
        console.error(error);
        showToast("danger", "Error loading channels", error, 18000);       
    }
    finally {
        // Re-enable the input
        $("#subChannels").prop("disabled", false);
    }
}

async function loadMutatorOptions() {

    // Disable input while options are loading
    $(".sub-mutators-field").prop("disabled", true);

    // Delete existing options
    $(".sub-mutators-field option").each(function() {
        if ($(this).val())
            $(this).remove();
    });

    // Clear select2 input
    $(".sub-mutators-field").val("").change();

    try {
        const mutators = await getMutators();
        console.log(JSON.stringify(mutators));

        mutators.forEach(mutator => {
            $(".sub-mutators-field").append($("<option>", {
                text: mutator.name,
                value: mutator.id
            }));
        });
    }
    catch(error) {
        console.error(error);
        showToast("danger", "Error loading sub mutators", error, 18000);       
    }
    finally {
        // Re-enable the input
        $(".sub-mutators-field").prop("disabled", false);
    }
}

async function loadFilterOptions(guildId) {

    // Disable input while options are loading
    $("#subFilters").prop("disabled", true);

    // Delete existing options
    $("#subFilters option").each(function() {
        if ($(this).val())
            $(this).remove();
    });

    // Clear select2 input
    $("#subFilters").val("").change();

    try {
        const filters = await getFilters(guildId);
        console.log(JSON.stringify(filters));

        filters.results.forEach(filter => {
            $("#subFilters").append($("<option>", {
                text: filter.name,
                value: filter.id
            }));
        });
    }
    catch(error) {
        console.error(error);
        showToast("danger", "Error loading sub filters", error, 18000);       
    }
    finally {
        // Re-enable the input
        $("#subFilters").prop("disabled", false);
    }

}

// #endregion


// #region Bot Not in Server Alert
function showServerJoinAlert() {
    const guildId = getCurrentlyActiveServer().guild_id;
    const inviteUrl = `https://discord.com/oauth2/authorize
?client_id=1129345991758336020
&permissions=2147534848&scope=bot+applications.commands
&guild_id=${guildId}
&disable_guild_select=true`

    $("#serverJoinAlert a.alert-link").attr("href", inviteUrl);
    $("#serverJoinAlert").show();
}
// #endregion

function updateBotInviteLink() {
    const guildId = getCurrentlyActiveServer().guild_id;
    const inviteUrl = `https://discord.com/oauth2/authorize
?client_id=1129345991758336020
&permissions=2147534848&scope=bot+applications.commands
&guild_id=${guildId}
&disable_guild_select=true`

    $("#invitePyrssToServerBtn").attr("href", inviteUrl);
}

// #region Colour Controls
$("#subEmbedColour").on("change", function() {
    $("#subEmbedColourText").val($(this).val());
});

$("#subEmbedColourText").on("change", function() {
    $("#subEmbedColour").val($(this).val());
});

function subSetHexColour(hexString) {
    hexString = hexString.toUpperCase();
    $("#subEmbedColour").val(hexString);
    $("#subEmbedColourText").val(hexString);
}

$(document).ready(() => {$("#subResetEmbedColour").click();});

$("#subResetEmbedColour").on("click", function() {
    subSetHexColour("#3498db");
});

$("#subRandomEmbedColour").on("click", function() {
    subSetHexColour(`#${genHexString(6)}`);
});
// #endregion