/* Table enhancer: instant search + column sort + client-side pagination.
 *
 * Opt in by wrapping a table:
 *   <div data-table data-page-size="25">
 *     <input data-table-search ...>          (optional search box)
 *     <table> ...sortable <th data-sort="text|num">, cells with data-sort-value... </table>
 *     <nav data-table-pager></nav>           (pager is rendered here)
 *   </div>
 *
 * One controller owns the row set and re-renders on any change in the order
 * filter -> sort -> slice-to-page, so the three behaviours never fight.
 * Dependency-free; rows are identified by [data-name] (the search key).
 *
 * Framework-neutral: `data-table-style="bootstrap|tailwind"` (default bootstrap)
 * switches the sort-icon set and pager markup; `data-table-hidden-class` sets the
 * class used to hide off-page rows (default per style: d-none / hidden).
 */
(function () {
    "use strict";

    // Per-framework chrome. Bootstrap is the default and unchanged from before.
    var STYLES = {
        bootstrap: {
            hidden: "d-none",
            iconNeutral: "fa-solid fa-sort ms-1 text-muted",
            iconAsc: "fa-solid fa-sort-up ms-1",
            iconDesc: "fa-solid fa-sort-down ms-1",
            renderPager: renderBootstrapPager,
        },
        tailwind: {
            hidden: "hidden",
            iconNeutral: "ti ti-arrows-sort ml-1 opacity-40",
            iconAsc: "ti ti-sort-ascending ml-1",
            iconDesc: "ti ti-sort-descending ml-1",
            renderPager: renderTailwindPager,
        },
    };

    function cellKey(row, colIndex, type) {
        var cell = row.children[colIndex];
        if (!cell) return type === "num" ? 0 : "";
        var raw =
            cell.dataset.sortValue !== undefined
                ? cell.dataset.sortValue
                : cell.textContent;
        if (type === "num") {
            var n = parseFloat(String(raw).replace(/[^0-9.\-]/g, ""));
            return isNaN(n) ? 0 : n;
        }
        return String(raw).trim().toLowerCase();
    }

    function countLabel(pageSize, total, start) {
        if (total === 0) return "Showing 0 of 0";
        var end = Math.min(start + pageSize, total);
        return "Showing " + (start + 1) + "–" + end + " of " + total;
    }

    function renderBootstrapPager(state, pageSize, total, pages, start) {
        function pageItem(label, page, opts) {
            opts = opts || {};
            return (
                '<li class="page-item' +
                (opts.active ? " active" : "") +
                (opts.disabled ? " disabled" : "") +
                '"><button type="button" class="page-link" data-page="' +
                page +
                '">' +
                label +
                "</button></li>"
            );
        }
        var html =
            '<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">' +
            '<small class="text-muted">' +
            countLabel(pageSize, total, start) +
            "</small>";
        // Count always shows; the buttons only when there's more than one page.
        if (pages > 1) {
            html += '<ul class="pagination pagination-sm mb-0">';
            html += pageItem("‹", state.page - 1, { disabled: state.page === 1 });
            for (var p = 1; p <= pages; p++) {
                html += pageItem(String(p), p, { active: p === state.page });
            }
            html += pageItem("›", state.page + 1, {
                disabled: state.page === pages,
            });
            html += "</ul>";
        }
        return html + "</div>";
    }

    function renderTailwindPager(state, pageSize, total, pages, start) {
        function pageItem(label, page, opts) {
            opts = opts || {};
            return (
                '<button type="button" class="join-item btn btn-sm' +
                (opts.active ? " btn-primary" : "") +
                (opts.disabled ? " btn-disabled" : "") +
                '" data-page="' +
                page +
                '">' +
                label +
                "</button>"
            );
        }
        var html =
            '<div class="flex justify-between items-center flex-wrap gap-2">' +
            '<span class="text-xs text-base-content/50">' +
            countLabel(pageSize, total, start) +
            "</span>";
        if (pages > 1) {
            html += '<div class="join">';
            html += pageItem("‹", state.page - 1, { disabled: state.page === 1 });
            for (var p = 1; p <= pages; p++) {
                html += pageItem(String(p), p, { active: p === state.page });
            }
            html += pageItem("›", state.page + 1, {
                disabled: state.page === pages,
            });
            html += "</div>";
        }
        return html + "</div>";
    }

    function initTable(root) {
        var table = root.querySelector("table");
        if (!table || !table.tBodies.length) return;
        var body = table.tBodies[0];
        var rows = Array.prototype.slice.call(
            body.querySelectorAll("tr[data-name]")
        );
        if (!rows.length) return; // empty table — nothing to enhance

        var input = root.querySelector("[data-table-search]");
        var pager = root.querySelector("[data-table-pager]");
        var pageSize = parseInt(root.dataset.pageSize, 10) || 25;
        var style = STYLES[root.dataset.tableStyle] || STYLES.bootstrap;
        var hiddenClass = root.dataset.tableHiddenClass || style.hidden;
        var state = { q: "", col: null, type: null, dir: 1, page: 1 };

        var headers = Array.prototype.slice.call(
            table.tHead ? table.tHead.querySelectorAll("th[data-sort]") : []
        );
        headers.forEach(function (th) {
            th.style.cursor = "pointer";
            th.style.userSelect = "none";
            var icon = document.createElement("i");
            icon.className = style.iconNeutral;
            th.appendChild(icon);
            th.__icon = icon;
            // The column matching the server's initial order shows its arrow on load.
            if (th.dataset.sortDefault !== undefined) {
                state.col = th.cellIndex;
                state.type = th.dataset.sort || "text";
                state.dir = th.dataset.sortDefault === "desc" ? -1 : 1;
                icon.className = state.dir === 1 ? style.iconAsc : style.iconDesc;
            }
            th.addEventListener("click", function () {
                if (state.col === th.cellIndex) {
                    state.dir = -state.dir;
                } else {
                    state.col = th.cellIndex;
                    state.type = th.dataset.sort || "text";
                    state.dir = 1;
                }
                state.page = 1;
                headers.forEach(function (h) {
                    h.__icon.className = style.iconNeutral;
                });
                icon.className = state.dir === 1 ? style.iconAsc : style.iconDesc;
                render();
            });
        });

        function render() {
            var matched = rows.filter(function (r) {
                return !state.q || (r.dataset.name || "").indexOf(state.q) !== -1;
            });
            if (state.col !== null) {
                var col = state.col,
                    type = state.type,
                    dir = state.dir;
                matched.sort(function (a, b) {
                    var ka = cellKey(a, col, type),
                        kb = cellKey(b, col, type);
                    return ka < kb ? -dir : ka > kb ? dir : 0;
                });
            }
            var pages = Math.max(1, Math.ceil(matched.length / pageSize));
            if (state.page > pages) state.page = pages;
            var start = (state.page - 1) * pageSize;
            var pageRows = matched.slice(start, start + pageSize);
            var show = new Set(pageRows);

            matched.forEach(function (r) {
                body.appendChild(r);
            });
            rows.forEach(function (r) {
                r.classList.toggle(hiddenClass, !show.has(r));
            });
            if (pager) {
                pager.innerHTML = style.renderPager(
                    state,
                    pageSize,
                    matched.length,
                    pages,
                    start
                );
            }
        }

        if (pager) {
            pager.addEventListener("click", function (e) {
                var btn = e.target.closest("button[data-page]");
                if (!btn) return;
                var p = parseInt(btn.dataset.page, 10);
                if (!isNaN(p)) {
                    state.page = p;
                    render();
                }
            });
        }
        if (input) {
            input.addEventListener("input", function () {
                state.q = input.value.trim().toLowerCase();
                state.page = 1;
                render();
            });
        }
        render();
    }

    document.addEventListener("DOMContentLoaded", function () {
        document.querySelectorAll("[data-table]").forEach(initTable);
    });
})();
