(function (global) {
    'use strict';

    if (global.IcyBillingTable) return;

    const allowedSizes = new Set(['10', '25', '50', 'all']);
    const escapeHtml = (value) => String(value == null ? '' : value).replace(
        /[&<>"']/g,
        (char) => ({'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'}[char])
    );

    function createPaging(defaultSize) {
        const normalized = allowedSizes.has(String(defaultSize)) ? String(defaultSize) : '10';
        return {
            defaultSize: normalized,
            sizes: Object.create(null),
            pages: Object.create(null),
            sorts: Object.create(null)
        };
    }

    function resetPaging(paging) {
        if (!paging) return;
        paging.pages = Object.create(null);
    }

    function pageFor(rows, paging, key) {
        const safeRows = Array.isArray(rows) ? rows : [];
        const groupKey = String(key || 'default');
        const sizeValue = allowedSizes.has(String(paging.sizes[groupKey]))
            ? String(paging.sizes[groupKey])
            : paging.defaultSize;
        const perPage = sizeValue === 'all' ? Math.max(1, safeRows.length) : Number(sizeValue);
        const pageCount = sizeValue === 'all' ? 1 : Math.max(1, Math.ceil(safeRows.length / perPage));
        const requestedPage = Math.max(1, Number(paging.pages[groupKey] || 1));
        const page = Math.min(requestedPage, pageCount);
        paging.pages[groupKey] = page;
        paging.sizes[groupKey] = sizeValue;
        const startIndex = safeRows.length ? (page - 1) * perPage : 0;
        const endIndex = sizeValue === 'all' ? safeRows.length : Math.min(startIndex + perPage, safeRows.length);

        return {
            key: groupKey,
            rows: safeRows.slice(startIndex, endIndex),
            total: safeRows.length,
            page,
            pageCount,
            sizeValue,
            start: safeRows.length ? startIndex + 1 : 0,
            end: endIndex
        };
    }

    function setPageSize(paging, key, value) {
        const groupKey = String(key || 'default');
        paging.sizes[groupKey] = allowedSizes.has(String(value)) ? String(value) : paging.defaultSize;
        paging.pages[groupKey] = 1;
    }

    function movePage(paging, key, direction, totalRows) {
        const groupKey = String(key || 'default');
        const sizeValue = allowedSizes.has(String(paging.sizes[groupKey]))
            ? String(paging.sizes[groupKey])
            : paging.defaultSize;
        const perPage = sizeValue === 'all' ? Math.max(1, Number(totalRows || 0)) : Number(sizeValue);
        const pageCount = sizeValue === 'all' ? 1 : Math.max(1, Math.ceil(Number(totalRows || 0) / perPage));
        const current = Math.max(1, Number(paging.pages[groupKey] || 1));
        paging.pages[groupKey] = Math.min(pageCount, Math.max(1, current + (direction === 'next' ? 1 : -1)));
    }

    function toggleSort(paging, key, column, type) {
        const groupKey = String(key || 'default');
        const current = paging.sorts[groupKey] || null;
        const firstDirection = type === 'number' || type === 'date' ? 'descending' : 'ascending';
        paging.sorts[groupKey] = {
            column: String(column || ''),
            type: String(type || 'text'),
            direction: current && current.column === String(column || '')
                ? (current.direction === 'ascending' ? 'descending' : 'ascending')
                : firstDirection
        };
        paging.pages[groupKey] = 1;
        return paging.sorts[groupKey];
    }

    function sortState(paging, key) {
        return paging.sorts[String(key || 'default')] || null;
    }

    function sortedRows(rows, paging, key, valueFor) {
        const safeRows = Array.isArray(rows) ? rows.slice() : [];
        const sort = sortState(paging, key);
        if (!sort || typeof valueFor !== 'function') return safeRows;
        const collator = new Intl.Collator('es', {numeric: true, sensitivity: 'base'});
        const normalized = safeRows.map((row, index) => ({
            row,
            index,
            value: valueFor(row, sort.column, sort.type)
        }));
        normalized.sort((left, right) => {
            const a = left.value;
            const b = right.value;
            const aEmpty = a === null || a === '' || typeof a === 'undefined';
            const bEmpty = b === null || b === '' || typeof b === 'undefined';
            if (aEmpty && bEmpty) return left.index - right.index;
            if (aEmpty) return 1;
            if (bEmpty) return -1;
            let result;
            if (sort.type === 'number') result = Number(a) - Number(b);
            else if (sort.type === 'date') result = String(a).localeCompare(String(b));
            else result = collator.compare(String(a), String(b));
            if (sort.direction === 'descending') result *= -1;
            return result || (left.index - right.index);
        });
        return normalized.map((entry) => entry.row);
    }

    function controls(meta, label) {
        const key = escapeHtml(meta.key);
        const accessibleLabel = escapeHtml(label || 'documentos');
        const sizeOptions = [
            ['10', '10'],
            ['25', '25'],
            ['50', '50'],
            ['all', 'Todos']
        ].map(([value, text]) => `<option value="${value}"${meta.sizeValue === value ? ' selected' : ''}>${text}</option>`).join('');

        return `<div class="billing-reference-table-controls">
            <label class="billing-reference-page-size">Mostrar
                <select data-billing-page-size="${key}" aria-label="Cantidad de registros de ${accessibleLabel}">${sizeOptions}</select>
                registros
            </label>
            <div class="billing-reference-pagination" aria-label="Paginación de ${accessibleLabel}">
                <span>${meta.start}-${meta.end} de ${meta.total}</span>
                <button type="button" data-billing-page="${key}" data-direction="prev"${meta.page <= 1 ? ' disabled' : ''}>Anterior</button>
                <button type="button" data-billing-page="${key}" data-direction="next"${meta.page >= meta.pageCount ? ' disabled' : ''}>Siguiente</button>
            </div>
        </div>`;
    }

    function positionOpenMenu(details) {
        if (!details || !details.open) return;
        const trigger = details.querySelector('summary');
        const panel = details.querySelector('.billing-row-actions__panel');
        if (!trigger || !panel) return;
        const rect = trigger.getBoundingClientRect();
        panel.style.right = `${Math.max(8, global.innerWidth - rect.right)}px`;
        panel.style.left = 'auto';
        panel.style.top = `${rect.bottom + 6}px`;
        global.requestAnimationFrame(() => {
            const panelRect = panel.getBoundingClientRect();
            if (panelRect.bottom > global.innerHeight - 8) {
                panel.style.top = `${Math.max(8, rect.top - panelRect.height - 6)}px`;
            }
        });
    }

    function closeRowMenus(except) {
        document.querySelectorAll('details[data-billing-row-actions][open]').forEach((details) => {
            if (details !== except) details.open = false;
        });
    }

    if (!global.__icyBillingRowMenusBound) {
        global.__icyBillingRowMenusBound = true;
        document.addEventListener('toggle', (event) => {
            const details = event.target;
            if (!(details instanceof HTMLDetailsElement) || !details.hasAttribute('data-billing-row-actions') || !details.open) return;
            closeRowMenus(details);
            positionOpenMenu(details);
        }, true);
        document.addEventListener('click', (event) => {
            const sortButton = event.target.closest('.billing-reference-table .icy-table-sort-button[data-billing-sort-key]');
            if (sortButton) {
                const table = sortButton.closest('table[data-billing-group]');
                if (table) {
                    event.preventDefault();
                    event.stopImmediatePropagation();
                    table.dispatchEvent(new CustomEvent('billing-reference-sort', {
                        bubbles: true,
                        detail: {
                            group: table.getAttribute('data-billing-group') || 'default',
                            column: sortButton.getAttribute('data-billing-sort-key') || '',
                            type: sortButton.getAttribute('data-sort-type') || 'text'
                        }
                    }));
                }
                return;
            }
            const action = event.target.closest('details[data-billing-row-actions] .billing-row-actions__panel button, details[data-billing-row-actions] .billing-row-actions__panel a');
            if (action) {
                const details = action.closest('details[data-billing-row-actions]');
                if (details) details.open = false;
            }
        });
        document.addEventListener('scroll', () => closeRowMenus(), true);
        global.addEventListener('resize', () => closeRowMenus());
    }

    global.IcyBillingTable = Object.freeze({
        createPaging,
        resetPaging,
        pageFor,
        setPageSize,
        movePage,
        toggleSort,
        sortState,
        sortedRows,
        controls
    });
})(window);
