/*
 * whp-table.js — reusable, accessible table enhancer for the WHP panel.
 *
 * Add `data-whp-table` to any <table> with a <thead> + <tbody> to get:
 *   - Column sort: click a header to sort (type-aware — byte sizes like "3.6 GB",
 *     numbers, percents, ISO dates, else natural string compare). aria-sort is kept
 *     in sync. Opt a column out with `data-whp-nosort` on its <th>.
 *   - Search: a labelled search box above the table filters rows by text.
 *     Disable with `data-whp-search="off"`.
 *   - Pagination: page size from `data-whp-page-size` (default 25; 0 disables).
 *     Renders a labelled pager + "Showing X–Y of Z" below the table.
 *
 * Per-cell override: put `data-sort="<value>"` on a <td> to control its sort key
 * (e.g. raw bytes behind a human-readable size). Numeric values sort numerically.
 *
 * Works in the browser (auto-init on DOMContentLoaded) and exports its pure helpers
 * for Node unit tests.
 */
(function (root, factory) {
    var api = factory();
    if (typeof module !== 'undefined' && module.exports) { module.exports = api; }
    if (root && root.document) {
        root.WhpTable = api;
        root.document.addEventListener('DOMContentLoaded', function () {
            root.document.querySelectorAll('table[data-whp-table]').forEach(api.enhance);
        });
    }
})(typeof window !== 'undefined' ? window : this, function () {
    'use strict';

    // ---- pure helpers (unit-tested) ------------------------------------ //

    // "1.2 GB" / "320 B" / "1 KiB" -> bytes (number), else null.
    function parseBytes(text) {
        var m = String(text).trim().match(/^([\d.,]+)\s*([KMGTP]i?B|B)$/i);
        if (!m) { return null; }
        var n = parseFloat(m[1].replace(/,/g, ''));
        if (isNaN(n)) { return null; }
        var unit = m[2].toUpperCase().replace('I', '');
        var mult = { B: 1, KB: 1e3, MB: 1e6, GB: 1e9, TB: 1e12, PB: 1e15 }[unit] || 1;
        return n * mult;
    }

    // Compare two sort keys. Numbers sort numerically and ahead of strings;
    // strings use locale natural (numeric-aware, case-insensitive) compare.
    function compareValues(a, b) {
        var na = typeof a === 'number', nb = typeof b === 'number';
        if (na && nb) { return a - b; }
        if (na) { return -1; }
        if (nb) { return 1; }
        return String(a).localeCompare(String(b), undefined, { numeric: true, sensitivity: 'base' });
    }

    // Derive a sort key for a cell: explicit data-sort wins, then bytes, then
    // number/percent, then ISO date, else lowercased string.
    function cellSortKey(td) {
        var ds = td.dataset ? td.dataset.sort : undefined;
        if (ds !== undefined && ds !== '') {
            var dn = parseFloat(ds);
            return (!isNaN(dn) && /^[-+]?[\d.,]+$/.test(ds)) ? dn : String(ds).toLowerCase();
        }
        var t = (td.textContent || '').trim();
        var b = parseBytes(t);
        if (b !== null) { return b; }
        if (/^[-+]?[\d,]*\.?\d+%?$/.test(t)) {
            var num = parseFloat(t.replace(/[,%]/g, ''));
            if (!isNaN(num)) { return num; }
        }
        if (/^\d{4}-\d{2}-\d{2}([ T]\d{2}:\d{2})?/.test(t)) {
            var ms = Date.parse(t);
            if (!isNaN(ms)) { return ms; }
        }
        return t.toLowerCase();
    }

    // ---- DOM enhancement (browser only) -------------------------------- //

    function enhance(table) {
        if (!table || table.__whpEnhanced) { return; }
        var tbody = table.tBodies[0], thead = table.tHead;
        if (!tbody || !thead || !thead.rows.length) { return; }
        table.__whpEnhanced = true;

        var headRow = thead.rows[thead.rows.length - 1];
        var allRows = Array.prototype.slice.call(tbody.rows);
        var pageSize = parseInt(table.getAttribute('data-whp-page-size'), 10);
        if (isNaN(pageSize)) { pageSize = 25; }
        var searchOn = table.getAttribute('data-whp-search') !== 'off';

        var state = { filter: '', sortIdx: -1, sortDir: 1, page: 1 };

        // ---- controls: search box (above) + status/pager (below) ----
        var host = table.closest('.table-responsive') || table;
        var topBar = document.createElement('div');
        topBar.className = 'd-flex justify-content-between align-items-center flex-wrap gap-2 mb-2';
        var tableId = table.id || ('whp-table-' + Math.floor(performance.now()));
        if (!table.id) { table.id = tableId; }

        if (searchOn) {
            var label = document.createElement('label');
            label.className = 'visually-hidden';
            label.setAttribute('for', tableId + '-search');
            label.textContent = 'Search table';
            var box = document.createElement('input');
            box.type = 'search';
            box.id = tableId + '-search';
            box.className = 'form-control form-control-sm';
            box.style.maxWidth = '280px';
            box.placeholder = 'Search…';
            box.setAttribute('aria-controls', tableId);
            box.addEventListener('input', function () {
                state.filter = box.value.trim().toLowerCase();
                state.page = 1;
                render();
            });
            topBar.appendChild(label);
            topBar.appendChild(box);
        } else {
            topBar.appendChild(document.createElement('span'));
        }
        host.parentNode.insertBefore(topBar, host);

        var bottomBar = document.createElement('div');
        bottomBar.className = 'd-flex justify-content-between align-items-center flex-wrap gap-2 mt-2';
        var status = document.createElement('div');
        status.className = 'small text-muted';
        status.setAttribute('aria-live', 'polite');
        var pager = document.createElement('nav');
        pager.setAttribute('aria-label', 'Table pagination');
        bottomBar.appendChild(status);
        bottomBar.appendChild(pager);
        host.parentNode.insertBefore(bottomBar, host.nextSibling);

        // ---- sortable headers ----
        Array.prototype.forEach.call(headRow.cells, function (th, idx) {
            if (th.hasAttribute('data-whp-nosort') || th.textContent.trim() === '') { return; }
            th.style.cursor = 'pointer';
            th.setAttribute('aria-sort', 'none');
            th.tabIndex = 0;
            th.setAttribute('role', 'button');
            var arrow = document.createElement('span');
            arrow.className = 'whp-sort-arrow ms-1 text-muted';
            arrow.setAttribute('aria-hidden', 'true');
            arrow.textContent = '↕';
            th.appendChild(arrow);
            function doSort() {
                state.sortDir = (state.sortIdx === idx) ? -state.sortDir : 1;
                state.sortIdx = idx;
                state.page = 1;
                Array.prototype.forEach.call(headRow.cells, function (h) {
                    h.setAttribute('aria-sort', 'none');
                    var a = h.querySelector('.whp-sort-arrow');
                    if (a) { a.textContent = '↕'; a.classList.add('text-muted'); }
                });
                th.setAttribute('aria-sort', state.sortDir > 0 ? 'ascending' : 'descending');
                arrow.textContent = state.sortDir > 0 ? '▲' : '▼';
                arrow.classList.remove('text-muted');
                render();
            }
            th.addEventListener('click', doSort);
            th.addEventListener('keydown', function (e) {
                if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); doSort(); }
            });
        });

        // ---- render pipeline: filter -> sort -> paginate ----
        function currentRows() {
            var rows = allRows;
            if (state.filter) {
                rows = rows.filter(function (r) {
                    return r.textContent.toLowerCase().indexOf(state.filter) !== -1;
                });
            }
            if (state.sortIdx >= 0) {
                rows = rows.slice().sort(function (a, b) {
                    var ca = a.cells[state.sortIdx], cb = b.cells[state.sortIdx];
                    if (!ca || !cb) { return 0; }
                    return state.sortDir * compareValues(cellSortKey(ca), cellSortKey(cb));
                });
            }
            return rows;
        }

        function render() {
            var rows = currentRows();
            var total = rows.length;
            var paginate = pageSize > 0 && total > pageSize;
            var pages = paginate ? Math.ceil(total / pageSize) : 1;
            if (state.page > pages) { state.page = pages; }
            if (state.page < 1) { state.page = 1; }
            var start = paginate ? (state.page - 1) * pageSize : 0;
            var end = paginate ? Math.min(start + pageSize, total) : total;

            // Re-attach rows in computed order; show only the current page slice.
            allRows.forEach(function (r) { r.style.display = 'none'; });
            rows.forEach(function (r, i) {
                tbody.appendChild(r); // reorder
                r.style.display = (i >= start && i < end) ? '' : 'none';
            });

            status.textContent = total === 0
                ? 'No matching rows'
                : 'Showing ' + (start + 1) + '–' + end + ' of ' + total;
            renderPager(pages);
        }

        function renderPager(pages) {
            pager.innerHTML = '';
            if (pages <= 1) { return; }
            var ul = document.createElement('ul');
            ul.className = 'pagination pagination-sm mb-0';
            function pageItem(label, target, opts) {
                opts = opts || {};
                var li = document.createElement('li');
                li.className = 'page-item' + (opts.disabled ? ' disabled' : '') + (opts.active ? ' active' : '');
                var a = document.createElement(opts.disabled ? 'span' : 'a');
                a.className = 'page-link';
                a.textContent = label;
                if (opts.ariaLabel) { a.setAttribute('aria-label', opts.ariaLabel); }
                if (opts.active) { a.setAttribute('aria-current', 'page'); }
                if (!opts.disabled) {
                    a.href = '#';
                    a.addEventListener('click', function (e) { e.preventDefault(); state.page = target; render(); });
                }
                li.appendChild(a);
                ul.appendChild(li);
            }
            pageItem('‹', state.page - 1, { disabled: state.page === 1, ariaLabel: 'Previous page' });
            // Windowed page numbers (max ~7) so long lists don't explode the pager.
            var win = 2, lo = Math.max(1, state.page - win), hi = Math.min(pages, state.page + win);
            if (lo > 1) { pageItem('1', 1, {}); if (lo > 2) { pageItem('…', 0, { disabled: true }); } }
            for (var p = lo; p <= hi; p++) { pageItem(String(p), p, { active: p === state.page, ariaLabel: 'Page ' + p }); }
            if (hi < pages) { if (hi < pages - 1) { pageItem('…', 0, { disabled: true }); } pageItem(String(pages), pages, {}); }
            pageItem('›', state.page + 1, { disabled: state.page === pages, ariaLabel: 'Next page' });
            pager.appendChild(ul);
        }

        render();
    }

    return { parseBytes: parseBytes, compareValues: compareValues, cellSortKey: cellSortKey, enhance: enhance };
});
