<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/js-md5@0.8.3/src/md5.min.js" crossorigin="anonymous" type="application/javascript"></script>

<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.33.0/tableExport.min.js" crossorigin="anonymous" type="application/javascript"></script>

<script src="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.min.js" crossorigin="anonymous" type="application/javascript"></script>

<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/bootstrap-table.min.js" crossorigin="anonymous" type="application/javascript"></script>

<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/export/bootstrap-table-export.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/resizable/bootstrap-table-resizable.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/filter-control/bootstrap-table-filter-control.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/cookie/bootstrap-table-cookie.min.js" crossorigin="anonymous" type="application/javascript"></script>

<script src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>

<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/highlight.min.js" crossorigin="anonymous" type="application/javascript"></script>

<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>

<script>
    async function copyToClipboard(text, button) {
        await navigator.clipboard.writeText(text);
        button.innerHTML = "<i class=\"bi bi-clipboard-check\"></i> copied";
        setTimeout(_ => {
            button.innerHTML = "<i class=\"bi bi-clipboard\"></i> copy";
        }, 2000);
    }

    function extractDataList(data, column) {
        const elements = data.flatMap(row => row[column].split("<br>")).filter(v => v); // remove empty elements from array
        return Array.from(new Set(elements)).sort();
    }

    function extractListProperties(description, property) {
        return Object.values(description.packages)
            .map(pkg => pkg[property])
            .reduce((left, right) => left.concat(right), []);
    }

    function filterContains(text, value) {
        return value.includes(text.toLowerCase().trim());
    }

    function filterDateRange(text, value) {
        const asOfStartOfDay = date => date.setUTCHours(0, 0, 0, 0);

        const [minDate, maxDate] = text.split(" - ");
        const buildDate = asOfStartOfDay(new Date(value));

        return (buildDate >= new Date(minDate)) && (buildDate <= new Date(maxDate));
    }

    function filterList(index, value, field, data) {
        const dataList = extractDataList(data, field);
        // the library removes all symbols from string, so it is just string
        return value.includes(dataList[index].toLowerCase());
    }

    function hasActiveSelection() {
        return !document.getSelection().isCollapsed; // not sure if it is a valid way, but I guess so
    }

    function hasActiveDropdown() {
        return Array.from(document.querySelectorAll(".dropdown-menu"))
            .some(el => el.classList.contains("show"));
    }

    function hasActiveModal() {
        return Array.from(document.querySelectorAll(".modal"))
            .some(el => el.classList.contains("show"));
    }

    function headerClass(status) {
        if (status === "pending") return ["bg-warning"];
        if (status === "building") return ["bg-warning"];
        if (status === "failed") return ["bg-danger", "text-white"];
        if (status === "success") return ["bg-success", "text-white"];
        return ["bg-secondary", "text-white"];
    }

    function listToTable(data) {
        return Array.from(new Set(data))
            .sort()
            .map(entry => safe(entry))
            .join("<br>");
    }

    function makeRequest(url, params, onSuccess, onFailure) {
        const requestParams = {
            method: params.method,
            body: params.json ? JSON.stringify(params.json) : params.json,
            headers: {
                "Accept": "application/json",
                "Content-Type": "application/json",
            },
        };
        if (params.query) {
            const query = new URLSearchParams(params.query);
            url += `?${query.toString()}`;
        }
        const convert = params.convert ?? (response => response.text());

        return fetch(url, requestParams)
            .then(response => {
                if (response.ok) {
                    return convert(response);
                } else {
                    const error = new Error("Network request error");
                    error.status = response.status;
                    error.statusText = response.statusText;
                    return response.text().then(text => {
                        error.text = text;
                        throw error;
                    });
                }
            })
            .then(data => onSuccess && onSuccess(data))
            .catch(error => onFailure && onFailure(error));
    }

    function readOptional(extractor, callback) {
        for (let value = extractor(); !!value; value = null) {
            callback(value);
        }
    }

    function ready(fn) {
        if (document.readyState === "complete" || document.readyState === "interactive") {
            setTimeout(fn, 1);
        } else {
            document.addEventListener("DOMContentLoaded", fn);
        }
    }

    function restoreAutoReloadSettings(toggle, intervalSelector) {
        readOptional(() => localStorage.getItem(`ahriman-${toggle.id}-refresh-enabled`), value => toggle.checked = value === "true");
        readOptional(() => localStorage.getItem(`ahriman-${toggle.id}-refresh-interval`), value => toggleActiveElement(intervalSelector, "interval", value));
    }

    function safe(string) {
        return String(string)
            .replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/"/g, "&quot;");
    }

    // because I'm tired of safe element generation
    function safeLink(url, text, title) {
        const element = document.createElement("a");
        element.href = url;
        element.innerText = text;
        if (title) {
            element.title = title;
        }
        return element;
    }

    function toggleActiveElement(selector, dataType, value) {
        const targetElement = selector.querySelector(`a[data-${dataType}="${value}"]`);
        if (targetElement?.classList?.contains("active")) {
            return; // element is already active, skip processing
        }

        Array.from(selector.children).forEach(il => {
            Array.from(il.children).forEach(el => el.classList.remove("active"));
        });
        targetElement?.classList?.add("active");
    }

    function toggleAutoReload(toggle, interval, intervalSelector, callback) {
        if (interval) {
            toggle.checked = true; // toggle reload
        } else {
            interval = intervalSelector.querySelector(".active")?.dataset?.interval; // find active element
        }

        let intervalId = null;
        if (interval) {
            if (toggle.checked) {
                // refresh UI
                toggleActiveElement(intervalSelector, "interval", interval);
                // finally create timer task
                intervalId = setInterval(callback, interval);
            }
        } else {
            toggle.checked = false; // no active interval found, disable toggle
        }

        localStorage.setItem(`ahriman-${toggle.id}-refresh-enabled`, toggle.checked);
        localStorage.setItem(`ahriman-${toggle.id}-refresh-interval`, interval);
        return intervalId;
    }

    function updateTable(table, rows, rowChangedKey) {
        // instead of using load method here, we just update rows manually to avoid table reinitialization
        const currentData = table.bootstrapTable("getData").reduce((accumulator, row) => {
            accumulator[row.id] = {state: row["0"], key: rowChangedKey(row)};
            return accumulator;
        }, {});
        // insert or update rows, skipping ones whose status hasn't changed
        rows.forEach(row => {
            if (Object.hasOwn(currentData, row.id)) {
                if (rowChangedKey(row) === currentData[row.id].key) {
                    return;
                }
                row["0"] = currentData[row.id].state; // copy checkbox state
                table.bootstrapTable("updateByUniqueId", {
                    id: row.id,
                    row: row,
                    replace: true,
                });
            } else {
                table.bootstrapTable("insertRow", {index: 0, row: row});
            }
        });
        // remove old rows
        const newData = rows.map(value => value.id);
        Object.keys(currentData).forEach(id => {
            if (!newData.includes(id)) {
                table.bootstrapTable("removeByUniqueId", id);
            }
        });
    }

    Array.prototype.equals = function (right, comparator) {
        let index = this.length;
        if (index !== right.length) {
            return false;
        }

        while (index--) {
            if (!comparator(this[index], right[index])) {
                return false;
            }
        }

        return true;
    }

    Date.prototype.toISOStringShort = function () {
        const pad = number => String(number).padStart(2, "0");
        return `${this.getFullYear()}-${pad(this.getMonth() + 1)}-${pad(this.getDate())} ${pad(this.getHours())}:${pad(this.getMinutes())}:${pad(this.getSeconds())}`;
    }
</script>
