/**
 * UltraGuard Pro — Smart Realtime Engine v4.0
 *
 * Replaces constant AJAX polling with Server-Sent Events (SSE).
 * One persistent connection per page. Server pushes only changed data.
 * Pages subscribe to only the channels they need.
 * Falls back to minimal polling if SSE is blocked.
 */
(function ($) {
    'use strict';

    if (typeof $ === 'undefined') { console.error('[UG RT] jQuery not available'); return; }

    /* --- Config --- */
    var CFG = {
        ajaxurl      : (window.ultraguardAdmin || window.ultraguardPro || {}).ajaxurl || '/wp-admin/admin-ajax.php',
        nonce        : (window.ultraguardAdmin || window.ultraguardPro || {}).realtime_nonce || '',
        debug        : (window.ultraguardAdmin || window.ultraguardPro || {}).debug || false,
        sseAction    : 'ultraguard_sse_stream',
        fallbackMs   : 15000,
        reconnectBase: 1000,
        reconnectMax : 30000,
        pauseHidden  : true,
        maxReconnects: 20
    };

    /* --- Channel renderers --- */
    var RENDERERS = {
        antivirus_status: function (d) {
            var s = !!d.is_scanning;
            _sb('[data-rt="antivirus-status"]', d.status, d.status_label);
            _pb('[data-rt="antivirus-progress"]', d.progress);
            _tx('[data-rt="antivirus-progress-label"]',   d.progress_label  || (d.progress + '%'));
            _tx('[data-rt="antivirus-phase"]',             d.phase_label     || '');
            _tx('[data-rt="antivirus-current-file"]',     d.current_file    || (s ? '…' : ''));
            _tx('[data-rt="antivirus-files-scanned"]',    _f(d.files_scanned));
            _tx('[data-rt="antivirus-total-files"]',      _f(d.total_files));
            _tx('[data-rt="antivirus-threats-found"]',    _f(d.threats_found));
            _tx('[data-rt="antivirus-infected-files"]',   _f(d.infected_files));
            _tx('[data-rt="antivirus-suspicious-files"]', _f(d.suspicious_files));
            _tx('[data-rt="antivirus-clean-files"]',      _f(d.clean_files));
            _tx('[data-rt="antivirus-quarantined"]',      _f(d.quarantined));
            _tx('[data-rt="antivirus-last-scan"]',        d.last_scan_human || '');
            _tx('[data-rt="antivirus-elapsed"]',          d.elapsed_human   || '');
            _tx('[data-rt="antivirus-estimated"]',        d.estimated_human || (s ? 'Calculating…' : ''));
            _tx('[data-rt="antivirus-scan-type"]',        d.scan_type       || '');
            if (d.total_batches > 0) _tx('[data-rt="antivirus-batch"]', d.current_batch + ' / ' + d.total_batches);
            _cl('[data-rt="antivirus-scanning-row"]',    'ug-scanning-active', s);
            _cl('[data-rt="antivirus-idle-row"]',         'ultraguard-hidden',          s);
            _cl('[data-rt="antivirus-progress-row"]',     'ultraguard-hidden',         !s);
            _cl('[data-rt="antivirus-phase-row"]',        'ultraguard-hidden',         !s);
            _cl('[data-rt="antivirus-current-file-row"]', 'ultraguard-hidden',         !s || !d.current_file);
            _cl('[data-rt="antivirus-batch-row"]',        'ultraguard-hidden',         !s || !d.total_batches);
            _cl('[data-rt="antivirus-timing-row"]',       'ultraguard-hidden',         !s);
            var i = ((window.ultraguardAdmin || {}).i18n || {});
            if (s) { $('[data-rt="antivirus-scan-btn"]').text(i.scanning || 'Scanning…').prop('disabled', true).addClass('ug-btn-scanning'); }
            else   { $('[data-rt="antivirus-scan-btn"]').text(i.run_scan || 'Run Scan').prop('disabled', false).removeClass('ug-btn-scanning'); }

            // ── Also update legacy class-based card (created by antivirus-page.js) ──
            var pct = parseFloat(d.progress) || 0;
            $('.ultraguard-scan-progress-bar').css('width', pct + '%').attr('data-percentage', pct);
            $('.ultraguard-scan-progress-value').text(pct.toFixed(1) + '%');
            $('.ultraguard-current-file-display').text(d.current_file || (s ? 'Scanning…' : ''));
            $('.ultraguard-estimated-time').text(d.estimated_human || (s ? 'Calculating…' : ''));
            if (d.current_batch && d.total_batches) {
                $('.ultraguard-batch-info').text('Batch ' + d.current_batch + ' of ' + d.total_batches);
            }
            if (d.phase_label && s) {
                $('.ultraguard-security-suitegress-label').text(d.phase_label);
            }
            // Update class-based stat containers (legacy antivirus-page.js card)
            $('.ultraguard-stat-scanned-files  .ultraguard-stat-value, .ultraguard-stat-scanned-files  .ultraguard-metric-value').text(_f(d.files_scanned));
            $('.ultraguard-stat-total-files    .ultraguard-stat-value, .ultraguard-stat-total-files    .ultraguard-metric-value').text(_f(d.total_files));
            $('.ultraguard-stat-infected-files .ultraguard-stat-value, .ultraguard-stat-infected-files .ultraguard-metric-value').text(_f(d.infected_files));
            if ((d.infected_files || 0) > 0) {
                $('.ultraguard-stat-infected-files').closest('.ultraguard-stat-card, .ultraguard-card').addClass('warning');
            }
            $('.ultraguard-stat-suspicious-files .ultraguard-stat-value, .ultraguard-stat-suspicious-files .ultraguard-metric-value').text(_f(d.suspicious_files));
            $('.ultraguard-stat-clean-files       .ultraguard-stat-value, .ultraguard-stat-clean-files       .ultraguard-metric-value').text(_f(d.clean_files));

            // ── Also update raw #id-based HTML card (file-scanner-progress-card) ──
            $('#file-scanner-scan-progress .progress-bar').css('width', pct + '%');
            $('#file-scanner-progress-text').text(pct.toFixed(1) + '%');
            $('#file-scanner-files-count').text(_f(d.files_scanned));
            $('#file-scanner-threats-count').text(_f(d.threats_found));

            // ── Show/hide scanning card if it doesn't exist yet and scan just started ──
            if (s && !$('.ultraguard-scan-progress-card').length) {
                $(document).trigger('ultraguard:scan:started', [d]);
            }

            // Fire scan completed event so page can reload results table etc.
            if (d.scan_just_completed) {
                $(document).trigger('ultraguard:scan:completed', [d]);
            }
        },
        file_scanner_status: function (d) {
            _sb('[data-rt="file-scanner-status"]', d.status, d.status_label);
            _tx('[data-rt="file-scanner-files-checked"]', _f(d.files_checked));
            _tx('[data-rt="file-scanner-changes"]',       _f(d.changes_detected));
            _tx('[data-rt="file-scanner-last-check"]',    d.last_check_human || '');
            _tx('[data-rt="file-scanner-quarantined"]',   _f(d.quarantined));
            _cl('[data-rt="file-scanner-alert"]', 'ug-has-changes', d.changes_detected > 0);
        },
        filechecker_progress: function (d) {
            // Delegate to filechecker-page.js renderer if loaded (preferred path).
            if (window.UltraGuardRealtime && window.UltraGuardRealtime._fcRenderer) {
                window.UltraGuardRealtime._fcRenderer(d);
                return;
            }
            // Minimal direct DOM update for non-FC pages
            var pct = parseInt(d.percent || 0, 10);
            _pb('[data-rt="fc-progress"]', pct);
            _tx('[data-rt="fc-scan-pct"]', pct + '%');
            _tx('[data-rt="fc-current-file"]', d.current_file || '');
            _tx('[data-rt="fc-changes"]', _f(d.changes_found));
            _cl('[data-rt="fc-scanning-row"]', 'ultraguard-hidden', !d.is_scanning);
            _cl('[data-rt="fc-idle-row"]',     'ultraguard-hidden',  d.is_scanning);
            var $startBtns = $('[data-ajax-action="ultraguard_fc_start_full_scan"], [data-ajax-action="ultraguard_fc_start_quick_scan"]');
            if (d.is_scanning) {
                $startBtns.prop('disabled', true);
            } else {
                $startBtns.prop('disabled', false);
                if (d.scan_just_completed) {
                    $(document).trigger('ultraguard:fc:scan:completed', [d]);
                }
            }
        },
        htaccess_status: function (d) {
            _sb('[data-rt="htaccess-status"]', d.status, d.status_label);
            _tx('[data-rt="htaccess-rules-active"]',  _f(d.rules_active));
            _tx('[data-rt="htaccess-blocks-today"]',  _f(d.blocks_today));
            _tx('[data-rt="htaccess-last-modified"]', d.last_modified_human || '');
            _cl('[data-rt="htaccess-integrity-icon"]', 'ug-ok',   !!d.integrity_ok);
            _cl('[data-rt="htaccess-integrity-icon"]', 'ug-warn', !d.integrity_ok);
        },
        login_status: function (d) {
            _tx('[data-rt="login-attempts-today"]', _f(d.attempts_today));
            _tx('[data-rt="login-locked-now"]',     _f(d.locked_now));
            _tx('[data-rt="login-blocked-ips"]',    _f(d.blocked_ips));
            _tx('[data-rt="login-last-attempt"]',   d.last_attempt_human || '');
            _cl('[data-rt="login-alert-row"]', 'ug-alert-active', d.locked_now > 0);
            if (d.recent_attempts && d.recent_attempts.length)
                _lt('[data-rt="login-attempts-table"]', d.recent_attempts, ['time_human','username','ip','status']);
        },
        firewall_status: function (d) {
            _sb('[data-rt="firewall-status"]', d.status, d.status_label);
            _tx('[data-rt="firewall-blocked-today"]', _f(d.blocked_today));
            _tx('[data-rt="firewall-blocked-total"]', _f(d.blocked_total));
            _tx('[data-rt="firewall-active-rules"]',  _f(d.active_rules));
            _tx('[data-rt="firewall-top-threat-ip"]', d.top_threat_ip || '—');
            if (d.recent_blocks && d.recent_blocks.length)
                _lt('[data-rt="firewall-blocks-table"]', d.recent_blocks, ['time_human','ip','rule','action']);
        },
        security_score: function (d) {
            _tx('[data-rt="security-score-value"]', d.score);
            _tx('[data-rt="security-score-label"]', d.label);
            var $a = $('[data-rt="security-score-arc"]');
            if ($a.length) {
                $a.attr('data-score', d.score).find('.ug-score-number').text(d.score);
                $a.removeClass('ug-score-critical ug-score-warning ug-score-good ug-score-excellent')
                  .addClass('ug-score-' + d.level);
            }
            _sb('[data-rt="dashboard-status-badge"]', d.level, d.status_text);
        },
        // ── Vulnerability Scanner ─────────────────────────────────────────────
        vuln_scan_status: function (d) {
            var running = d.status === 'running' || d.status === 'queued';
            var pct     = parseFloat(d.progress) || 0;
            var phase   = d.phase || '';

            // Progress bar + percentage text
            _pb('[data-rt="vuln-progress-bar"]', pct);
            _tx('[data-rt="vuln-progress-pct"]', pct.toFixed(0) + '%');
            _tx('[data-rt="vuln-phase"]', phase);

            // Badge
            var $badge = $('[data-rt="vuln-scan-badge"]');
            $badge.removeClass('ug-scan-badge--idle ug-scan-badge--running ug-scan-badge--error');
            $badge.addClass('ug-scan-badge--' + (running ? 'running' : (d.status === 'error' ? 'error' : 'idle')));
            _tx('[data-rt="vuln-scan-status-text"]', running ? 'Running' : (d.status ? d.status.charAt(0).toUpperCase() + d.status.slice(1) : 'Idle'));

            // Show/hide progress wrap
            var $wrap = $('[data-rt="vuln-progress-wrap"]');
            if (running) { $wrap.show(); } else { $wrap.hide(); }

            // Show/hide stop button
            var $stop = $('[data-rt="vuln-stop-btn"]');
            if (running) { $stop.show(); } else { $stop.hide(); }

            // Update scan-id on stop button
            if (d.scan_id) { $stop.attr('data-scan-id', d.scan_id); }

            // Live component counters from phase string
            if (phase.indexOf('plugin') !== -1 || phase.indexOf('Plugin') !== -1) {
                var m = phase.match(/\((\d+)\/(\d+)\)/);
                if (m) { _tx('[data-rt="vuln-plugins-checked"]', m[1] + ' / ' + m[2]); }
            }
            if (phase.indexOf('theme') !== -1 || phase.indexOf('Theme') !== -1) {
                var t = phase.match(/\((\d+)\/(\d+)\)/);
                if (t) { _tx('[data-rt="vuln-themes-checked"]', t[1] + ' / ' + t[2]); }
            }
            if (d.threats_found !== undefined) {
                _tx('[data-rt="vuln-cves-found"]', _f(d.threats_found));
            }

            // Error box
            if (d.status === 'error' && d.error) {
                $('[data-rt="vuln-error-box"]').show();
                _tx('[data-rt="vuln-error-msg"]', d.error);
            } else {
                $('[data-rt="vuln-error-box"]').hide();
            }

            // Complete summary
            if (d.status === 'complete') {
                $('[data-rt="vuln-complete-summary"]').show();
                _tx('[data-rt="vuln-complete-detail"]',
                    (_f(d.threats_found || 0)) + ' new vulnerabilities found.');
                $(document).trigger('ultraguard:vuln:scan:completed', [d]);
            }

            // Disable/enable run-scan button
            if (running) {
                $('[data-rt="vuln-scan-btn"]').prop('disabled', true).text('Scanning…');
            } else {
                $('[data-rt="vuln-scan-btn"]').prop('disabled', false).text('Run Scan');
            }
        },

        // ── Database Scanner ──────────────────────────────────────────────────
        db_scan_status: function (d) {
            var running = d.status === 'running' || d.status === 'queued';
            var pct     = parseFloat(d.progress) || 0;
            var phase   = d.phase || '';

            $('#db-scan-progress-bar .ultraguard-security-suitegress-bar').css('width', pct.toFixed(0) + '%').attr('aria-valuenow', pct.toFixed(0));
            $('#db-scan-progress-bar [data-rt-progress-label]').text(pct.toFixed(0) + '%');
            $('#db-scan-progress-bar .ultraguard-security-suitegress-label').text('Scanning… ' + pct.toFixed(0) + '%');
            $('#db-scan-phase').text(phase || 'Preparing scan…');

            // Badge
            var $badge = $('#ug-db-scan-panel .ultraguard-card-title .ultraguard-badge');
            $badge.removeClass(function(i, cls) {
                return (cls.match(/\bultraguard-badge-\S+/g) || []).join(' ');
            });
            $badge.addClass('ultraguard-badge-' + (running ? 'warning' : (d.status === 'error' ? 'danger' : 'info')));
            $badge.text(running ? 'Running' : (d.status ? d.status.charAt(0).toUpperCase() + d.status.slice(1) : 'Idle'));

            // Show/hide progress and phase
            var $wrap = $('#db-scan-progress-bar');
            var $phase = $('#db-scan-phase');
            if (running) {
                $wrap.show().removeClass('ultraguard-hidden');
                $phase.show().removeClass('ultraguard-hidden');
            } else {
                $wrap.hide();
                if (!phase) { $phase.hide(); }
            }

            // Stop button
            var $stop = $('[data-rt="db-stop-btn"]');
            if (running) { $stop.show(); } else { $stop.hide(); }
            if (d.scan_id) { $stop.attr('data-scan-id', d.scan_id); }

            var $stats = $('#db-scan-live-stats .ultraguard-list-item');
            if (d.total_checked !== undefined && $stats.length >= 1) {
                $stats.eq(0).find('.ultraguard-list-badge').text(_f(d.total_checked));
            }
            if (d.threats_found !== undefined && $stats.length >= 2) {
                var $threatBadge = $stats.eq(1).find('.ultraguard-list-badge');
                $threatBadge.text(_f(d.threats_found));
                if ((parseInt(d.threats_found, 10) || 0) > 0) {
                    $threatBadge.removeClass('ultraguard-badge-success').addClass('ultraguard-badge-danger');
                } else {
                    $threatBadge.removeClass('ultraguard-badge-danger').addClass('ultraguard-badge-success');
                }
            }

            // Per-table status labels driven by progress milestones
            var tableStatuses = {
                2: { start: 10, end: 40 },
                3: { start: 40, end: 65 },
                4: { start: 65, end: 80 },
                5: { start: 80, end: 92 }
            };
            $.each(tableStatuses, function(index, range) {
                var $el = $stats.eq(index).find('.ultraguard-list-badge');
                if (!$el.length) return;
                if (pct >= range.end) {
                    $el.text('Done')
                        .removeClass('ultraguard-badge-secondary ultraguard-badge-warning')
                        .addClass('ultraguard-badge-success');
                } else if (pct >= range.start) {
                    $el.text('Scanning…')
                        .removeClass('ultraguard-badge-secondary ultraguard-badge-success')
                        .addClass('ultraguard-badge-warning');
                } else {
                    $el.text('Pending')
                        .removeClass('ultraguard-badge-warning ultraguard-badge-success')
                        .addClass('ultraguard-badge-secondary');
                }
            });

            // Error box
            if (d.status === 'error' && d.error) {
                $('#db-scan-error-box').show().removeClass('ultraguard-hidden');
                $('#db-scan-error-box .ultraguard-alert-message').text(d.error);
            } else {
                $('#db-scan-error-box').hide();
            }

            // Complete summary
            if (d.status === 'complete') {
                $('#db-scan-complete-summary').show().removeClass('ultraguard-hidden');
                $('#db-scan-complete-detail').show().removeClass('ultraguard-hidden').text(_f(d.threats_found || 0) + ' threats found.');
                $(document).trigger('ultraguard:db:scan:completed', [d]);
            } else {
                $('#db-scan-complete-summary, #db-scan-complete-detail').hide();
            }

            // Disable/enable scan button
            if (running) {
                $('[data-rt="db-scan-btn"]').prop('disabled', true).text('Scanning…');
            } else {
                $('[data-rt="db-scan-btn"]').prop('disabled', false).text('Scan Database');
            }
        },

        threat_feed: function (d) {
            _tx('[data-rt="threats-blocked-count"]',  _f(d.threats_blocked));
            _tx('[data-rt="threats-detected-count"]', _f(d.threats_detected));
            if (d.feed && d.feed.length) {
                var $fl = $('[data-rt="threat-feed-list"]');
                if ($fl.length) {
                    var h = '';
                    $.each(d.feed.slice(0, 10), function (i, x) {
                        h += '<li class="ug-feed-item ug-severity-' + _e(x.severity) + '">'
                           + '<span class="ug-feed-time">' + _e(x.time_human) + '</span>'
                           + '<span class="ug-feed-type">' + _e(x.type)       + '</span>'
                           + '<span class="ug-feed-detail">' + _e(x.detail)   + '</span></li>';
                    });
                    $fl.html(h);
                }
            }
        }
    };

    /* --- State --- */
    var _subs             = new Set();
    var _es               = null;
    var _paused           = false;
    var _retries          = 0;
    var _retryTmr         = null;
    var _resubTmr         = null;      // debounce timer for batched subscribe() calls
    var _channelListeners = {};        // named listeners for leak-free removal on reconnect
    var _fbTimers         = {};
    var _noSSE            = !('EventSource' in window);
    var _lastId           = '';
    var _cid              = 'ug_' + Math.random().toString(36).slice(2, 11) + '_' + Date.now();

    /* --- Public API --- */
    window.UltraGuardRealtime = {
        subscribe: function (ch) {
            _subs.add(ch);
            if (_noSSE && !_paused) {
                _startFb(ch);
            } else if (_es && _es.readyState !== 2 /* CLOSED */) {
                // Debounce reconnect by 250ms to allow batching multiple subscribe() calls
                if (_resubTmr) clearTimeout(_resubTmr);
                _resubTmr = setTimeout(function() { _reconnect(0); _resubTmr = null; }, 250);
            }
            // else: will be connected via start()
        },
        unsubscribe: function (ch) {
            _subs.delete(ch);
            if (_fbTimers[ch]) { clearInterval(_fbTimers[ch]); delete _fbTimers[ch]; }
        },
        start: function () {
            CFG.nonce = ((window.ultraguardAdmin || window.ultraguardPro || {}).realtime_nonce) || CFG.nonce;
            if (_subs.size === 0) { _log('No subscriptions'); return; }
            if (_noSSE) { _subs.forEach(_startFb); }
            else        { _open(); }
            if (CFG.pauseHidden && typeof document.hidden !== 'undefined') {
                document.addEventListener('visibilitychange', function () {
                    if (document.hidden) UltraGuardRealtime.pause();
                    else                 UltraGuardRealtime.resume();
                });
            }
            _log('Started: ' + Array.from(_subs).join(','));
        },
        pause: function () {
            _paused = true;
            if (_es) { _es.close(); _es = null; }
            if (_retryTmr) { clearTimeout(_retryTmr); _retryTmr = null; }
            if (_resubTmr) { clearTimeout(_resubTmr); _resubTmr = null; }
            _channelListeners = {};
            for (var k in _fbTimers) { clearInterval(_fbTimers[k]); delete _fbTimers[k]; }
        },
        resume: function () {
            if (!_paused) return;
            _paused = false; _retries = 0;
            UltraGuardRealtime.start();
        },
        forceRefresh: function (ch) {
            if (_noSSE) _pollFb(ch);
            else        _reconnect(0);
        },
        addRenderer: function (ch, fn) { RENDERERS[ch] = fn; },
        getClientId: function () { return _cid; },
        isConnected: function () { return _es && _es.readyState === 1 /* OPEN */; }
    };

    /* --- SSE --- */
    function _open() {
        if (_paused || _subs.size === 0) return;
        if (_es) { _es.close(); _es = null; }
        var url = CFG.ajaxurl
            + '?action='    + CFG.sseAction
            + '&nonce='     + encodeURIComponent(CFG.nonce)
            + '&channels='  + encodeURIComponent(Array.from(_subs).join(','))
            + '&client_id=' + encodeURIComponent(_cid)
            + (_lastId ? '&last_event_id=' + encodeURIComponent(_lastId) : '');
        _log('SSE open: ' + url.replace(CFG.ajaxurl, '…'));
        _es = new EventSource(url);
        _es.onopen = function () { _retries = 0; _log('SSE connected'); $(document).trigger('ultraguard:rt:connected'); };
        _es.onerror = function () {
            _log('SSE error st=' + (_es ? _es.readyState : '?'));
            if (_es) { _es.close(); _es = null; }
            if (_paused) return;
            _retries++;
            if (_retries > CFG.maxReconnects) { _log('Max retries — fallback'); _noSSE = true; _subs.forEach(_startFb); return; }
            var d = Math.min(CFG.reconnectBase * Math.pow(1.5, _retries - 1), CFG.reconnectMax);
            _retryTmr = setTimeout(_open, d);
        };
        // Store named listeners so we can remove them on reconnect (prevents memory leak)
        _channelListeners = _channelListeners || {};
        _subs.forEach(function (ch) {
            // Remove any previously registered listener for this channel
            if (_channelListeners[ch] && _es.removeEventListener) {
                _es.removeEventListener(ch, _channelListeners[ch]);
            }
            _channelListeners[ch] = function (ev) {
                _lastId = ev.lastEventId || _lastId;
                try {
                    var data = JSON.parse(ev.data);
                    if (RENDERERS[ch]) { RENDERERS[ch](data); $(document).trigger('ultraguard:rt:update', [ch, data]); }
                } catch(err) { _log('Parse err [' + ch + ']: ' + err); }
            };
            _es.addEventListener(ch, _channelListeners[ch]);
        });
        _es.addEventListener('stream_end', function () {
            if (_es) { _es.close(); _es = null; }
            if (!_paused) _retryTmr = setTimeout(_open, 200);
        });
    }
    function _reconnect(delay) {
        if (_es) { _es.close(); _es = null; }
        if (_retryTmr) clearTimeout(_retryTmr);
        _retryTmr = setTimeout(_open, delay);
    }

    /* --- Fallback polling --- */
    var _fbActions = {
        antivirus_status      : 'ultraguard_rt_antivirus_status',
        file_scanner_status   : 'ultraguard_rt_file_scanner_status',
        htaccess_status       : 'ultraguard_rt_htaccess_status',
        login_status          : 'ultraguard_rt_login_status',
        firewall_status       : 'ultraguard_rt_firewall_status',
        security_score        : 'ultraguard_rt_security_score',
        threat_feed           : 'ultraguard_rt_threat_feed',
        filechecker_progress  : 'ultraguard_rt_filechecker_progress',
        db_scan_status        : 'ultraguard_rt_db_scan_status',
        vuln_scan_status      : 'ultraguard_rt_vuln_scan_status'
    };
    function _startFb(ch) {
        if (_fbTimers[ch]) return;
        _pollFb(ch);
        _fbTimers[ch] = setInterval(function () { _pollFb(ch); }, CFG.fallbackMs);
    }
    function _pollFb(ch) {
        var action = _fbActions[ch]; if (!action) return;
        $.post(CFG.ajaxurl, { action: action, nonce: CFG.nonce, client_id: _cid }, function (res) {
            if (res && res.success && res.data && RENDERERS[ch]) RENDERERS[ch](res.data);
        });
    }

    /* --- DOM helpers --- */
    function _sb(sel, st, lb) {
        var $e = $(sel); if (!$e.length) return;
        $e.removeClass('ug-badge-active ug-badge-inactive ug-badge-scanning ug-badge-error ug-badge-warning ug-badge-ok ug-badge-protected ug-badge-danger ug-badge-success ug-badge-good ug-badge-excellent ug-badge-critical');
        $e.addClass('ug-badge-' + (st || 'unknown'));
        if (lb) $e.text(lb);
    }
    function _pb(sel, pct) {
        var $e = $(sel); if (!$e.length) return;
        var v = Math.min(100, Math.max(0, parseFloat(pct) || 0));
        $e.css('width', v + '%').attr('aria-valuenow', v).attr('data-width', v);
        $e.closest('[data-rt-progress-wrap]').find('[data-rt-progress-label]').text(v.toFixed(1) + '%');
        // Also update sibling pct text elements
        $e.closest('.ug-scan-progress-bar-track').next().find('[data-rt-progress-label], [data-rt$="-progress-pct"], [data-rt$="-progress-label"]').text(v.toFixed(0) + '%');
    }
    // On DOM ready: restore bar widths from data-width attribute (since wp_kses_post strips inline style)
    $(document).ready(function() {
        $('[data-rt$="-progress"][data-width]').each(function() {
            $(this).css('width', $(this).attr('data-width') + '%');
        });
        // Also init any fill bars that have aria-valuenow set (PHP renders this even when kses strips style)
        $('[data-rt$="-progress"][aria-valuenow]').each(function() {
            var v = parseFloat($(this).attr('aria-valuenow')) || 0;
            if (v > 0) { $(this).css('width', v + '%'); }
        });
    });
    function _tx(sel, txt) { var $e = $(sel); if ($e.length) $e.text(txt != null ? txt : '—'); }
    function _cl(sel, cls, cond) { var $e = $(sel); if (!$e.length) return; if (cond) $e.addClass(cls); else $e.removeClass(cls); }
    function _lt(sel, rows, cols) {
        var $t = $(sel); if (!$t.length || !rows.length) return;
        var h = '';
        $.each(rows.slice(0, 20), function (i, row) {
            h += '<tr>';
            $.each(cols, function (j, col) { h += '<td>' + _e(row[col] != null ? row[col] : '') + '</td>'; });
            h += '</tr>';
        });
        var $b = $t.is('table') ? $t.find('tbody') : $t;
        if (!$b.length) $b = $('<tbody>').appendTo($t);
        $b.html(h);
    }
    function _f(n) { var x = parseInt(n, 10); return isNaN(x) ? '0' : x.toLocaleString(); }
    function _e(s) { return $('<span>').text(String(s == null ? '' : s)).html(); }
    function _log(m) { if (CFG.debug) { console.log('[UG-RT]', m); } }

    /* --- Pre-subscriptions from PHP localized data --- */
    var _pre = ((window.ultraguardAdmin || window.ultraguardPro || {}).rt_channels || []);
    _pre.forEach(function (ch) { _subs.add(ch); });

    $(document).ready(function () {
        if (_subs.size > 0) window.UltraGuardRealtime.start();

        // When a scan is triggered (button click or external event),
        // ensure antivirus_status is subscribed and force an immediate refresh
        $(document).on('ultraguard:scan:started', function () {
            _log('Scan started event — subscribing antivirus_status');
            window.UltraGuardRealtime.subscribe('antivirus_status');
            window.UltraGuardRealtime.forceRefresh('antivirus_status');
        });

        // When a scan completes, refresh once more to get final state
        $(document).on('ultraguard:scan:completed', function () {
            _log('Scan completed event — final refresh');
            window.UltraGuardRealtime.forceRefresh('antivirus_status');
        });

        // When a filechecker scan starts, ensure the channel is subscribed
        $(document).on('ultraguard:ajax:success', function (e, response, requestData) {
            var action = (requestData && requestData.action) || '';
            if (action === 'ultraguard_fc_start_full_scan' || action === 'ultraguard_fc_start_quick_scan') {
                _log('FC scan started — subscribing filechecker_progress');
                window.UltraGuardRealtime.subscribe('filechecker_progress');
                window.UltraGuardRealtime.forceRefresh('filechecker_progress');
            }
        });
    });

    // Expose a hook so filechecker-page.js can register its own renderer
    // without a race-condition: it sets this before document.ready fires.
    window.UltraGuardRealtime._fcRenderer = null;

}(jQuery));
