<div id="package-info-modal" tabindex="-1" role="dialog" class="modal fade">
    <div class="modal-dialog modal-xl" role="document">
        <div class="modal-content">
            <div id="package-info-modal-header" class="modal-header">
                <h4 id="package-info" class="modal-title"></h4>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
            </div>
            <div class="modal-body">
                <div class="form-group row mt-2">
                    <div class="col-4 col-lg-1" style="text-align: right">packages</div>
                    <div id="package-info-packages" class="col-8 col-lg-5"></div>
                    <div class="col-4 col-lg-1" style="text-align: right">version</div>
                    <div id="package-info-version" class="col-8 col-lg-5"></div>
                </div>

                <div class="form-group row mt-2">
                    <div class="col-4 col-lg-1" style="text-align: right">packager</div>
                    <div id="package-info-packager" class="col-8 col-lg-5"></div>
                    <div class="col-4 col-lg-1" style="text-align: right"></div>
                    <div id="package-info---placeholder" class="col-8 col-lg-5"></div>
                </div>

                <div class="form-group row mt-2">
                    <div class="col-4 col-lg-1" style="text-align: right">groups</div>
                    <div id="package-info-groups" class="col-8 col-lg-5"></div>
                    <div class="col-4 col-lg-1" style="text-align: right">licenses</div>
                    <div id="package-info-licenses" class="col-8 col-lg-5"></div>
                </div>

                <div class="form-group row mt-2">
                    <div class="col-4 col-lg-1" style="text-align: right">upstream</div>
                    <div id="package-info-upstream-url" class="col-8 col-lg-5"></div>
                    <div class="col-4 col-lg-1" style="text-align: right">AUR</div>
                    <div id="package-info-aur-url" class="col-8 col-lg-5"></div>
                </div>

                <div class="form-group row mt-2">
                    <div class="col-4 col-lg-1" style="text-align: right">depends</div>
                    <div id="package-info-depends" class="col-8 col-lg-5"></div>
                    <div class="col-4 col-lg-1" style="text-align: right">implicitly depends</div>
                    <div id="package-info-implicitly-depends" class="col-8 col-lg-5"></div>
                </div>

                <hr class="col-12">

                <div id="package-info-variables-block" hidden>
                    <h3>Environment variables</h3>
                    <div id="package-info-variables-div" class="form-group row"></div>

                    <hr class="col-12">
                </div>

                <nav>
                    <div class="nav nav-tabs" role="tablist">
                        <button id="package-info-logs-button" class="nav-link active" data-bs-toggle="tab" data-bs-target="#package-info-logs" type="button" role="tab" aria-controls="package-info-logs" aria-selected="true">Build logs</button>
                        <button id="package-info-changes-button" class="nav-link" data-bs-toggle="tab" data-bs-target="#package-info-changes" type="button" role="tab" aria-controls="package-info-changes" aria-selected="false">Changes</button>
                        <button id="package-info-events-button" class="nav-link" data-bs-toggle="tab" data-bs-target="#package-info-events" type="button" role="tab" aria-controls="package-info-events" aria-selected="false">Events</button>
                    </div>
                </nav>
                <div class="tab-content" id="nav-tabContent">
                    <div id="package-info-logs" class="tab-pane fade show active" role="tabpanel" aria-labelledby="package-info-logs-button" tabindex="0">
                        <div class="row">
                            <div class="col-1 dropend">
                                <button id="package-info-logs-dropdown" class="btn dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
                                    <i class="bi bi-list"></i>
                                </button>
                                <nav id="package-info-logs-versions" class="dropdown-menu" aria-labelledby="package-info-logs-dropdown"></nav>
                            </div>
                            <div class="col-11">
                                <pre class="language-console"><code id="package-info-logs-input" class="pre-scrollable language-console"></code><button id="package-info-logs-copy-button" type="button" class="btn language-console" onclick="copyLogs()"><i class="bi bi-clipboard"></i> copy</button></pre>
                            </div>
                        </div>
                    </div>
                    <div id="package-info-changes" class="tab-pane fade" role="tabpanel" aria-labelledby="package-info-changes-button" tabindex="0">
                        <pre class="language-diff"><code id="package-info-changes-input" class="pre-scrollable language-diff"></code><button id="package-info-changes-copy-button" type="button" class="btn language-diff" onclick="copyChanges()"><i class="bi bi-clipboard"></i> copy</button></pre>
                    </div>
                    <div id="package-info-events" class="tab-pane fade" role="tabpanel" aria-labelledby="package-info-events-button" tabindex="0">
                        <canvas id="package-info-events-update-chart" hidden></canvas>
                        <table id="package-info-events-table"
                               data-classes="table table-hover"
                               data-sortable="true"
                               data-sort-name="timestamp"
                               data-sort-order="desc">
                            <thead class="table-primary">
                                <tr>
                                    <th data-align="right" data-field="timestamp">date</th>
                                    <th data-field="event">event</th>
                                    <th data-field="message">description</th>
                                </tr>
                            </thead>
                        </table>
                    </div>
                </div>
            </div>
            <div class="modal-footer">
                {% if not auth.enabled or auth.username is not none %}
                    <input id="package-info-refresh-input" type="checkbox" class="form-check-input" value="" checked>
                    <label for="package-info-refresh-input" class="form-check-label">update pacman databases</label>

                    <button id="package-info-update-button" type="submit" class="btn btn-success" onclick="packageInfoUpdate()"><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button>
                    <button id="package-info-remove-button" type="submit" class="btn btn-danger" onclick="packageInfoRemove()" data-bs-dismiss="modal"><i class="bi bi-trash"></i><span class="d-none d-sm-inline"> remove</span></button>
                {% endif %}
                {% if autorefresh_intervals %}
                    <button type="button" class="btn btn-secondary" onclick="showPackageInfo()"><i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span></button>
                    <div class="btn-group dropup">
                        <input id="package-info-autoreload-button" type="checkbox" class="btn-check" autocomplete="off" onclick="togglePackageInfoAutoReload()" checked>
                        <label for="package-info-autoreload-button" class="btn btn-outline-secondary" title="toggle auto reload"><i class="bi bi-clock"></i></label>
                        <button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
                            <span class="visually-hidden">select interval</span>
                        </button>
                        <ul id="package-info-autoreload-input" class="dropdown-menu">
                            {% for interval in autorefresh_intervals %}
                                <li><a class="dropdown-item {{ "active" if interval.is_active }}" onclick="togglePackageInfoAutoReload({{ interval.interval }})" data-interval="{{ interval.interval }}">{{ interval.text }}</a></li>
                            {% endfor %}
                        </ul>
                    </div>
                {% endif %}
                <button type="button" class="btn btn-primary" data-bs-dismiss="modal"><i class="bi bi-x"></i><span class="d-none d-sm-inline"> close</span></button>
            </div>
        </div>
    </div>
</div>

<script>
    const packageInfoModal = document.getElementById("package-info-modal");
    const packageInfoModalHeader = document.getElementById("package-info-modal-header");
    const packageInfo = document.getElementById("package-info");

    const packageInfoLogsVersions = document.getElementById("package-info-logs-versions");
    const packageInfoLogsInput = document.getElementById("package-info-logs-input");
    const packageInfoLogsCopyButton = document.getElementById("package-info-logs-copy-button");

    const packageInfoChangesInput = document.getElementById("package-info-changes-input");
    const packageInfoChangesCopyButton = document.getElementById("package-info-changes-copy-button");

    // so far bootstrap-table only operates with jquery elements
    const packageInfoEventsTable = $(document.getElementById("package-info-events-table"));
    const packageInfoEventsUpdateChartCanvas = document.getElementById("package-info-events-update-chart");
    let packageInfoEventsUpdateChart = null;

    const packageInfoAurUrl = document.getElementById("package-info-aur-url");
    const packageInfoDepends = document.getElementById("package-info-depends");
    const packageInfoGroups = document.getElementById("package-info-groups");
    const packageInfoImplicitlyDepends = document.getElementById("package-info-implicitly-depends");
    const packageInfoLicenses = document.getElementById("package-info-licenses");
    const packageInfoPackager = document.getElementById("package-info-packager");
    const packageInfoPackages = document.getElementById("package-info-packages");
    const packageInfoUpstreamUrl = document.getElementById("package-info-upstream-url");
    const packageInfoVersion = document.getElementById("package-info-version");

    const packageInfoVariablesBlock = document.getElementById("package-info-variables-block");
    const packageInfoVariablesDiv = document.getElementById("package-info-variables-div");

    const packageInfoRefreshInput = document.getElementById("package-info-refresh-input");

    const packageInfoAutoReloadButton = document.getElementById("package-info-autoreload-button");
    const packageInfoAutoReloadInput = document.getElementById("package-info-autoreload-input");
    let packageInfoAutoReloadTask = null;

    function clearChart() {
        packageInfoEventsUpdateChartCanvas.hidden = true;
        if (packageInfoEventsUpdateChart) {
            packageInfoEventsUpdateChart.data = {};
            packageInfoEventsUpdateChart.update();
        }
    }

    function convertLogs(data, filter) {
        return data
            .filter((filter || Boolean))
            .map(log_record => `[${new Date(1000 * log_record.created).toISOString()}] ${log_record.message}`)
            .join("\n");
    }

    async function copyChanges() {
        const changes = packageInfoChangesInput.textContent;
        await copyToClipboard(changes, packageInfoChangesCopyButton);
    }

    async function copyLogs() {
        const logs = packageInfoLogsInput.textContent;
        await copyToClipboard(logs, packageInfoLogsCopyButton);
    }

    function highlight(element) {
        delete element.dataset.highlighted;
        hljs.highlightElement(element);
    }

    function insertVariable(packageBase, variable) {
        const variableInput = document.createElement("div");
        variableInput.classList.add("input-group");

        const variableNameInput = document.createElement("input");
        variableNameInput.classList.add("form-control");
        variableNameInput.readOnly = true;
        variableNameInput.value = variable.key;

        const variableSeparator = document.createElement("span");
        variableSeparator.classList.add("input-group-text")
        variableSeparator.textContent = "=";

        const variableValueInput = document.createElement("input");
        variableValueInput.classList.add("form-control");
        variableValueInput.readOnly = true;
        variableValueInput.value = JSON.stringify(variable.value);

        const variableButtonRemove = document.createElement("button");
        variableButtonRemove.type = "button";
        variableButtonRemove.classList.add("btn");
        variableButtonRemove.classList.add("btn-outline-danger");
        variableButtonRemove.innerHTML = "<i class=\"bi bi-trash\"></i>";
        variableButtonRemove.onclick = _ => {
            makeRequest(
                `/api/v1/packages/${packageBase}/patches/${variable.key}`,
                {
                    method: "DELETE",
                },
                _ => variableInput.remove(),
            );
        };

        // bring them together
        variableInput.appendChild(variableNameInput);
        variableInput.appendChild(variableSeparator);
        variableInput.appendChild(variableValueInput);
        variableInput.appendChild(variableButtonRemove);

        packageInfoVariablesDiv.appendChild(variableInput);
    }

    function loadChanges(packageBase, onFailure) {
        makeRequest(
            `/api/v1/packages/${packageBase}/changes`,
            {
                query: {
                    architecture: repository.architecture,
                    repository: repository.repository,
                },
                convert: response => response.json(),
            },
            data => {
                const changes = data.changes;
                packageInfoChangesInput.textContent = changes ?? "";
                highlight(packageInfoChangesInput);
            },
            onFailure,
        );
    }

    function loadDependencies(packageBase, onFailure) {
        makeRequest(
            `/api/v1/packages/${packageBase}/dependencies`,
            {
                query: {
                    architecture: repository.architecture,
                    repository: repository.repository,
                },
                convert: response => response.json(),
            },
            data => {
                 packageInfoImplicitlyDepends.innerHTML = listToTable(
                    Object.values(data.paths)
                        .reduce((accumulator, currentValue) => accumulator.concat(currentValue), [])
                );
            },
            onFailure,
        );
    }

    function loadEvents(packageBase, onFailure) {
        packageInfoEventsTable.bootstrapTable("showLoading");
        clearChart();

        makeRequest(
            "/api/v1/events",
            {
                query: {
                    architecture: repository.architecture,
                    repository: repository.repository,
                    object_id: packageBase,
                    limit: 30,
                },
                convert: response => response.json(),
            },
            data => {
                const events = data.map(event => {
                    return {
                        timestamp: new Date(1000 * event.created).toISOStringShort(),
                        event: event.event,
                        message: event.message || "",
                    };
                });
                const chart = data.filter(event => event.event === "package-updated");

                packageInfoEventsTable.bootstrapTable("load", events);
                packageInfoEventsTable.bootstrapTable("hideLoading");

                if (packageInfoEventsUpdateChart) {
                    packageInfoEventsUpdateChart.config.data = {
                        labels: chart.map(event => new Date(1000 * event.created).toISOStringShort()),
                        datasets: [{
                            label: "update duration, s",
                            data: chart.map(event => event.data.took),
                            cubicInterpolationMode: "monotone",
                            tension: 0.4,
                        }],
                    };
                    packageInfoEventsUpdateChart.update();
                }
                packageInfoEventsUpdateChartCanvas.hidden = !chart.length;
            },
            onFailure,
        );
    }

    function loadLogs(packageBase, onFailure) {
        const sortFn = (left, right) => left.process_id.localeCompare(right.process_id) || left.version.localeCompare(right.version);
        const compareFn = (left, right) => left.process_id === right.process_id && left.version === right.version;

        makeRequest(
            `/api/v2/packages/${packageBase}/logs`,
            {
                query: {
                    architecture: repository.architecture,
                    head: true,
                    repository: repository.repository,
                },
                convert: response => response.json(),
            },
            data => {
                const currentVersions = Array.from(packageInfoLogsVersions.children)
                    .map(el => {
                        return {
                            process_id: el.dataset.processId,
                            version: el.dataset.version,
                        };
                    })
                    .sort(sortFn);
                const newVersions = data
                    .map(el => {
                        return {
                            process_id: el.process_id,
                            version: el.version,
                        };
                    })
                    .sort(sortFn);

                if (currentVersions.equals(newVersions, compareFn))
                    loadLogsActive(packageBase);
                else
                    loadLogsAll(packageBase, onFailure);
            },
        )
    }

    function loadLogsActive(packageBase) {
        const activeLogSelector = packageInfoLogsVersions.querySelector(".active");

        if (activeLogSelector) {
            makeRequest(
                `/api/v2/packages/${packageBase}/logs`,
                {
                    query: {
                        architecture: repository.architecture,
                        repository: repository.repository,
                        version: activeLogSelector.dataset.version,
                        process_id: activeLogSelector.dataset.processId,
                    },
                    convert: response => response.json(),
                },
                data => {
                    activeLogSelector.dataset.logs = convertLogs(data);
                    activeLogSelector.click();
                },
            );
        }
    }

    function loadLogsAll(packageBase, onFailure) {
        makeRequest(
            `/api/v2/packages/${packageBase}/logs`,
            {
                query: {
                    architecture: repository.architecture,
                    repository: repository.repository,
                },
                convert: response => response.json(),
            },
            data => {
                const selectors = Object
                    .values(
                        data.reduce((acc, log_record) => {
                            const id = `${log_record.version}-${log_record.process_id}`;
                            if (acc[id])
                                acc[id].created = Math.min(log_record.created, acc[id].created);
                            else
                                acc[id] = log_record;
                            return acc;
                        }, {})
                    )
                    .sort(({created: left}, {created: right}) =>
                        right - left
                    )
                    .map(version => {
                        const link = document.createElement("a");
                        link.classList.add("dropdown-item");

                        link.dataset.version = version.version;
                        link.dataset.processId = version.process_id;
                        link.dataset.logs = convertLogs(data, log_record => log_record.version === version.version && log_record.process_id === version.process_id);

                        link.textContent = new Date(1000 * version.created).toISOStringShort();
                        link.href = "#";
                        link.onclick = _ => {
                            // check if we are at the bottom of the code block
                            const isScrolledToBottom = packageInfoLogsInput.scrollTop + packageInfoLogsInput.clientHeight >= packageInfoLogsInput.scrollHeight;
                            packageInfoLogsInput.textContent = link.dataset.logs;
                            highlight(packageInfoLogsInput);
                            if (isScrolledToBottom)
                                packageInfoLogsInput.scrollTop = packageInfoLogsInput.scrollHeight; // scroll to the new end

                            Array.from(packageInfoLogsVersions.children).forEach(el => el.classList.remove("active"));
                            link.classList.add("active");

                            return false;
                        };

                        return link;
                    });

                packageInfoLogsVersions.replaceChildren(...selectors);
                selectors.find(Boolean)?.click();
            },
            onFailure,
        );
    }

    function loadPackage(packageBase, onFailure) {
        makeRequest(
            `/api/v1/packages/${packageBase}`,
            {
                query: {
                    architecture: repository.architecture,
                    repository: repository.repository,
                },
                convert: response => response.json(),
            },
            data => {
                const description = data.find(Boolean);
                const packages = description.package.packages;
                const aurUrl = description.package.remote.web_url;
                const upstreamUrls = Array.from(new Set(Object.values(packages).map(single => single.url))).sort();

                packageInfo.textContent = `${description.package.base} ${description.status.status} at ${new Date(1000 * description.status.timestamp).toISOStringShort()}`;

                packageInfoModalHeader.classList.remove(...packageInfoModalHeader.classList);
                packageInfoModalHeader.classList.add("modal-header");
                headerClass(description.status.status).forEach(clz => packageInfoModalHeader.classList.add(clz));

                packageInfoAurUrl.innerHTML = aurUrl ? safeLink(aurUrl, aurUrl, "AUR link").outerHTML : "";
                packageInfoDepends.innerHTML = listToTable(
                    Object.values(packages)
                        .reduce((accumulator, currentValue) => {
                            return accumulator.concat(currentValue.depends.filter(v => !packages.hasOwnProperty(v)))
                                .concat(currentValue.make_depends.filter(v => !packages.hasOwnProperty(v)).map(v => `${v} (make)`))
                                .concat(currentValue.opt_depends.filter(v => !packages.hasOwnProperty(v)).map(v => `${v} (optional)`));
                        }, [])
                );
                packageInfoGroups.innerHTML = listToTable(extractListProperties(description.package, "groups"));
                packageInfoLicenses.innerHTML = listToTable(extractListProperties(description.package, "licenses"));
                packageInfoPackager.textContent = description.package.packager;
                packageInfoPackages.innerHTML = listToTable(Object.entries(packages).map(([key, value]) => `${key} (${value.description})`));
                packageInfoUpstreamUrl.innerHTML = upstreamUrls.map(url => safeLink(url, url, "upstream link").outerHTML).join("<br>");
                packageInfoVersion.textContent = description.package.version;
            },
            onFailure,
        );
    }

    function loadPatches(packageBase, onFailure) {
        makeRequest(
            `/api/v1/packages/${packageBase}/patches`,
            {
                convert: response => response.json(),
            },
            data => {
                packageInfoVariablesDiv.replaceChildren();
                data.map(patch => insertVariable(packageBase, patch));
                packageInfoVariablesBlock.hidden = !data.length;
            },
            onFailure,
        );
    }

    function packageInfoRemove() {
        const packageBase = packageInfoModal.dataset.package;
        packagesRemove([packageBase]);
    }

    function packageInfoUpdate() {
        const packageBase = packageInfoModal.dataset.package;
        packagesAdd(packageBase, [], repository, {refresh: packageInfoRefreshInput.checked});
    }

    function showPackageInfo(packageBase) {
        const isPackageBaseSet = packageBase !== undefined;
        if (isPackageBaseSet) {
            // set package base as currently used
            packageInfoModal.dataset.package = packageBase;
        } else {
            // read package base from the current window attribute
            packageBase = packageInfoModal.dataset.package;
        }

        const onFailure = error => {
            if (isPackageBaseSet) {
                const message = details => `Could not load package ${packageBase} info: ${details}`;
                showFailure("Load failure", message, error);
            }
        };

        loadPackage(packageBase, onFailure);
        loadDependencies(packageBase, onFailure);
        loadPatches(packageBase, onFailure);
        loadLogs(packageBase, onFailure);
        loadChanges(packageBase, onFailure);
        loadEvents(packageBase, onFailure);

        if (isPackageBaseSet) {
            bootstrap.Modal.getOrCreateInstance(packageInfoModal).show();
            {% if autorefresh_intervals %}
                togglePackageInfoAutoReload();
            {% endif %}
        }
    }

    function togglePackageInfoAutoReload(interval) {
        clearInterval(packageInfoAutoReloadTask);
        packageInfoAutoReloadTask = toggleAutoReload(packageInfoAutoReloadButton, interval, packageInfoAutoReloadInput, _ => {
            if (!hasActiveSelection()) {
                const packageBase = packageInfoModal.dataset.package;
                // we only poll status and logs here
                loadPackage(packageBase);
                loadLogs(packageBase);
            }
        });
    }

    ready(_ => {
        packageInfoEventsTable.bootstrapTable({});

        packageInfoEventsUpdateChart = new Chart(packageInfoEventsUpdateChartCanvas, {
            type: "line",
            data: {},
            options: {
                responsive: true,
            },
        });

        packageInfoModal.addEventListener("hidden.bs.modal", _ => {
            packageInfoAurUrl.textContent = "";
            packageInfoDepends.textContent = "";
            packageInfoGroups.textContent = "";
            packageInfoImplicitlyDepends.textContent = "";
            packageInfoLicenses.textContent = "";
            packageInfoPackager.textContent = "";
            packageInfoPackages.textContent = "";
            packageInfoUpstreamUrl.textContent = "";
            packageInfoVersion.textContent = "";

            packageInfoVariablesBlock.hidden = true;
            packageInfoVariablesDiv.replaceChildren();

            packageInfoLogsInput.textContent = "";
            packageInfoChangesInput.textContent = "";
            packageInfoEventsTable.bootstrapTable("load", []);
            clearChart();

            clearInterval(packageInfoAutoReloadTask);
            packageInfoAutoReloadTask = null; // not really required (?) but lets clear everything
        });

        restoreAutoReloadSettings(packageInfoAutoReloadButton, packageInfoAutoReloadInput);
    });
</script>
