(function () {
    // Minimal global loader utility for reuse across components
    // Exposes: window.MBLoader.{clearTimer, show, hide, scheduleShow, onRequestStart, onRequestEnd, bindHtmxOverlay}
    if (window.MBLoader) return; // don't redefine

    function clearTimer(obj, key) {
        try {
            if (obj && obj[key]) clearTimeout(obj[key]);
        } catch (e) {
        }
        if (obj) obj[key] = null;
    }

    function show(overlay) {
        if (!overlay) return;
        clearTimer(overlay, '_showTimer');
        clearTimer(overlay, '_hideTimer');

        // Apply a meaningful temporary min-height only in modal context so the body doesn't collapse
        try {
            var parent = overlay.parentElement;
            var inModal = false;
            var dlg = null;
            try {
                if (overlay.closest) {
                    dlg = overlay.closest('dialog');
                    // Our BeezUI modal wraps content in <el-dialog-panel> inside a <dialog>
                    if (overlay.closest('el-dialog-panel')) {
                        inModal = true;
                    } else {
                        if (dlg && dlg.id && document.getElementById(dlg.id + '-panel')) inModal = true;
                    }
                }
            } catch (e2) { /* noop */ }
            if (inModal && parent) {
                if (overlay._mbPrevMinHeight === undefined) {
                    overlay._mbPrevMinHeight = parent.style.minHeight || '';
                }
                if (!parent.style.minHeight) {
                    var style = dlg ? dlg.getAttribute('data-style') : '';
                    parent.style.minHeight = (style === 'confirm') ? '100px' : '240px';
                    overlay._mbAppliedMinHeight = true;
                }
            }
        } catch (e3) { /* noop */ }

        overlay.classList.remove('hidden', 'opacity-0');
        overlay.classList.add('flex');
        try {
            overlay.getBoundingClientRect();
        } catch (e) {
        }
        // opacity utility used by our overlays; keep consistent with components
        overlay.classList.add('opacity-100');
    }

    function hide(overlay) {
        if (!overlay) return;
        clearTimer(overlay, '_showTimer');
        overlay.classList.remove('opacity-100');
        overlay.classList.add('opacity-0');

        // If we applied a temporary min-height in modal context, restore it on hide
        try {
            var parent = overlay.parentElement;
            if (parent && overlay._mbAppliedMinHeight) {
                parent.style.minHeight = overlay._mbPrevMinHeight || '';
                overlay._mbAppliedMinHeight = false;
                overlay._mbPrevMinHeight = undefined;
            }
        } catch (e) { /* noop */ }

        overlay._hideTimer = setTimeout(function () {
            overlay.classList.add('hidden');
            overlay.classList.remove('flex');
            overlay._hideTimer = null;
        }, 120);
    }

    function scheduleShow(overlay, delayMs) {
        if (!overlay) return;
        clearTimer(overlay, '_showTimer');
        overlay._showTimer = setTimeout(function () {
            show(overlay);
        }, typeof delayMs === 'number' ? delayMs : 350);
    }

    function onRequestStart(overlay, delayMs) {
        if (!overlay) return;
        overlay._reqCount = (overlay._reqCount || 0) + 1;
        scheduleShow(overlay, delayMs);
    }

    function onRequestEnd(overlay) {
        if (!overlay) return;
        overlay._reqCount = Math.max(0, (overlay._reqCount || 0) - 1);
        if (overlay._reqCount === 0) {
            clearTimer(overlay, '_showTimer');
            if (!overlay.classList.contains('hidden')) hide(overlay);
        }
    }

    function bindHtmxOverlay(bodyId, overlayId, delayMs, options) {
        if (!window.htmx) {
            // If HTMX is not yet loaded (e.g. because it is deferred), wait for it and retry.
            setTimeout(function () {
                bindHtmxOverlay(bodyId, overlayId, delayMs, options);
            }, 30);
            return;
        }
        var key = bodyId + '::' + overlayId;
        if (window.__MBL_BOUND && window.__MBL_BOUND[key]) return;
        window.__MBL_BOUND = window.__MBL_BOUND || {};
        window.__MBL_BOUND[key] = true;

        var filter = (typeof options === 'function') ? options : (options && options.filter ? options.filter : null);

        function isBodyTarget(evt) {
            try {
                if (evt && evt.detail) {
                    var t = evt.detail.target;
                    if (t && (t.id === bodyId || (t.closest && t.closest('#' + bodyId)))) return true;

                    // Initiating element and its hx-target attribute
                    var elt = evt.detail.elt;
                    if (elt) {
                        var hxTarget = elt.getAttribute && (elt.getAttribute('hx-target') || elt.getAttribute('data-hx-target'));
                        if (hxTarget && typeof hxTarget === 'string' && hxTarget.indexOf('#' + bodyId) !== -1) {
                            return true;
                        }
                        // If the initiating element is inside the body container, consider it a match
                        if (elt.closest && elt.closest('#' + bodyId)) return true;
                    }

                    // Some HTMX versions expose resolved target on requestConfig
                    var rc = evt.detail.requestConfig || {};
                    var rcTarget = rc.target || rc.swapTarget || rc.targetOverride;
                    if (rcTarget) {
                        // rcTarget can be an element or a CSS selector string
                        if (typeof rcTarget === 'string') {
                            if (rcTarget.indexOf('#' + bodyId) !== -1) return true;
                        } else if (rcTarget.id === bodyId || (rcTarget.closest && rcTarget.closest('#' + bodyId))) {
                            return true;
                        }
                    }
                }
                // As a final fallback, if the event was dispatched from within the body container
                if (evt && evt.target && evt.target.closest && evt.target.closest('#' + bodyId)) return true;
            } catch (e) { /* noop */ }
            return false;
        }

        // Resolve nearest data-no-*-loader attribute from the initiating element upward.
        // Returns:
        // - true  => ignore loader (attribute present and truthy/empty)
        // - false => do not ignore (attribute present and explicitly "0"/"false"/"no"/"off")
        // - null  => no attribute found in chain
        function resolveNoLoader(elt, attrName) {
            try {
                var node = elt;
                while (node && node.getAttribute) {
                    if (node.hasAttribute && node.hasAttribute(attrName)) {
                        var raw = node.getAttribute(attrName);
                        var val = (raw == null ? '' : ('' + raw)).trim().toLowerCase();
                        if (val === '0' || val === 'false' || val === 'no' || val === 'off') return false;
                        // presence or any other value counts as truthy
                        return true;
                    }
                    node = node.parentElement;
                }
            } catch (e) { /* noop */ }
            return null;
        }

        function shouldIgnoreGlobal(evt) {
            try {
                var elt = evt && evt.detail && evt.detail.elt;
                if (!elt) return false;

                // Support opting out via data attribute
                var attrDecision = resolveNoLoader(elt, 'data-no-global-loader');
                if (attrDecision !== null) return attrDecision;

                // NEW: Ignore background/lazy triggers for the global loader
                var rc = evt.detail.requestConfig || {};
                var triggeringEvent = rc.triggeringEvent;
                if (triggeringEvent && (triggeringEvent.type === 'preload' || triggeringEvent.type === 'intersect')) return true;

                // optional header flag via hx-headers
                var headers = rc.headers || {};
                for (var k in headers) {
                    if (!Object.prototype.hasOwnProperty.call(headers, k)) continue;
                    if ((k + '').toLowerCase() === 'x-no-global-loader') {
                        var val = headers[k];
                        if (val === true || val === 1 || val === '1' || ('' + val).toLowerCase() === 'true' || ('' + val).toLowerCase() === 'yes') return true;
                        if (val === 0 || val === '0' || ('' + val).toLowerCase() === 'false' || ('' + val).toLowerCase() === 'no' || ('' + val).toLowerCase() === 'off') return false;
                    }
                }
            } catch (e) { /* noop */ }
            return false;
        }

        function shouldIgnoreLocal(evt) {
            try {
                var elt = evt && evt.detail && evt.detail.elt;
                if (!elt) return false;
                // attribute on initiating element or nearest ancestor wins
                var attrDecision = resolveNoLoader(elt, 'data-no-local-loader');
                if (attrDecision !== null) return attrDecision;
                // optional header flag via hx-headers
                var rc = evt.detail.requestConfig || {};
                var headers = rc.headers || {};
                for (var k in headers) {
                    if (!Object.prototype.hasOwnProperty.call(headers, k)) continue;
                    if ((k + '').toLowerCase() === 'x-no-local-loader') {
                        var val = headers[k];
                        if (val === true || val === 1 || val === '1' || ('' + val).toLowerCase() === 'true' || ('' + val).toLowerCase() === 'yes') return true;
                        if (val === 0 || val === '0' || ('' + val).toLowerCase() === 'false' || ('' + val).toLowerCase() === 'no' || ('' + val).toLowerCase() === 'off') return false;
                    }
                }
            } catch (e) { /* noop */ }
            return false;
        }

        function getOverlay() {
            return document.getElementById(overlayId);
        }

        htmx.on('htmx:beforeRequest', function (evt) {
            var overlay = getOverlay();
            if (!isBodyTarget(evt) || !overlay) return;

            if (filter && !filter(evt)) return;

            // If the request was prevented (e.g. by a local validation listener), skip the loader
            if (evt.defaultPrevented) return;

            // Allow opting out of the global app-loading overlay on a per-request basis
            if (overlayId === 'app-loading' && shouldIgnoreGlobal(evt)) return;
            // Allow opting out of local/module overlays on a per-request basis
            if (overlayId !== 'app-loading' && shouldIgnoreLocal(evt)) return;
            onRequestStart(overlay, typeof delayMs === 'number' ? delayMs : 350);
        });

        function endHandler(evt) {
            var overlay = getOverlay();
            var hasOverlay = !!overlay;
            if (!hasOverlay) return;

            var bodyMatch = isBodyTarget(evt);
            if (bodyMatch && filter && !filter(evt)) bodyMatch = false;

            var pending = (overlay._reqCount || 0) > 0;
            if (overlayId === 'app-loading' && shouldIgnoreGlobal(evt)) return;
            if (!(bodyMatch || pending)) return;
            onRequestEnd(overlay);
        }

        // Hide the overlay once the request completes, even if no swap occurs (e.g., missing target)
        htmx.on('htmx:afterRequest', endHandler);
        htmx.on('htmx:afterSwap', endHandler);
        htmx.on('htmx:responseError', endHandler);
        htmx.on('htmx:afterOnLoad', endHandler);
        htmx.on('htmx:sendError', endHandler);
        htmx.on('htmx:timeout', endHandler);
        htmx.on('htmx:swapError', endHandler);
    }

    window.MBLoader = {clearTimer, show, hide, scheduleShow, onRequestStart, onRequestEnd, bindHtmxOverlay};
})();