/* CodeDE Table Viewer – Frontend JS
 * Features: bottom scrollbar mirror · live search (Pro) · column sort (Pro) · column filters (Pro) · pagination
 */

(function () {
    'use strict';

    /* ── Bottom scrollbar mirror ────────────────────────────────────────────
     * The native browser scrollbar sits at the bottom of a tall table –
     * far below the viewport.  We create a fixed mirror at the viewport
     * bottom that appears while the table is visible but its native
     * scrollbar is off-screen.
     */
    function initScrollMirror(wrap, scrollEl, table) {
        if (wrap.classList.contains('cdtv-scroll-off')) return;

        var mode = wrap.classList.contains('cdtv-scroll-always') ? 'scroll' : 'auto';

        scrollEl.style.setProperty('overflow-x', mode,   'important');
        scrollEl.style.setProperty('display',    'block', 'important');
        scrollEl.style.setProperty('width',      '100%',  'important');
        table.style.setProperty('width',     'auto', 'important');
        table.style.setProperty('min-width', '100%', 'important');
        table.style.setProperty('max-width', 'none', 'important');

        // Fix any overflow:hidden ancestors
        var el = wrap.parentElement, lvl = 0;
        while (el && el.tagName !== 'BODY' && lvl < 12) {
            var cs = window.getComputedStyle(el);
            if (cs.overflow === 'hidden' || cs.overflowX === 'hidden') {
                el.style.setProperty('overflow-x', 'auto', 'important');
            }
            el = el.parentElement; lvl++;
        }

        var mirror = document.createElement('div');
        mirror.className = 'cdtv-scroll-mirror-bottom';
        var spacer = document.createElement('div');
        spacer.className = 'cdtv-scroll-spacer';
        mirror.appendChild(spacer);
        document.body.appendChild(mirror);

        var overflows = false;

        function syncWidth() {
            var tableW = table.scrollWidth;
            var wrapW  = scrollEl.clientWidth || wrap.clientWidth;
            overflows  = tableW > wrapW && wrapW > 0;
            spacer.style.width = tableW + 'px';
            if (!overflows) mirror.style.display = 'none';
        }

        function updateMirror() {
            if (!overflows) { mirror.style.display = 'none'; return; }
            var rect  = scrollEl.getBoundingClientRect();
            var viewH = window.innerHeight;
            if (rect.top < viewH && rect.bottom > viewH && rect.width > 0) {
                mirror.style.left    = rect.left + 'px';
                mirror.style.width   = rect.width + 'px';
                mirror.style.display = 'block';
                mirror.scrollLeft    = scrollEl.scrollLeft;
            } else {
                mirror.style.display = 'none';
            }
        }

        var syncing = false;
        mirror.addEventListener('scroll', function () {
            if (syncing) return; syncing = true;
            scrollEl.scrollLeft = mirror.scrollLeft;
            syncing = false;
        });
        scrollEl.addEventListener('scroll', function () {
            if (syncing) return; syncing = true;
            mirror.scrollLeft = scrollEl.scrollLeft;
            syncing = false;
        });

        window.addEventListener('scroll', updateMirror, { passive: true });
        window.addEventListener('resize', function () { syncWidth(); updateMirror(); });

        syncWidth();
        requestAnimationFrame(function () { syncWidth(); updateMirror(); });
        window.addEventListener('load', function () { syncWidth(); updateMirror(); });

        if (window.ResizeObserver) {
            new ResizeObserver(function () { syncWidth(); updateMirror(); }).observe(table);
        }
    }

    /* ── Table: Search + Sort + Pagination + Column Filters ─────────────────
     * All features share a single row list so filtering, sorting and
     * paging always interact correctly.
     */
    function initTable(wrap) {
        var scrollEl = wrap.querySelector('.cdtv-table-scroll');
        var table    = wrap.querySelector('table');
        if (!scrollEl || !table) return;

        // Start scroll mirror
        initScrollMirror(wrap, scrollEl, table);

        var tbody = wrap.querySelector('.cdtv-table tbody');
        if (!tbody) return;

        // Collect all body rows (original DOM order)
        var allRows = Array.prototype.slice.call(tbody.querySelectorAll('tr'));
        if (!allRows.length) return;

        var rpp             = parseInt(wrap.getAttribute('data-rows-per-page'), 10) || 0;
        var hasSearch       = wrap.hasAttribute('data-search');
        var hasSort         = wrap.hasAttribute('data-sort');
        var filterColsAttr  = wrap.getAttribute('data-filter-cols') || '';
        var filterColIndices = filterColsAttr ? filterColsAttr.split(',').map(function(s) { return parseInt(s, 10); }) : [];

        // State
        var searchQuery  = '';
        var sortState    = null; // { col, dir }
        var colFilters   = {}; // colIdx -> Set of values, or null for no filter on this column
        var currentPage  = 1;
        var totalPages   = 1;

        // Pagination DOM refs (built below if needed)
        var prevBtn, nextBtn, pageInfo;

        /* ── 1. Search field ──────────────────────────────────────────────── */
        if (hasSearch) {
            var searchWrap = document.createElement('div');
            searchWrap.className = 'cdtv-search-wrap';

            var searchInput = document.createElement('input');
            searchInput.type        = 'search';
            searchInput.className   = 'cdtv-search-input';
            searchInput.placeholder = 'Search…';
            searchInput.setAttribute('aria-label', 'Search table');
            searchWrap.appendChild(searchInput);

            // Insert above the scroll container
            wrap.insertBefore(searchWrap, scrollEl);

            searchInput.addEventListener('input', function () {
                applySearch(searchInput.value);
            });
        }

        /* ── 2. Sort headers ──────────────────────────────────────────────── */
        if (hasSort) {
            var thead = wrap.querySelector('.cdtv-table thead tr');
            if (thead) {
                var ths = Array.prototype.slice.call(thead.querySelectorAll('th'));
                ths.forEach(function (th, colIdx) {
                    th.classList.add('cdtv-sortable');

                    var indicator = document.createElement('span');
                    indicator.className   = 'cdtv-sort-indicator';
                    indicator.setAttribute('aria-hidden', 'true');
                    // Two separate spans so the badge never changes width.
                    // CSS dims the inactive direction; the active one stays fully opaque.
                    indicator.innerHTML = '<span class="cdtv-si-asc">&#9650;</span><span class="cdtv-si-desc">&#9660;</span>';
                    th.appendChild(indicator);

                    th.addEventListener('click', function () {
                        var prev = th.getAttribute('data-sort-dir') || '';
                        var dir  = prev === 'asc' ? 'desc' : 'asc';

                        // Reset all columns
                        ths.forEach(function (other) {
                            other.removeAttribute('data-sort-dir');
                            other.classList.remove('cdtv-sorted-asc', 'cdtv-sorted-desc');
                        });

                        // Set this column (CSS handles opacity via class on th)
                        th.setAttribute('data-sort-dir', dir);
                        th.classList.add(dir === 'asc' ? 'cdtv-sorted-asc' : 'cdtv-sorted-desc');

                        sortRows(colIdx, dir);
                    });
                });
            }
        }

        /* ── 3. Column filters ────────────────────────────────────────────── */
        if (filterColIndices.length > 0) {
            var thead = wrap.querySelector('.cdtv-table thead tr');
            if (thead) {
                var ths = Array.prototype.slice.call(thead.querySelectorAll('th'));
                filterColIndices.forEach(function (colIdx) {
                    if (!ths[colIdx]) return;
                    var th = ths[colIdx];

                    // Add filter indicator to header.
                    // Using <span role="button"> instead of <button> so that
                    // theme CSS button resets (display:none etc.) cannot hide it.
                    var filterBtn = document.createElement('span');
                    filterBtn.setAttribute('role', 'button');
                    filterBtn.setAttribute('tabindex', '0');
                    filterBtn.className = 'cdtv-col-filter-btn';
                    filterBtn.setAttribute('aria-label', 'Filter');
                    filterBtn.textContent = '▼';
                    th.appendChild(filterBtn);

                    filterBtn.addEventListener('click', function (e) {
                        e.stopPropagation();
                        openFilterDropdown(colIdx, th);
                    });
                    filterBtn.addEventListener('keydown', function (e) {
                        if (e.key === 'Enter' || e.key === ' ') {
                            e.preventDefault();
                            e.stopPropagation();
                            openFilterDropdown(colIdx, th);
                        }
                    });
                });
            }
        }

        /* ── 4. Pagination bar ────────────────────────────────────────────── */
        if (rpp > 0 && allRows.length > rpp) {
            var bar = document.createElement('div');
            bar.className = 'cdtv-pagination';

            prevBtn = document.createElement('button');
            prevBtn.type      = 'button';
            prevBtn.className = 'cdtv-page-btn cdtv-page-prev';
            prevBtn.innerHTML = '&#8592;';
            prevBtn.setAttribute('aria-label', 'Previous page');

            pageInfo = document.createElement('span');
            pageInfo.className = 'cdtv-page-info';

            nextBtn = document.createElement('button');
            nextBtn.type      = 'button';
            nextBtn.className = 'cdtv-page-btn cdtv-page-next';
            nextBtn.innerHTML = '&#8594;';
            nextBtn.setAttribute('aria-label', 'Next page');

            bar.appendChild(prevBtn);
            bar.appendChild(pageInfo);
            bar.appendChild(nextBtn);
            wrap.appendChild(bar);

            prevBtn.addEventListener('click', function () {
                goToPage(currentPage - 1);
                wrap.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
            });
            nextBtn.addEventListener('click', function () {
                goToPage(currentPage + 1);
                wrap.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
            });
        }

        /* ── Core logic ───────────────────────────────────────────────────── */

        function computeFiltered() {
            var filtered = allRows.filter(function (row) {
                // Apply search query
                if (searchQuery) {
                    if (row.textContent.toLowerCase().indexOf(searchQuery) === -1) {
                        return false;
                    }
                }

                // Apply column filters (AND logic)
                for (var colIdx in colFilters) {
                    if (colFilters.hasOwnProperty(colIdx)) {
                        var filterSet = colFilters[colIdx];
                        if (filterSet !== null) { // null = no filter on this column
                            var cell = row.cells[colIdx];
                            var cellVal = cell ? cell.textContent.trim() : '';
                            if (!filterSet.has(cellVal)) {
                                return false;
                            }
                        }
                    }
                }

                return true;
            });
            return filtered;
        }

        function applySearch(query) {
            searchQuery = query.toLowerCase().trim();
            currentPage = 1;
            render();
        }

        function sortRows(colIdx, dir) {
            sortState = { col: colIdx, dir: dir };
            var mult = dir === 'asc' ? 1 : -1;
            function cmp(a, b) {
                var av = a.cells[colIdx] ? a.cells[colIdx].textContent.trim() : '';
                var bv = b.cells[colIdx] ? b.cells[colIdx].textContent.trim() : '';
                var an = parseFloat(av.replace(/[^\d.-]/g, ''));
                var bn = parseFloat(bv.replace(/[^\d.-]/g, ''));
                if (!isNaN(an) && !isNaN(bn)) return (an - bn) * mult;
                return av.localeCompare(bv) * mult;
            }
            allRows.sort(cmp);
            currentPage = 1;
            render();
        }

        function goToPage(page) {
            var filtered = computeFiltered();
            totalPages  = rpp > 0 ? Math.max(1, Math.ceil(filtered.length / rpp)) : 1;
            currentPage = Math.max(1, Math.min(page, totalPages));
            render();
        }

        function render() {
            var filtered = computeFiltered();
            totalPages = rpp > 0 ? Math.max(1, Math.ceil(filtered.length / rpp)) : 1;
            var start  = rpp > 0 ? (currentPage - 1) * rpp : 0;
            var end    = rpp > 0 ? start + rpp : filtered.length;

            // Reorder DOM to match current order, then apply visibility
            filtered.forEach(function (row, idx) {
                tbody.appendChild(row); // moves to end → maintains order
                row.style.display = (idx >= start && idx < end) ? '' : 'none';
            });
            // Ensure rows NOT in filtered are hidden
            allRows.forEach(function (row) {
                if (filtered.indexOf(row) === -1) row.style.display = 'none';
            });

            // Update pagination UI
            if (pageInfo) {
                if (filtered.length === 0) {
                    pageInfo.textContent = '0 / 0';
                    prevBtn.disabled = true;
                    nextBtn.disabled = true;
                } else {
                    var from = start + 1;
                    var to   = Math.min(end, filtered.length);
                    pageInfo.textContent = from + '\u2013' + to + ' / ' + filtered.length;
                    prevBtn.disabled = (currentPage === 1);
                    nextBtn.disabled = (currentPage === totalPages);
                }
            }
        }

        function openFilterDropdown(colIdx, th) {
            // Close any open dropdown (appended to body)
            var openDrop = document.querySelector('.cdtv-col-filter-dropdown.open');
            if (openDrop) openDrop.remove();

            // Collect unique values from this column across all rows
            var valueSet = {};
            allRows.forEach(function (row) {
                var cell = row.cells[colIdx];
                if (cell) {
                    var val = cell.textContent.trim();
                    if (val) valueSet[val] = true;
                }
            });
            var sortedValues = Object.keys(valueSet).sort();

            // Build dropdown
            var dropdown = document.createElement('div');
            dropdown.className = 'cdtv-col-filter-dropdown open';

            // Search input
            var dropSearch = document.createElement('input');
            dropSearch.type = 'text';
            dropSearch.className = 'cdtv-col-filter-search';
            dropSearch.placeholder = 'Search…';
            dropdown.appendChild(dropSearch);

            // Select All row
            var selectAllLabel = document.createElement('label');
            selectAllLabel.className = 'cdtv-col-filter-select-all';
            var selectAllCb = document.createElement('input');
            selectAllCb.type = 'checkbox';
            selectAllCb.checked = true;
            selectAllLabel.appendChild(selectAllCb);
            selectAllLabel.appendChild(document.createTextNode(' ☑ Select All'));
            dropdown.appendChild(selectAllLabel);

            // Value list
            var listDiv = document.createElement('div');
            listDiv.className = 'cdtv-col-filter-list';

            var checkboxes = [];
            sortedValues.forEach(function (val) {
                var item = document.createElement('label');
                item.className = 'cdtv-col-filter-item';
                var cb = document.createElement('input');
                cb.type = 'checkbox';
                cb.value = val;
                var currentFilter = colFilters[colIdx];
                if (currentFilter === null || currentFilter === undefined || currentFilter.has(val)) {
                    cb.checked = true;
                }
                item.appendChild(cb);
                item.appendChild(document.createTextNode(val));
                checkboxes.push(cb);
                listDiv.appendChild(item);
            });
            dropdown.appendChild(listDiv);

            // OK / Cancel buttons
            var actionsDiv = document.createElement('div');
            actionsDiv.className = 'cdtv-col-filter-actions';
            var okBtn = document.createElement('button');
            okBtn.type = 'button';
            okBtn.className = 'cdtv-filter-ok';
            okBtn.textContent = 'OK';
            var cancelBtn = document.createElement('button');
            cancelBtn.type = 'button';
            cancelBtn.textContent = 'Cancel';
            actionsDiv.appendChild(okBtn);
            actionsDiv.appendChild(cancelBtn);
            dropdown.appendChild(actionsDiv);

            /* ── Position dropdown using getBoundingClientRect so it escapes
               any overflow:hidden ancestor (common in themes).               ── */
            document.body.appendChild(dropdown);
            var rect = th.getBoundingClientRect();
            var scrollY = window.pageYOffset || document.documentElement.scrollTop;
            var scrollX = window.pageXOffset || document.documentElement.scrollLeft;
            dropdown.style.position = 'fixed';
            dropdown.style.top  = (rect.bottom) + 'px';
            dropdown.style.left = (rect.left)   + 'px';

            // Keep in viewport horizontally
            requestAnimationFrame(function () {
                var dw = dropdown.offsetWidth;
                var vw = window.innerWidth;
                if (rect.left + dw > vw) {
                    dropdown.style.left = Math.max(0, vw - dw - 8) + 'px';
                }
            });

            // Inline search
            dropSearch.addEventListener('input', function () {
                var q = dropSearch.value.toLowerCase();
                Array.prototype.forEach.call(listDiv.querySelectorAll('.cdtv-col-filter-item'), function (item) {
                    item.style.display = item.textContent.toLowerCase().indexOf(q) !== -1 ? '' : 'none';
                });
            });

            // Select All
            selectAllCb.addEventListener('change', function () {
                var checked = selectAllCb.checked;
                checkboxes.forEach(function (cb) {
                    if (cb.parentElement.style.display !== 'none') cb.checked = checked;
                });
            });

            // OK
            okBtn.addEventListener('click', function () {
                var checkedValues = [];
                checkboxes.forEach(function (cb) {
                    if (cb.checked && cb.parentElement.style.display !== 'none') {
                        checkedValues.push(cb.value);
                    }
                });
                if (checkedValues.length === sortedValues.length) {
                    colFilters[colIdx] = null;
                    th.classList.remove('cdtv-col-filter-active');
                } else {
                    colFilters[colIdx] = new Set(checkedValues);
                    th.classList.add('cdtv-col-filter-active');
                }
                dropdown.remove();
                currentPage = 1;
                render();
            });

            // Cancel
            cancelBtn.addEventListener('click', function () { dropdown.remove(); });

            // Close on outside click
            function closeDropdown(e) {
                if (!dropdown.contains(e.target) && !th.contains(e.target)) {
                    dropdown.remove();
                    document.removeEventListener('click', closeDropdown);
                }
            }
            setTimeout(function () { document.addEventListener('click', closeDropdown); }, 0);
        }

        /* ── Initial render ───────────────────────────────────────────────── */
        render();
    }

    /* ── Init all tables ─────────────────────────────────────────────────── */

    function init() {
        var wraps = document.querySelectorAll('.cdtv-table-wrap');
        for (var i = 0; i < wraps.length; i++) {
            initTable(wraps[i]);
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
}());
