(() => {
    'use strict';

    const norm = (value) => (value ?? '').toString().normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();

    class UnifiedList {
        constructor(options) {
            this.table = document.querySelector(options.table);
            if (!this.table) return;
            this.rows = [...this.table.querySelectorAll('tbody tr[data-af-row]')];
            this.search = document.querySelector(options.search);
            this.perPage = document.querySelector(options.perPage);
            this.filters = (options.filters || []).map((cfg) => ({...cfg, element: document.querySelector(cfg.selector)}));
            this.summary = document.querySelector(options.summary);
            this.pagination = document.querySelector(options.pagination);
            this.empty = document.querySelector(options.empty);
            this.currentPage = 1;
            this.bind();
            this.render();
        }

        bind() {
            const resetPage = () => { this.currentPage = 1; this.render(); };
            this.search?.addEventListener('input', resetPage);
            this.perPage?.addEventListener('change', resetPage);
            this.filters.forEach((f) => f.element?.addEventListener('change', resetPage));
        }

        matches(row) {
            const query = norm(this.search?.value);
            if (query && !norm(row.dataset.search || row.textContent).includes(query)) return false;
            return this.filters.every((filter) => {
                const selected = filter.element?.value ?? '';
                if (selected === '') return true;
                const actual = row.dataset[filter.data] ?? '';
                return filter.mode === 'contains' ? norm(actual).includes(norm(selected)) : String(actual) === String(selected);
            });
        }

        render() {
            const filtered = this.rows.filter((row) => this.matches(row));
            const raw = this.perPage?.value || '100';
            const limit = raw === 'all' ? Math.max(1, filtered.length) : Math.max(1, parseInt(raw, 10) || 100);
            const pages = Math.max(1, Math.ceil(filtered.length / limit));
            this.currentPage = Math.min(this.currentPage, pages);
            const start = (this.currentPage - 1) * limit;
            const end = Math.min(start + limit, filtered.length);
            this.rows.forEach((row) => { row.hidden = true; });
            filtered.slice(start, end).forEach((row) => { row.hidden = false; });

            if (this.summary) {
                this.summary.textContent = filtered.length
                    ? `Mostrando ${start + 1}–${end} de ${filtered.length} registros`
                    : 'No hay registros que coincidan con los filtros';
            }
            if (this.empty) this.empty.hidden = filtered.length !== 0;
            this.drawPagination(pages);
            this.table.dispatchEvent(new CustomEvent('af:list-rendered', {
                bubbles: true,
                detail: {filtered: filtered.length, start, end, pages, currentPage: this.currentPage}
            }));
        }

        drawPagination(pages) {
            if (!this.pagination) return;
            this.pagination.innerHTML = '';
            const add = (label, page, disabled = false, active = false) => {
                const button = document.createElement('button');
                button.type = 'button';
                button.className = `af-page-btn${active ? ' active' : ''}`;
                button.textContent = label;
                button.disabled = disabled;
                button.addEventListener('click', () => { this.currentPage = page; this.render(); });
                this.pagination.appendChild(button);
            };
            if (pages <= 1) {
                add('1', 1, false, true);
                return;
            }
            add('‹', Math.max(1, this.currentPage - 1), this.currentPage === 1);
            let from = Math.max(1, this.currentPage - 2);
            let to = Math.min(pages, this.currentPage + 2);
            if (from > 1) { add('1', 1, false, this.currentPage === 1); if (from > 2) this.pagination.append('…'); }
            for (let page = from; page <= to; page++) add(String(page), page, false, page === this.currentPage);
            if (to < pages) { if (to < pages - 1) this.pagination.append('…'); add(String(pages), pages, false, this.currentPage === pages); }
            add('›', Math.min(pages, this.currentPage + 1), this.currentPage === pages);
        }
    }

    class ModalController {
        constructor(backdrop, options = {}) {
            this.backdrop = typeof backdrop === 'string' ? document.querySelector(backdrop) : backdrop;
            if (!this.backdrop) return;
            this.form = this.backdrop.querySelector('form');
            this.backdrop.__afModalController = this;
            this.dirty = false;
            this.warnDirty = options.warnDirty !== false;
            this.lastFocus = null;
            this.form?.addEventListener('input', () => { this.dirty = true; });
            this.form?.addEventListener('change', () => { this.dirty = true; });
            this.backdrop.querySelectorAll('[data-af-close]').forEach((button) => button.addEventListener('click', () => this.close()));
            this.backdrop.addEventListener('mousedown', (event) => { if (event.target === this.backdrop) this.close(); });
            this.backdrop.addEventListener('afenixx:escape-close', () => this.close(true));
        }
        open(trigger = null) {
            this.lastFocus = trigger || document.activeElement;
            this.dirty = false;
            this.backdrop.classList.add('is-open');
            this.backdrop.setAttribute('aria-hidden', 'false');
            document.body.classList.add('af-modal-open');
            setTimeout(() => this.backdrop.querySelector('input:not([type=hidden]), select, textarea, button')?.focus(), 30);
        }
        close(force = false) {
            force = force || window.__afenixEscapeClosing === true;
            if (!force && this.warnDirty && this.dirty && !window.confirm('Hay cambios sin guardar. ¿Deseas cerrar el formulario?')) return false;
            this.backdrop.classList.remove('is-open');
            this.backdrop.setAttribute('aria-hidden', 'true');
            if (!document.querySelector('.af-modal-backdrop.is-open')) document.body.classList.remove('af-modal-open');
            this.lastFocus?.focus?.();
            return true;
        }
        clean() { this.dirty = false; }
    }

    // Escape siempre cierra el modal activo sin solicitar confirmación.
    // Se usa la fase de captura para adelantarse a manejadores antiguos de cada módulo
    // que intentaban ejecutar el cierre normal y mostraban la advertencia de cambios.
    window.addEventListener('keydown', (event) => {
        if (event.key !== 'Escape' && event.keyCode !== 27) return;

        const open = [...document.querySelectorAll('.af-modal-backdrop.is-open')].pop();
        if (!open) return;

        // Window + fase de captura: se ejecuta antes que los manejadores
        // del documento o del módulo y evita cualquier confirmación antigua.
        event.preventDefault();
        event.stopPropagation();
        event.stopImmediatePropagation();

        const controller = open.__afModalController;
        if (controller && typeof controller.close === 'function') {
            controller.close(true);
            return;
        }

        open.classList.remove('is-open');
        open.setAttribute('aria-hidden', 'true');
        if (!document.querySelector('.af-modal-backdrop.is-open')) {
            document.body.classList.remove('af-modal-open');
        }
    }, true);

    async function request(url, options = {}) {
        const response = await fetch(url, {credentials: 'same-origin', ...options});
        let payload = {};
        try { payload = await response.json(); } catch (_) { payload = {success: false, message: 'El servidor devolvió una respuesta no válida.'}; }
        if (!response.ok || payload.success === false) throw new Error(payload.message || 'No se pudo completar la operación.');
        return payload;
    }

    async function copy(text) {
        if (!text) return false;
        try { await navigator.clipboard.writeText(text); return true; }
        catch (_) {
            const field = document.createElement('textarea'); field.value = text; field.style.position = 'fixed'; field.style.opacity = '0'; document.body.appendChild(field); field.select();
            const ok = document.execCommand('copy'); field.remove(); return ok;
        }
    }

    window.AfenixAdmin = { UnifiedList, ModalController, request, copy, norm };
})();
