
// region Init Table

function initializeDataTable(tableId, columns) {
    $(tableId).DataTable({
        info: false,
        paging: false,
        ordering: false,
        searching: false,
        autoWidth: false,
        order: [],
        language: {
            emptyTable: "No results found"
        },
        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: "col-checkbox text-center",
                render: function() {
                    return '<input type="checkbox" class="form-check-input table-select-row" />'
                }
            },
            { data: "id", visible: false },
            ...columns
        ]
    });
    bindTablePagination(tableId);
    bindTablePageSizer(tableId);
    bindTableSearch(tableId);
    bindRefreshButton(tableId);
    bindTableSelectColumn(tableId);
}


// region Filters & Ordering

// Filter methods
var _tableFilters = {};
function getTableFilters(tableId) {
    return _tableFilters[tableId];
}

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

    _tableFilters[tableId][key] = value;
}

// Sort methods
var _tableOrdering = {};
function getTableOrdering(tableId) {
    return _tableOrdering[tableId];
}

function setTableOrdering(tableId, value) {
    _tableOrdering[tableId] = value;
}

// Clear all kinds of sorting and filtering when changing servers
$(document).on("selectedServerChange", function() {
    _tableFilters = {};
    _tableOrdering = {};
    $(".table-search-input").val("");
});


// region Load & Clear Data

function wipeTable(tableId) {
    $(`${tableId} thead .table-select-all`).prop("checked", false).prop("indeterminate", false);
    $(tableId).DataTable().clear().draw(false)
}

function populateTable(tableId, data) {
    $(tableId).DataTable().rows.add(data.results).draw(false);
    updateTablePagination(tableId, data);
    updateTableTotalCount(tableId, data);
}

async function loadTableData(tableId, url, method) {
    fixTablePagination(tableId);
    disableTableControls(tableId);

    // Create querystring for filtering against the API
    const filters = getTableFilters(tableId);
    const ordering = getTableOrdering(tableId);
    const querystring = makeQuerystring(filters, ordering);

    // API request
    const data = await ajaxRequest(url + querystring, method);

    // Update table with new data
    wipeTable(tableId);
    populateTable(tableId, data);
    enableTableControls(tableId);
}


// region Pagination

function bindTablePagination(tableId) {
    let $paginationArea = $(tableId).closest('.js-tableBody').siblings('.js-tableControls').find('.pagination');
    $paginationArea.on("click", ".page-link", function() {
        let currentPage = parseInt($paginationArea.data("page"));
        let wantedPage;

        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);
        $(tableId).trigger("doDataLoad");
    });
}

function fixTablePagination(tableId) {
    let filters = getTableFilters(tableId);
    let $pageSizer = $(tableId).closest('.js-tableBody').siblings('.js-tableControls').find(".table-page-sizer");

    if (!("page" in filters)) {
        setTableFilter(tableId, "page", 1);
    }
    if (!("page_size" in filters)) {
        setTableFilter(tableId, "page_size", $($pageSizer).val())
    }
}

function updateTablePagination(tableId, data) {
    let filters = getTableFilters(tableId);

    let $paginationArea = $(tableId).closest('.js-tableBody').siblings('.js-tableControls').find('.pagination');
    $paginationArea.data("page", filters.page);  // store the page for later

    // Remove existing buttons for specific pages
    $paginationArea.find(".page-pick").remove();

    // Enable/disable 'next' and 'prev' buttons
    $paginationArea.find(".page-prev").toggleClass("disabled", !data.previous);
    $paginationArea.find(".page-next").toggleClass("disabled", !data.next);

    const pagesToShow = Math.max(Math.ceil(data.count / filters.page_size), 1);
    const maxPagesToShow = 10;

    let startPage = 1;
    let endPage;
    let page = parseInt(filters.page);

    // Determine the start and end page
    if (pagesToShow <= maxPagesToShow) {
        endPage = pagesToShow;
    }
    else {
        const halfVisible = Math.floor(maxPagesToShow / 2);
        if (page <= halfVisible) {
            endPage = maxPagesToShow;
        }
        else if (page + halfVisible >= pagesToShow) {
            startPage = pagesToShow - maxPagesToShow + 1;
            endPage = pagesToShow;
        }
        else {
            startPage = page - halfVisible;
            endPage = page + halfVisible;
        }
    }

    // Add buttons
    if (startPage > 1) {
        AddTablePageButton($paginationArea, 1);
        if (startPage > 2) {
            AddTablePageEllipsis($paginationArea);
        }
    }
    for (i = startPage; i <= endPage; i++) {
        AddTablePageButton($paginationArea, i, page);
    }
    if (endPage < pagesToShow) {
        if (endPage < pagesToShow - 1) {
            AddTablePageEllipsis($paginationArea);
        }
        AddTablePageButton($paginationArea, pagesToShow);
    }
}

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

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

    pageItem.append(pageLink);
    $paginationArea.find(".page-next").parent().before(pageItem);
}

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


// region Page Sizer

function updateTableTotalCount(tableId, data) {
    let $tableControls = $(tableId).closest('.js-tableBody').siblings('.js-tableControls');
    $tableControls.find('.pageinfo-total').text(data.count);
}

function bindTablePageSizer(tableId) {
    let $tableControls = $(tableId).closest('.js-tableBody').siblings('.js-tableControls');
    $tableControls.on("change", ".table-page-sizer", function() {
        setTableFilter(tableId, "page", "1");
        setTableFilter(tableId, "page_size", $(this).val());
        $(tableId).trigger("doDataLoad");
    });
}


// region Search Filters

_searchTimeouts = {};
function bindTableSearch(tableId) {
    const $tableFilters = $(tableId).closest('.js-tableBody').siblings('.js-tableFilters');
    $tableFilters.on("input", ".table-search-input", function() {
        $(this).data("was-focused", true);
        clearTimeout(_searchTimeouts[tableId]);

        const searchString = $(this).val();
        setTableFilter(tableId, "search", searchString);
        setTableFilter(tableId, "page", 1);  // back to first page, as desired page no. might not exist after filtering

        _searchTimeouts[tableId] = setTimeout(function() {
            $(tableId).trigger("doDataLoad");
        }, 300);
    });
}


// region Button Controls

function bindRefreshButton(tableId) {
    const $tableFilters = $(tableId).closest('.js-tableBody').siblings('.js-tableFilters');
    $tableFilters.on("click", ".table-refresh-btn", function() {
        $tableFilters.find(".table-del-btn").prop("disabled", true);
        $(tableId).trigger("doDataLoad");
    })
}


// region Select Checkboxes

function bindTableSelectColumn(tableId) {
    $(tableId).on("change", "tbody tr .table-select-row", function() {
        let selected = $(this).prop("checked");
        let rowIndex = $(this).closest("tr").index();
        let row = $(tableId).DataTable().row(rowIndex);

        selected === true ? row.select() : row.deselect();
        determineSelectAllState(tableId);
    });

    $(tableId).on("change", "thead .table-select-all", function() {
        let selected = $(this).prop("checked");
        let table = $(tableId).DataTable();
        $(tableId).find("tbody tr").each(function(rowIndex) {
            let row = table.row(rowIndex);
            selected === true ? row.select() : row.deselect();
            $(this).find(".table-select-row").prop("checked", selected);
        });

        determineSelectAllState(tableId);
    });
}

function determineSelectAllState(tableId) {
    let table = $(tableId).DataTable();
    let selectedRowsCount = table.rows(".selected").data().toArray().length;
    let allRowsCount = table.rows().data().toArray().length;

    let doCheck = selectedRowsCount === allRowsCount;
    let doIndeterminate = !doCheck && selectedRowsCount > 0;

    $checkbox = $(tableId).find("thead .table-select-all");
    $checkbox.prop("checked", doCheck);
    $checkbox.prop("indeterminate", doIndeterminate);

    $(tableId).closest(".js-tableBody").siblings(".js-tableFilters").find(".table-del-btn").prop("disabled", !doCheck && !doIndeterminate);
}


// region On/Off Controls

function enableTableControls(tableId) {
    setTableControlsUsability(tableId, false);
}

function disableTableControls(tableId) {
    setTableControlsUsability(tableId, true);
}

function setTableControlsUsability(tableId, disabled) {
    const $table = $(tableId);
    const $tableBody = $table.closest(".js-tableBody");
    const $tableFilters = $tableBody.siblings(".js-tableFilters");
    const $tableControls = $tableBody.siblings(".tableControls");

    $tableBody.find(".disable-while-loading").prop("disabled", disabled);
    $tableFilters.find(".disable-while-loading").prop("disabled", disabled);
    $tableControls.find(".disable-while-loading").prop("disabled", disabled);

    // Re-focus search bar if used
    const $search = $tableFilters.find(".disable-while-loading[type=search]");
    $search.data("was-focused") ? $search.focus() : null;
}


// region Modals


async function openDataModal(modalId, pk, url) {
    $modal = $(modalId);
    $modal.data("primary-key", pk);
    clearValidation($modal);

    if (parseInt(pk) === -1) {
        $modal.find(".form-create").show();
        $modal.find(".form-edit").hide();
        setDefaultModalData($modal);
    }
    else {
        $modal.find(".form-create").hide();
        $modal.find(".form-edit").show();
        await loadModalData($modal, url);
    }

    $modal.modal("show");
}

function setDefaultModalData($modal) {
    $modal.find("[data-field]").each(function() {
        const type = $(this).attr("type");
        const defaultVal = $(this).attr("data-default") || "";

        if (type === "checkbox") {
            $(this).prop("checked", defaultVal === "true");
        }
        else if (type === "datetime-local") {
            $(this).val(getCurrentDateTime());
        }
        else if ($(this).is("select") && defaultVal === "firstOption") {
            $(this).val($(this).find("option:first").val()).change();
        }
        else {
            $(this).val(defaultVal).change();
        }
    });
}

function clearValidation($modal) {
    $modal.find(".invalid-feedback").remove();
    $modal.find(".is-invalid").removeClass("is-invalid");
}

async function loadModalData($modal, url) {
    const data = await ajaxRequest(url, "GET");

    $modal.find("[data-field]").each(function() {
        const key = $(this).attr("data-field");
        const value = data[key];

        if (typeof value === "boolean") {
            $(this).prop("checked", value);
        }
        else if (isISODateTimeString(value)) {
            $(this).val(value.split('+')[0].substring(0, 16));
        }
        else if ($(this).attr("type") === "color") {
            $(this).val(`#${value}`);
        }
        else {
            $(this).val(value).change();
        }
    });
}

async function onModalSubmit($modal, $table, url) {
    if (!selectedServer) {
        return;
    }

    clearValidation($modal);
    let data = { server: selectedServer.id };

    $modal.find("[data-field]").each(function() {
        const type = $(this).attr("type");
        const key = $(this).attr("data-field");
        if (!key) {
            return;
        }

        let value;
        switch (type) {
            case "checkbox":
                value = $(this).prop("checked");
                break;
            case "color":
                value = $(this).val();
                value = value ? value.replace("#", "") : value;
                break;
            default:
                value = $(this).val();
                break;
        }

        data[key] = value;
    });

    const formData = objectToFormData(data);
    const id = $modal.data("primary-key");
    const isNewItem = parseInt(id) !== -1;
    const method = isNewItem ? "PUT" : "POST";
    url = isNewItem ? url + `${id}/` : url;

    ajaxRequest(url, method, formData)
        .then(response => {
            $table.trigger("doDataLoad");
            $modal.modal("hide");
        })
        .catch(error => {
            logError(error);
            if (typeof error === "object" && "responseJSON" in error) {
                renderErrorMessages($modal, error.responseJSON);
            }
        });
}


// region Modal Error Msgs

function renderErrorMessages($modal, errorObj) {
    for (const key in errorObj) {
        const value = errorObj[key];
        const $input = $modal.find(`[data-field="${key}"]`);
        $input.addClass("is-invalid");
        $input.nextAll(".form-text").last().after(
            `<div class="invalid-feedback">${value}</div>`
        )
    }
}


// region Table Col Types

function renderEditColumn(data) {
    const name = sanitise(data);
    return `<span class="act-as-link edit-modal" role="button">${name}</span>`
}

function renderAnchorColumn(name, href) {
    name = sanitise(name);
    href = sanitise(href);

    return `<a href="${href}" class="act-as-link">${name}</a>`;
}

function renderBooleanColumn(data) {
    const iconClass = data ? "bi-check-circle-fill text-success" : "bi-x-circle-fill text-danger";
    return `<i class="bi ${iconClass}"></i>`;
}

function renderBadgeColumn(data, colour=null) {
    let badge = $(`<span class="badge text-bg-secondary rounded-1 border border-1">${data}</span>`)
    if (colour) {
        badge[0].style.setProperty("border-color", `#${colour}`, "important");
    }

    return badge.prop("outerHTML");
}

function renderArrayBadgesColumn(data) {
    let badges = $("<div>");

    data.forEach((item, index) => {
        let badge = $(`<span class="badge text-bg-secondary rounded-1">${item}</span>`);
        if (index > 0) { badge.addClass("ms-2") }
        badges.append(badge);
    });

    return badges.html();
}

function renderArrayDropdownColumn(data, icon, headerText) {
    if (!data.length) {
        return "";
    }

    let $dropdown = $(`
        <div class="dropdown">
            <button type="button" class="dropdown-toggle" data-bs-toggle="dropdown">
                <i class="fs-5 bi ${icon}"></i>
            </button>
            <div class="dropdown-menu">
                <li><h6 class="dropdown-header">${headerText} (${data.length})</h6></li>
            </div>
        </div>
    `);
    let $dropdownItems = $dropdown.find(".dropdown-menu")

    data.forEach(item => {
        let $itemBtn = $(`<li><button type="button" class="dropdown-item">${item}</button></li>`);
        $dropdownItems.append($itemBtn);
    });

    return $dropdown.prop("outerHTML");
}

const renderHexColourColumn = data => {
    const hexWithHashtag = `#${data}`.toUpperCase();

    let icon = $("<div>");
    icon.addClass("col-hex-icon");
    icon.css("background-color", hexWithHashtag);

    return $(`<span data-bs-toggle="tooltip" data-bs-title="${hexWithHashtag}">${icon.prop("outerHTML")}</span>`).tooltip()[0];
}

function renderMutatorColumn(data) {
    if (!("id" in data)) {
        return "";
    }

    return $(`<span class="badge text-bg-secondary rounded-1">${data.name}</span>`).prop("outerHTML");
}

const renderLinkToStyleColumn = style => {    
    const hexWithHashtag = `#${style.colour}`.toUpperCase();

    let icon = $("<div>");
    icon.addClass("col-hex-icon js-openSubStyle");
    icon.css("background-color", hexWithHashtag);

    return $(`<span data-bs-toggle="tooltip" data-bs-title="${hexWithHashtag}" role="button">${icon.prop("outerHTML")}</span>`).tooltip()[0];
}

const renderPopoverBadgesColumn = (items, iconClass) => {
    if (!items.length) {
        return "";
    }

    let $span = $("<span>");
    $span.attr("data-bs-toggle", "popover");
    $span.attr("data-bs-trigger", "hover focus");
    $span.attr("data-bs-custom-class", "table-badge-popover")
    $span.attr("data-bs-html", "true");

    let $placeholderContainer = $("<div>");

    items.forEach(item => {
        let $badge = $("<div>");
        $badge.addClass("badge text-bg-secondary rounded-1 me-2 mb-2 text-wrap mw-100 text-start");
        $badge.text(item);
        $placeholderContainer.append($badge);
    });

    $span.attr("data-bs-content", $placeholderContainer.html());
    $span.html(`<i class="bi ${iconClass} fs-5"></i>`)
    return $span.popover()[0];
}


// region Get Table Parts

function getTableFiltersComponent(tableId) {
    return $(tableId).closest(".js-tableBody").siblings(".js-tableFilters");
}

function getTableControlsComponent(tableId) {
    return $(tableId).closest(".js-tableBody").siblings(".js-tableControls");
}

function getSelectedTableRows(tableId) {
    return $(tableId).DataTable().rows(".selected").data().toArray();
}