var timeouts = {};
    tableFilters = {};
    tableSorts = {};

$(document).on("selectedServerChange", async function() {
    // Clear filters and sorts when changing server
    tableFilters = {};
    tableSorts = {};
})

function setTableFilter(tableId, key, value) {
    if (!tableFilters[tableId])
        tableFilters[tableId] = {}

    tableFilters[tableId][key] = value;
}

function setTableSorts(tableId, value) {
    tableSorts[tableId] = value;
}

function ensureTablePagination(tableId) {
    if (!tableFilters[tableId])
        throw new Error(`${tableId} isn't a valid table ID`);

    if (!tableFilters[tableId]["page"])
        setTableFilter(tableId, "page", "1");

    if (!tableFilters[tableId]["page_size"])
        setTableFilter(tableId, "page_size", $(`#${tableId}`).closest(".tab-pane").find(".table-page-sizer").val());
}

async function initTable(containingSelector, tableId, loadDataFunc, newRowFunc, deleteSelectedFunc, options=null) {
    let pageSizeId = tableId + "PageSize";
        searchId = tableId + "SearchBar";
        sortDropdownId = tableId + "SortDropdown";
        filterDropdownId = tableId + "FilterDropdown";

    createSearchRow(containingSelector, searchId, sortDropdownId, filterDropdownId, options, newRowFunc, deleteSelectedFunc);
    createTable(containingSelector, tableId);
    createTableControls(containingSelector, pageSizeId);

    await bindSearchBar(tableId, searchId, loadDataFunc);
    await bindSortDropdown(tableId, sortDropdownId, loadDataFunc);
    await bindFilterDropdown(tableId, filterDropdownId, loadDataFunc);
    await bindTablePagination(tableId, `${containingSelector} .table-pagination`, loadDataFunc);
    await bindTablePaginationResizer(tableId, `${containingSelector} .table-page-sizer`, loadDataFunc);
}

function createSearchRow(containingSelector, searchId, sortDropdownId, filterDropdownId, options, newRowFunc, deleteSelectedFunc) {
    $(containingSelector).append(`
        <div class="row my-3 px-sm-3 table-search-row">
            <div class="col-md-6 col-lg-5 col-xl-4 col-xxl-3">
                <div class="input-group mb-lg-0 mb-3 rounded-1">
                    <span class="input-group-text">
                        <i class="bi bi-search"></i>
                    </span>
                    <input type="search" id="${searchId}" name="${searchId}" class="form-control table-searchbar" placeholder="Search">
                </div>
            </div>
            <div class="col-md-6 col-lg-7 col-xl-8 col-xxl-9 text-md-end table-search-buttons">
                <div class="d-inline-block">
                    <button type="button" class="table-refresh-btn btn btn-outline-secondary rounded-1">
                        <i class="bi bi-arrow-clockwise"></i>
                    </button>
                </div>
            </div>
        </div>
    `);

    // If there is a function that allows the creation of new rows, create a button for it
    if (newRowFunc) {
        $(`${containingSelector} .table-search-row .table-search-buttons`).prepend(`
            <div class="d-inline-block">
                <button type="button" class="table-new-btn btn btn-primary rounded-1">
                    <i class="bi bi-plus-lg"></i>
                </button>
            </div>
        `)
        $(`${containingSelector} .table-search-row .table-search-buttons .table-new-btn`).on("click", async () => {await newRowFunc(-1)});
    }

    // Show the sort dropdown
    if (options && options.sort && options.actions.GET) {
        $(`${containingSelector} .table-search-row .table-search-buttons`).append(`
            <div class="d-inline-block">
                <div id="${sortDropdownId}" class="dropdown table-sort-dropdown">
                    <button type="button" class="btn btn-secondary rounded-1" data-bs-toggle="dropdown" data-bs-auto-close="outside">
                        <i class="bi bi-sort-alpha-up"></i>
                    </button>
                    <ul class="dropdown-menu dropdown-menu-end">
                        <li><h6 class="dropdown-header">Sort By</h6></li>
                    </ul>
                </div>
            </div>
        `);
        populateSortDropdown(sortDropdownId, options.actions.GET, options.sort);
    }

    // Show the filters dropdown
    if (options && options.filter && options.actions.GET) {
        $(`${containingSelector} .table-search-row .table-search-buttons`).append(`
            <div class="d-inline-block">
                <div id="${filterDropdownId}" class="dropdown table-filter-dropdown">
                    <button type="button" class="btn btn-secondary rounded-1" data-bs-toggle="dropdown" data-bs-auto-close="outside">
                        <i class="bi bi-funnel"></i>
                    </button>
                    <ul class="dropdown-menu dropdown-menu-end">
                        <li><h6 class="dropdown-header">Filter By</h6></li>
                    </ul>
                </div>
            </div>
        `);
        populateFilterDropdown(filterDropdownId, options.actions.GET, options.filter);
    }

    // If there is a function for deleting selected rows, create a button for it
    if (deleteSelectedFunc) {
        $(`${containingSelector} .table-search-row .table-search-buttons`).append(`
            <div class="d-inline-block">
                <button type="button" class="table-del-btn btn btn-danger rounded-1">
                    <i class="bi bi-trash3"></i>
                </button>
            </div>
        `)
        $(`${containingSelector} .table-search-row .table-search-buttons .table-del-btn`).on("click", async () => {await deleteSelectedFunc()});
    }
}

function populateSortDropdown(sortDropdownId, options, sortKeys) {
    for (key in options) {
        if (!sortKeys.includes(key))
            continue;

        let label = options[key].label;
            $elem = null;

        $elem = $(`
            <li>
                <button type="button" class="dropdown-item d-flex justify-content-between" data-sortkey="${key}">
                    <span class="me-3">${label}</span><i class="bi bi-x"></i>
                </button>
            </li>
        `);
        bindSortBoolean($elem);
        $(`#${sortDropdownId} .dropdown-menu`).append($elem);
        updateSortCheckboxState($elem);
    }
}

function populateFilterDropdown(filterDropdownId, options, filterKeys) {
    for (key in options) {
        if (!filterKeys.includes(key))
            continue;

        let label = options[key].label;
            id = key + "FilterOption";
            $elem = null;

        switch (options[key].type) {
            case ("boolean"):
                $elem = $(`
                    <li>
                        <input type="checkbox" name="${id}" id="${id}" class="btn-check" autocomplete="off" data-key="${key}">
                        <label for="${id}" class="dropdown-item d-flex justify-content-between" role="button">
                            <span>${label}</span>
                            <i class="bi bi-x"></i>
                        </label>
                    </li>
                `);
                bindFilterBoolean($elem);
                $elem.find('input[type="checkbox"]').prop("checked", options[key].default ? true : false).trigger("change");
                updateFilterCheckboxState($elem);
                break

            default:
                continue;
        }

        $(`#${filterDropdownId} .dropdown-menu`).append($elem);
    }

    // Reset the controls
    $(document).on("selectedServerChange", async function() {
        $(`#${filterDropdownId} .dropdown-menu input[type="checkbox"]`).each(function() {
            $(this).data("state", null);
            updateFilterCheckboxState($(this).parent());
        });
    });
}

function bindSortBoolean($elem) {
    $elem.find("button").on("click", function() {

        // Reset sibling buttons
        $(this).parent().siblings("li").each(function() {
            $(this).find("button").data("state", null);
            updateSortCheckboxState($(this));
        });

        let state = $(this).data("state");
        state = determineState(state);
        $(this).data("state", state);
        updateSortCheckboxState($elem)
    });
}

function bindFilterBoolean($elem) {
    $elem.find('label').on("click", function() {
        let $input = $elem.find('input[type="checkbox"]');
        let state = $input.data("state");
        state = determineState(state);
        $input.data("state", state);
        updateFilterCheckboxState($elem);
    });
}

function determineState(state) {
    switch (state) {
        case true:
            return false;
        case false:
            return null;
        case null:
        default:
            return true;
    }
}

function updateSortCheckboxState($elem) {
    let state = $elem.find("button").data("state");
    let $icon = $elem.find(".bi");
    $icon.removeClass();

    switch (state) {
        case true:
            $icon.addClass("bi bi-sort-alpha-up");
            break;
        case false:
            $icon.addClass("bi bi-sort-alpha-down-alt");
            break;
        case null:
        default:
            $icon.addClass("bi bi-slash invisible");
    }
}

function updateFilterCheckboxState($elem) {
    let state = $elem.find('input[type="checkbox"]').data("state");
    let $icon = $elem.find(".bi");

    switch (state) {
        case true:
            $elem.find('input[type="checkbox"]').prop("checked", true);
            $icon.removeClass().addClass("bi bi-check");
            break;
        case false:
            $elem.find('input[type="checkbox"]').prop("checked", false);
            $icon.removeClass().addClass("bi bi-x");
            break;
        case null:
        default:
            $elem.find('input[type="checkbox"]').prop("checked", false);
            $icon.removeClass().addClass("bi bi-dash invisible");
            break;
    }
}

async function bindSearchBar(tableId, searchBarSelector, loadDataFunc) {
    searchBar = $("#" + searchBarSelector)
    searchBar.on("input", async function() {
        clearTimeout(timeouts[searchBarSelector]);
        var searchString = $(this).val();
        setTableFilter(tableId, "search", searchString);
        timeouts[searchBarSelector] = setTimeout(async function() {
            await loadDataFunc(selectedServer.id);
        }, 300);
    });
}

async function bindFilterDropdown(tableId, filterDropdownId, loadDataFunc) {
    $(`#${filterDropdownId} .dropdown-menu`).on("change", "input", async function() {
        setTableFilter(tableId, $(this).attr("data-key"), $(this).data("state"));
        setTableFilter(tableId, "page", "1");
        await loadDataFunc(selectedServer.id);
    });
}

async function bindSortDropdown(tableId, sortDropdownId, loadDataFunc) {
    $(`#${sortDropdownId} .dropdown-menu`).on("click", "button", async function() {
        let state = $(this).data("state");
            sortKey = "";

        switch(state) {
            case true:
                sortKey = $(this).attr("data-sortkey")
                break;
            case false:
                sortKey = "-" + $(this).attr("data-sortkey")
                break;
            case null:
            default:
                sortKey = ""
        }

        setTableSorts(tableId, sortKey);
        await loadDataFunc(selectedServer.id);
    });
}

function createTable(containingSelector, tableId) {
    $(containingSelector).append(`
    <div class="table-responsive my-3 px-sm-3">
        <table id="${tableId}" class="table table-hover align-middle"></table>
    </div>
    `);
}

function createTableControls(containingSelector, pageSizeId) {
    $(containingSelector).append(`
        <div class="table-controls row px-sm-3 mb-4">
            <div class="col-lg-6">
                <nav class="table-pagination mb-4 mb-lg-0">
                    <ul class="pagination mb-0">
                        <li class="page-item">
                            <button type="button" class="page-link page-prev rounded-start-1">
                                <i class="bi bi-chevron-left"></i>
                            </button>
                        </li>
                        <li class="page-item">
                            <button type="button" class="page-link page-next rounded-end-1">
                                <i class="bi bi-chevron-right"></i>
                            </button>
                        </li>  
                    </ul>
                </nav>
            </div>
            <div class="col-lg-6 d-flex align-items-center justify-content-lg-end">
                <label for="${pageSizeId}" class="form-label align-self-center mb-0 me-2">Show</label>
                <select name="${pageSizeId}" id="${pageSizeId}" class="select-2 table-page-sizer">
                    <option value="10" selected>10&emsp;</option>
                    <option value="15">15&emsp;</option>
                    <option value="20">20&emsp;</option>
                    <option value="25">25&emsp;</option>
                </select>
                <span class="ms-2">of&nbsp;</span>
                <span class="pageinfo-total text-nowrap"></span>
            </div>
        </div>
    `);

    $("#" + pageSizeId).select2({
        theme: "bootstrap",
        minimumResultsForSearch: 10,
    });
}

function updateTableContainer(containerId, page, pageSize, itemsCount, totalItemsCount, nextExists, prevExists) {
    if (!page || !pageSize)
        throw new Error(`Missing page data '${containerId}': page=${page} pageSize=${pageSize}`);

    updateTablePagination(
        `#${containerId} .table-pagination`,
        page,
        pageSize,
        totalItemsCount,
        nextExists,
        prevExists
    );

    $(`#${containerId} .pageinfo-total`).text(`${totalItemsCount} Result${totalItemsCount !== 1 ? "s" : ""}`);
}

// Updates the pagination buttons for a given pageControlsId
function updateTablePagination(pageControlsId, currentPage, pageSize, totalItems, nextExists, prevExists) {
    $(pageControlsId).attr("data-page", currentPage);

    // Remove existing page specific buttons
    $(`${pageControlsId} .page-pick`).remove();

    // Determine states of 'previous page' 'next page' buttons
    $(`${pageControlsId} .page-prev`).toggleClass("disabled", !prevExists).attr("tabindex", prevExists ? "" : "-1");
    $(`${pageControlsId} .page-next`).toggleClass("disabled", !nextExists).attr("tabindex", nextExists ? "" : "-1");

    // Calculate amount of pages to account for
    const pages = Math.max(Math.ceil(totalItems / pageSize), 1);
    const maxVisiblePages = 10;

    let startPage, endPage;

    currentPage = parseInt(currentPage);
    pageSize = parseInt(pageSize);

    if (pages <= maxVisiblePages) {
        // If total pages are less than or equal to max visible pages, show all
        startPage = 1;
        endPage = pages;
    } else {
        // Determine the start and end pages
        const halfVisible = Math.floor(maxVisiblePages / 2);

        if (currentPage <= halfVisible) {
            startPage = 1;
            endPage = maxVisiblePages;
        } else if (currentPage + halfVisible >= pages) {
            startPage = pages - maxVisiblePages + 1;
            endPage = pages;
        } else {
            startPage = currentPage - halfVisible;
            endPage = currentPage + halfVisible;
        }
    }

    if (startPage > 1) {
        addPageButton(pageControlsId, 1);
        if (startPage > 2) {
            addEllipsis(pageControlsId);
        }
    }

    for (let i = startPage; i <= endPage; i++) {
        addPageButton(pageControlsId, i, currentPage);
    }

    if (endPage < pages) {
        if (endPage < pages - 1) {
            addEllipsis(pageControlsId);
        }
        addPageButton(pageControlsId, pages);
    }
}

function addPageButton(pageControlsId, pageNumber, currentPage) {
    let pageItem = $("<li>").addClass("page-item");
    let pageLink = $("<button>")
        .attr("type", "button")
        .attr("data-page", pageNumber)
        .addClass("page-link page-pick")
        .text(pageNumber);

    if (pageNumber === parseInt(currentPage)) {
        pageLink.addClass("disabled").attr("tabindex", -1);
    }

    pageItem.append(pageLink);
    $(`${pageControlsId} .pagination .page-next`).parent().before(pageItem);
}

function addEllipsis(pageControlsId) {
    let ellipsisItem = $("<li>").addClass("page-item disabled");
    let ellipsisLink = $("<span>").addClass("page-link page-pick").text("...");
    ellipsisItem.append(ellipsisLink);
    $(`${pageControlsId} .pagination .page-next`).parent().before(ellipsisItem);
}

// Bind the table pagination buttons to control the table pagination
async function bindTablePagination(tableId, pageControlsId, dataLoadFunc) {
    $(`${pageControlsId} .pagination`).on("click", ".page-link", async function() {
        let wantedPage;
        let currentPage = parseInt($(pageControlsId).attr("data-page"));

        if ($(this).hasClass("page-prev"))
            wantedPage = currentPage - 1;
        else if ($(this).hasClass("page-next"))
            wantedPage = currentPage + 1;
        else
            wantedPage = $(this).attr("data-page");

        setTableFilter(tableId, "page", wantedPage);
        await dataLoadFunc(selectedServer.id)
    });
}

// Bind the table pagination page resizer control
async function bindTablePaginationResizer(tableId, resizerControlId, dataLoadFunc) {
    $(resizerControlId).on("change", async function() {
        setTableFilter(tableId, "page", "1");
        setTableFilter(tableId, "page_size", $(this).val());
    
        await dataLoadFunc(selectedServer.id);
    });
}

function bindTableCheckboxes(tableSelector, tableObject, deleteButtonSelector) {
    // Select a row via checkbox
    $(tableSelector).on("change", "tbody tr .table-select-row", function() {
    
        var selected = $(this).prop("checked");
            rowIndex = $(this).closest("tr").index();
            row = tableObject.row(rowIndex);
    
        if (selected) row.select();
        else row.deselect();
    
        determineSelectAllState(tableSelector, tableObject, deleteButtonSelector);
    });
    
    // Select all rows checkbox
    $(tableSelector).on("change", "thead .table-select-all", function() {
        var selected = $(this).prop("checked");
        $(`${tableSelector} tbody tr`).each(function(rowIndex) {
            var row = tableObject.row(rowIndex);
    
            if (selected) row.select();
            else row.deselect();
    
            $(this).find('.table-select-row').prop("checked", selected);
        });
    
        determineSelectAllState(tableSelector, tableObject, deleteButtonSelector);
    });
}

function determineSelectAllState(tableSelector, tableObject, deleteButtonSelector) {
    var selectedRowsCount = tableObject.rows(".selected").data().toArray().length;
        allRowsCount = tableObject.rows().data().toArray().length;

        selectAllCheckbox = $(`${tableSelector} thead .table-select-all`);

        checked = selectedRowsCount === allRowsCount;
        indeterminate = !checked && selectedRowsCount > 0;

    selectAllCheckbox.prop("checked", checked);
    selectAllCheckbox.prop("indeterminate", indeterminate);

    $(deleteButtonSelector).prop("disabled", !(checked || indeterminate) || !(allRowsCount > 0));
}
