/**
 * Structure Viewer — App Controller (sv-app.js)
 *
 * Handles all UI interactions for the SV admin page:
 *   - Accent colour + dark mode
 *   - Tab switching + persistence
 *   - Explorer toolbar (type pills, item picker, recently viewed)
 *   - Dashboard (largest plugins async load)
 *   - Lazy tree loading (all-mode)
 *   - File type tag filter + live search
 *   - File viewer modal + fullscreen
 *   - Settings auto-save
 *   - Pro Comparator (SVC) — initialised only when UI is present
 *
 * Depends on: jQuery, structureviewerSettings (wp_localize_script)
 */
/* global structureviewerSettings */
    jQuery(document).ready(function($) {

        // ── Safe HTML attribute escape ────────────────────────────────
        // $('<div>').text(x).html() encodes <, >, &  but NOT " or '.
        // When the result is used inside a quoted HTML attribute value,
        // a plugin/theme name containing a double-quote would break out
        // of the attribute. svEscAttr escapes everything needed for
        // attribute context.
        function svEscAttr(str) {
            return String(str == null ? '' : str)
                .replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#39;');
        }
        function svEscHtml(str) {
            return String(str == null ? '' : str)
                .replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;');
        }

        // ── Accent colour map ─────────────────────────────────────────
        var accentMap = {
            violet:  { accent:'#7c3aed', light:'#8b5cf6', text:'#a78bfa', hover:'#6d28d9', dim:'rgba(139,92,246,0.2)', glow:'rgba(124,58,237,0.3)' },
            blue:    { accent:'#1d4ed8', light:'#3b82f6', text:'#60a5fa', hover:'#1e40af', dim:'rgba(59,130,246,0.2)',  glow:'rgba(59,130,246,0.3)'  },
            emerald: { accent:'#059669', light:'#10b981', text:'#34d399', hover:'#047857', dim:'rgba(16,185,129,0.2)', glow:'rgba(16,185,129,0.3)' },
            amber:   { accent:'#d97706', light:'#f59e0b', text:'#fbbf24', hover:'#b45309', dim:'rgba(245,158,11,0.2)', glow:'rgba(245,158,11,0.3)'  },
            rose:    { accent:'#e11d48', light:'#f43f5e', text:'#fb7185', hover:'#be123c', dim:'rgba(244,63,94,0.2)',  glow:'rgba(244,63,94,0.3)'   },
        };

        function applyAccentColor(key) {
            var c = accentMap[key];
            if (!c) return;
            var r = document.documentElement;
            r.style.setProperty('--sv-accent',       c.accent);
            r.style.setProperty('--sv-accent-light',  c.light);
            r.style.setProperty('--sv-accent-text',   c.text);
            r.style.setProperty('--sv-accent-hover',  c.hover);
            r.style.setProperty('--sv-accent-dim',    c.dim);
            r.style.setProperty('--sv-accent-glow',   c.glow);
        }

        // Apply saved accent on load
        var savedAccent = localStorage.getItem('sv_accent_color') || $('#sv-setting-accent-color').val() || 'emerald';
        applyAccentColor(savedAccent);
        $('.sv-accent-swatch').removeClass('active');
        $('.sv-accent-swatch[data-accent="' + savedAccent + '"]').addClass('active');

        // Swatch click
        $(document).on('click', '.sv-accent-swatch', function() {
            var key = $(this).data('accent');
            $('.sv-accent-swatch').removeClass('active');
            $(this).addClass('active');
            $('#sv-setting-accent-color').val(key);
            applyAccentColor(key);
            localStorage.setItem('sv_accent_color', key);
        });

        // ── Dark mode ─────────────────────────────────────────────────
        function applyDarkMode(isDark) {
            $('.sv-app').toggleClass('sv-light-mode', !isDark);
            $('#sv-setting-dark-mode').prop('checked', isDark);
        }

        // Restore from localStorage first, fall back to saved PHP setting
        var savedDark = localStorage.getItem('sv_dark_mode');
        var initDark  = savedDark !== null ? (savedDark === '1') : ($('#sv-setting-dark-mode').prop('checked'));
        applyDarkMode(initDark);

        $('#sv-setting-dark-mode').on('change', function() {
            var isDark = $(this).is(':checked');
            applyDarkMode(isDark);
            localStorage.setItem('sv_dark_mode', isDark ? '1' : '0');
        });

        // ── Show File Info ─────────────────────────────────────────────
        function applyFileInfoVisibility(show) { $('.sv-file-info').toggle(show); }

        // Initialise from the toggle's checked state (set by PHP from saved setting)
        var initFileInfo = $('#sv-show-file-info').prop('checked');
        applyFileInfoVisibility(initFileInfo);

        $('#sv-show-file-info').on('change', function() {
            applyFileInfoVisibility($(this).is(':checked'));
        });

        // ── Tab switching + persistence ────────────────────────────────
        function switchTab(tab) {
            $('[data-sv-tab]').removeClass('active');
            $('[data-sv-tab="' + tab + '"]').addClass('active');
            $('.sv-tab-pane').removeClass('active');
            $('#sv-tab-' + tab).addClass('active');
            sessionStorage.setItem('sv_active_tab', tab);
        }

        // Restore tab from sessionStorage on load — run BEFORE making app visible
        var savedTab = sessionStorage.getItem('sv_active_tab') || 'dashboard';
        switchTab(savedTab);
        // Now reveal the app — correct tab is already active, zero flash
        $('.sv-app').addClass('sv-ready');

        $('[data-sv-tab]').on('click', function() {
            switchTab($(this).data('sv-tab'));
        });

        // Quick-action cards navigate to their tab
        $('[data-sv-tab-link]').on('click', function() {
            switchTab($(this).data('sv-tab-link'));
        });

        // ══════════════════════════════════════════════════════
        //  STRUCTURE VIEWER – toolbar, picker, live search, tags
        // ══════════════════════════════════════════════════════

        // ── Item picker dropdown ──────────────────────────────
        var $picker     = $('#sv-item-picker');
        var $pickerBtn  = $('#sv-item-picker-btn');
        var $pickerDrop = $('#sv-item-picker-dropdown');
        var $pickerList = $('#sv-picker-list');
        var $pickerSrch = $('#sv-picker-search');

        function buildPickerList(type) {
            // Fetch item options via hidden select (already rendered in PHP)
            // We'll call an AJAX helper to get them, but simpler: read from PHP-rendered options
            $pickerList.html('<div class="sv-picker-loading">Loading…</div>');
            $.ajax({
                url: structureviewerSettings.ajaxurl,
                type: 'POST',
                data: { action: 'structureviewer_get_item_list', nonce: structureviewerSettings.nonce, type: type },
                success: function(r) {
                    if (!r.success) return;
                    var html = '';
                    var all  = type === 'plugin' ? 'All Plugins' : 'All Themes';
                    var curItem = $('#sv-item').val();
                    html += '<div class="sv-picker-item ' + (curItem === '' ? 'active' : '') + '" data-value="" data-name="' + svEscAttr(all) + '">' + svEscHtml(all) + '</div>';
                    $.each(r.data, function(folder, name) {
                        html += '<div class="sv-picker-item ' + (curItem === folder ? 'active' : '') + '" data-value="' + svEscAttr(folder) + '" data-name="' + svEscAttr(name) + '">' + svEscHtml(name) + '</div>';
                    });
                    $pickerList.html(html);
                    filterPickerList($pickerSrch.val());
                }
            });
        }

        function filterPickerList(q) {
            q = (q || '').toLowerCase();
            $pickerList.find('.sv-picker-item').each(function() {
                var name = $(this).data('name') || $(this).text();
                $(this).toggle(q === '' || name.toLowerCase().includes(q));
            });
        }

        $pickerBtn.on('click', function(e) {
            e.stopPropagation();
            var open = $picker.hasClass('sv-picker-open');
            if (!open) {
                $picker.addClass('sv-picker-open');
                buildPickerList($('#sv-type').val());
                setTimeout(function() { $pickerSrch.focus(); }, 50);
            } else {
                $picker.removeClass('sv-picker-open');
            }
        });

        $pickerSrch.on('input', function() { filterPickerList($(this).val()); });

        $(document).on('click', '.sv-picker-item', function() {
            var val  = $(this).data('value');
            var name = $(this).data('name');
            $('#sv-item').val(val);
            $('#sv-item-picker-label').text(name);
            $picker.removeClass('sv-picker-open');
            if (val) {
                $(document).trigger('sv:item-selected', [val, name, $('#sv-type').val()]);
                svAddRecent(val, name, $('#sv-type').val());
            }
            sessionStorage.setItem('sv_active_tab', 'structure-viewer');
            $('#sv-form').submit();
        });

        $(document).on('click', function(e) {
            if (!$picker.is(e.target) && !$picker.has(e.target).length) {
                $picker.removeClass('sv-picker-open');
            }
        });

        // ── Recently Viewed (localStorage) ───────────────────────────
        var SV_RECENT_KEY = 'sv_recently_viewed';
        var SV_RECENT_MAX = 4;

        function svGetRecent() {
            try { return JSON.parse(localStorage.getItem(SV_RECENT_KEY) || '[]'); } catch(e) { return []; }
        }

        function svAddRecent(folder, name, type) {
            if (!folder) return;
            var list = svGetRecent().filter(function(r) { return r.folder !== folder; });
            list.unshift({ folder: folder, name: name, type: type, ts: Date.now() });
            list = list.slice(0, SV_RECENT_MAX);
            try { localStorage.setItem(SV_RECENT_KEY, JSON.stringify(list)); } catch(e) {}
        }

        function svRenderRecent() {
            var list  = svGetRecent();
            var $wrap = $('#sv-recently-viewed');
            var $empty= $('#sv-recent-empty');
            $wrap.find('.sv-recent-item').remove();
            if (!list.length) { $empty.show(); return; }
            $empty.hide();
            list.forEach(function(r) {
                var typeLabel = r.type === 'plugin' ? 'Plugin' : 'Theme';
                var iconSvg   = r.type === 'plugin'
                    ? '<svg viewBox="0 0 24 24" fill="none" width="13" height="13"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>'
                    : '<svg viewBox="0 0 24 24" fill="none" width="13" height="13"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg>';
                var $item = $('<div class="sv-recent-item"></div>')
                    .attr({'data-folder': r.folder, 'data-type': r.type, 'data-name': r.name})
                    .html(
                        '<span class="sv-recent-icon">' + iconSvg + '</span>' +
                        '<div class="sv-recent-info">' +
                            '<span class="sv-recent-name">' + svEscHtml(r.name) + '</span>' +
                            '<span class="sv-recent-type">' + typeLabel + '</span>' +
                        '</div>'
                    );
                $wrap.append($item);
            });
        }

        // Click handler for recently viewed items
        $(document).on('click', '.sv-recent-item', function() {
            var folder = $(this).data('folder');
            var name   = $(this).data('name');
            var type   = $(this).data('type');
            $('#sv-type').val(type);
            $('#sv-item').val(folder);
            $('#sv-item-picker-label').text(name);
            // Sync type pills
            $('.sv-type-pill').removeClass('active');
            $('.sv-type-pill[data-type="' + type + '"]').addClass('active');
            // Record the visit so the item moves to position 0 and its
            // timestamp refreshes. svAddRecent dedupes so this is safe.
            svAddRecent(folder, name, type);
            sessionStorage.setItem('sv_active_tab', 'structure-viewer');
            $('#sv-form').submit();
        });

        // ── Dashboard: load largest plugins async (avoids cold-cache block) ──
        function svLoadLargestPlugins() {
            var $table = $('#sv-largest-table');
            if (!$table.length) return;
            // If table already has rows, skip (cache was warm, PHP rendered them)
            if ($table.find('.sv-largest-row').length > 0) return;
            // Show loading state
            $table.html('<div class="sv-tree-loading" style="padding:16px;"><svg viewBox="0 0 24 24" fill="none" width="16" height="16" class="sv-spin"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg> Computing…</div>');
            $.ajax({
                url: structureviewerSettings.ajaxurl,
                type: 'POST',
                data: { action: 'structureviewer_get_top_plugins', nonce: structureviewerSettings.nonce },
                success: function(r) {
                    if (!r.success || !r.data) { $table.html(''); return; }
                    var plugins = r.data;
                    var keys    = Object.keys(plugins);
                    if (!keys.length) { $table.html(''); return; }
                    var maxFiles = 1;
                    keys.forEach(function(k) { if (plugins[k].files > maxFiles) maxFiles = plugins[k].files; });
                    var html = '';
                    keys.forEach(function(k) {
                        var p   = plugins[k];
                        var pct = Math.round((p.files / maxFiles) * 100);
                        var sz  = svFormatBytes(p.size);
                        var arrow = '<svg class="sv-dash-stat-arrow" viewBox="0 0 24 24" fill="none" width="13" height="13"><path d="M5 12h14M12 5l7 7-7 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
                        html += '<div class="sv-largest-row sv-dash-stat-clickable"'
                             + ' data-sv-tab-link="structure-viewer"'
                             + ' data-sv-preset-type="plugin"'
                             + ' data-sv-preset-item="' + svEscAttr(p.folder) + '"'
                             + ' data-sv-preset-name="' + svEscAttr(p.name) + '">'
                             + '<div class="sv-largest-name">' + svEscHtml(p.name) + '</div>'
                             + '<div class="sv-largest-bar-wrap"><div class="sv-largest-bar" style="width:' + pct + '%"></div></div>'
                             + '<div class="sv-largest-meta"><span>' + p.files.toLocaleString() + ' files</span><span class="sv-largest-size">' + svEscHtml(sz) + '</span></div>'
                             + arrow + '</div>';
                    });
                    $table.html(html);
                },
                error: function() { $table.html(''); }
            });
        }

        function svFormatBytes(bytes) {
            if (bytes >= 1073741824) return (bytes / 1073741824).toFixed(1) + ' GB';
            if (bytes >= 1048576)    return (bytes / 1048576).toFixed(1) + ' MB';
            if (bytes >= 1024)       return (bytes / 1024).toFixed(1) + ' KB';
            return bytes + ' B';
        }

        svLoadLargestPlugins();
        svRenderRecent();

        // ── Track recently viewed when item picker selects something ──
        // Extend the picker item click handler to also record the visit
        $(document).on('sv:item-selected', function(e, folder, name, type) {
            svAddRecent(folder, name, type);
        });

        // ── Dashboard stat card / largest plugin click (preset type + item) ──
        $(document).on('click', '.sv-dash-stat-clickable', function() {
            var tabLink  = $(this).data('sv-tab-link');
            var preType  = $(this).data('sv-preset-type');
            var preItem  = $(this).data('sv-preset-item') || '';
            var preName  = $(this).data('sv-preset-name') || (preType === 'plugin' ? 'All Plugins' : 'All Themes');

            if (preType) {
                $('#sv-type').val(preType);
                $('#sv-item').val(preItem);
                $('#sv-item-picker-label').text(preName);
                $('.sv-type-pill').removeClass('active');
                $('.sv-type-pill[data-type="' + preType + '"]').addClass('active');
                // Only record real item selections — the "All Plugins" /
                // "All Themes" cards have preType set but preItem empty, and
                // we don't want "All Plugins" cluttering Recently Viewed.
                if (preItem) {
                    svAddRecent(preItem, preName, preType);
                }
            }
            if (tabLink) {
                switchTab(tabLink);
                if (preType) {
                    sessionStorage.setItem('sv_active_tab', tabLink);
                    $('#sv-form').submit();
                }
            }
        });
        // ── All-mode heading: navigate to single-item view ──────────
        // Clicking navigates; the lazy tree AJAX is preloaded on hover for speed
        $(document).on('click keydown', '.sv-plugin-heading-simple', function(e) {
            if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
            e.preventDefault();
            var item  = $(this).data('item');
            var name  = $(this).data('name');
            var dtype = $(this).data('type');
            if (!item) return;
            if (dtype) svAddRecent(item, name, dtype);
            $('#sv-item').val(item);
            $('#sv-item-picker-label').text(name);
            sessionStorage.setItem('sv_active_tab', 'structure-viewer');
            $('#sv-form').submit();
        });

        // Preload tree on hover — by the time user clicks the page is loading anyway,
        // but this warms the server-side cache for the AJAX single-item render
        $(document).on('mouseenter', '.sv-plugin-heading-simple', function() {
            var item  = $(this).data('item');
            var dtype = $(this).data('type');
            if (!item || !dtype) return;
            var cacheKey = dtype + ':' + item;
            if (svTreeCache[cacheKey]) return; // already cached
            // Fire-and-forget prefetch so the WP transient / opcode cache is warm
            $.ajax({
                url: structureviewerSettings.ajaxurl,
                type: 'POST',
                data: { action: 'structureviewer_load_tree', nonce: structureviewerSettings.nonce, type: dtype, item: item },
                success: function(r) {
                    if (r.success) svTreeCache[cacheKey] = r.data.html;
                }
            });
        });

        // ── Lazy AJAX tree cache for all-mode ─────────────────────────
        var svTreeCache = {};
        function svLoadTree(item, type, $placeholder) {
            var cacheKey = type + ':' + item;
            if (svTreeCache[cacheKey]) {
                $placeholder.html(svTreeCache[cacheKey]).show();
                refreshFileTypeTags();
                return;
            }
            $placeholder.html('<div class="sv-tree-loading"><svg viewBox="0 0 24 24" fill="none" width="16" height="16" class="sv-spin"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg> Loading…</div>').show();
            $.ajax({
                url: structureviewerSettings.ajaxurl,
                type: 'POST',
                data: { action: 'structureviewer_load_tree', nonce: structureviewerSettings.nonce, type: type, item: item },
                success: function(r) {
                    if (r.success) {
                        svTreeCache[cacheKey] = r.data.html;
                        $placeholder.html(r.data.html).show();
                        refreshFileTypeTags();
                        // Folder-click handling is delegated globally on
                        // #sv-tree-scroll — no per-load binding needed.
                    } else {
                        $placeholder.html('<p class="sv-tree-error">Could not load structure.</p>').show();
                    }
                },
                error: function() { $placeholder.html('<p class="sv-tree-error">Request failed.</p>').show(); }
            });
        }
        $(document).on('click', '.sv-type-pill', function() {
            var newType = $(this).data('type');
            if (newType === $('#sv-type').val()) return;
            $('.sv-type-pill').removeClass('active');
            $(this).addClass('active');
            $('#sv-type').val(newType);
            $('#sv-item').val('');
            sessionStorage.setItem('sv_active_tab', 'structure-viewer');
            $('#sv-form').submit();
        });

        // ── Dynamically show only available file type tags ────
        var imageExts = ['jpg','jpeg','png','gif','svg','webp','bmp','ico'];

        function refreshFileTypeTags() {
            // In All-mode (no specific item selected), hide the entire tag strip
            if (!$('#sv-item').val()) {
                $('#sv-filetype-tags').hide();
                return;
            }
            $('#sv-filetype-tags').show();

            // Collect all extension classes present in the rendered tree
            var presentExts = {};
            var hasImages = false;
            $('#sv-structure-display .sv-file').each(function() {
                var cls = $(this).attr('class') || '';
                var m = cls.match(/sv-item-type-([a-z0-9]+)/g);
                if (m) {
                    m.forEach(function(c) {
                        var ext = c.replace('sv-item-type-', '');
                        if (imageExts.indexOf(ext) !== -1) {
                            hasImages = true;
                        } else {
                            presentExts[ext] = true;
                        }
                    });
                }
            });
            if (hasImages) presentExts['images'] = true;

            // Show tags that have matching files; hide others (keep All always visible)
            $('#sv-filetype-tags .sv-ft-tag[data-ext!=""]').each(function() {
                var ext = $(this).data('ext');
                $(this).toggle(!!presentExts[ext]);
            });
        }

        // Run on page load after tree is rendered
        refreshFileTypeTags();
        $(document).on('click', '.sv-ft-tag', function() {
            $('.sv-ft-tag').removeClass('active');
            $(this).addClass('active');
            applyFileTypeFilter($(this).data('ext'));
        });

        function applyFileTypeFilter(ext) {
            clearLiveSearch();
            if (!ext) {
                $('#sv-structure-display .sv-file, #sv-structure-display .sv-folder').show();
                $('#sv-structure-display .sv-subtree, #sv-structure-display .sv-nested-subtree').hide();
                return;
            }
            var exts = [];
            if (ext === 'images') { exts = imageExts; }
            else                  { exts = [ext]; }

            $('#sv-structure-display .sv-file').each(function() {
                var cls = $(this).attr('class') || '';
                var match = exts.some(function(e) { return cls.includes('sv-item-type-' + e); });
                $(this).toggle(match);
            });
            $('#sv-structure-display .sv-folder').show();
            $('#sv-structure-display .sv-subtree, #sv-structure-display .sv-nested-subtree').each(function() {
                $(this).show();
                $(this).prev('.sv-folder-header').find('.sv-toggle').text('−');
            });
        }

        // ── Explorer refresh button ───────────────────────────
        // Full AJAX refresh: rebuilds the picker list AND reloads the tree
        // without any page reload.
        $('#sv-explorer-refresh').on('click', function() {
            var $btn = $(this);
            if ($btn.hasClass('sv-refreshing')) return;

            var type = $('#sv-type').val();
            var item = $('#sv-item').val();

            $btn.addClass('sv-refreshing');

            // Clear entire tree cache so every re-fetch gets fresh data
            svTreeCache = {};

            // Clear live search state
            clearLiveSearch();

            // ── Step 1: refresh the picker list via AJAX ──────────────
            // Uses the same buildPickerList() already defined above but we
            // also need to know when it finishes so we can chain the tree
            // reload, so we call the AJAX directly here.
            $.ajax({
                url:  structureviewerSettings.ajaxurl,
                type: 'POST',
                data: { action: 'structureviewer_get_item_list', nonce: structureviewerSettings.nonce, type: type },
                success: function(r) {
                    if (r.success) {
                        // Re-render picker list in place
                        var html = '';
                        var all  = type === 'plugin' ? 'All Plugins' : 'All Themes';
                        html += '<div class="sv-picker-item ' + (item === '' ? 'active' : '') + '" data-value="" data-name="' + svEscAttr(all) + '">' + svEscHtml(all) + '</div>';
                        $.each(r.data, function(folder, name) {
                            html += '<div class="sv-picker-item ' + (item === folder ? 'active' : '') + '" data-value="' + svEscAttr(folder) + '" data-name="' + svEscAttr(name) + '">' + svEscHtml(name) + '</div>';
                        });
                        $pickerList.html(html);
                    }
                },
                complete: function() {
                    // ── Step 2: reload the tree ───────────────────────
                    var $placeholder = $('#sv-structure-display');
                    if (item) {
                        svLoadTree(item, type, $placeholder);
                    }
                    // Always remove the spinning state once both are done
                    setTimeout(function() { $btn.removeClass('sv-refreshing'); }, 400);
                }
            });
        });

        // ── Live search in tree ───────────────────────────────
        var $liveSearch   = $('#sv-live-search');
        var $clearBtn     = $('#sv-live-search-clear');
        var $matchCount   = $('#sv-match-count');
        var $searchStatus = $('#sv-search-status');
        var $noResults    = $('#sv-no-results');
        var liveTimer     = null;
        var $matches      = $();
        var matchIdx      = 0;

        function clearLiveSearch() {
            $liveSearch.val('');
            $clearBtn.hide();
            $searchStatus.hide();
            $noResults.hide();
            $('#sv-structure-display .sv-file, #sv-structure-display .sv-folder').show().removeClass('sv-search-match');
            $('#sv-structure-display .sv-subtree, #sv-structure-display .sv-nested-subtree').hide();
            $('#sv-structure-display .sv-folder-header .sv-toggle').text('+');
        }

        function runLiveSearch(q) {
            q = q.toLowerCase().trim();
            $clearBtn.toggle(q.length > 0);
            if (!q) { clearLiveSearch(); return; }

            // Reset all visibility
            var $allFiles   = $('#sv-structure-display .sv-file');
            var $allFolders = $('#sv-structure-display .sv-folder');
            $allFiles.hide();
            $allFolders.hide();

            // Find matching file names
            $matches = $();
            $allFiles.each(function() {
                var fname = $(this).find('.sv-file-name').first().text().trim().toLowerCase();
                if (fname.includes(q)) {
                    $(this).show().addClass('sv-search-match');
                    $matches = $matches.add(this);
                    // Show all ancestor folders + their subtrees
                    $(this).parents('.sv-folder').each(function() {
                        $(this).show();
                        $(this).children('.sv-subtree, .sv-nested-subtree').show();
                        $(this).children('.sv-folder-header').find('.sv-toggle').text('−');
                    });
                } else {
                    $(this).removeClass('sv-search-match');
                }
            });

            var count = $matches.length;
            if (count === 0) {
                $noResults.show();
                $searchStatus.hide();
            } else {
                $noResults.hide();
                matchIdx = 0;
                $matchCount.text(count + ' match' + (count !== 1 ? 'es' : ''));
                $searchStatus.show();
                scrollToMatch(0);
            }
        }

        function scrollToMatch(idx) {
            if (!$matches.length) return;
            idx = ((idx % $matches.length) + $matches.length) % $matches.length;
            matchIdx = idx;
            $matches.removeClass('sv-match-current');
            var $cur = $($matches[idx]).addClass('sv-match-current');
            $cur[0].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
            $matchCount.text((idx + 1) + ' / ' + $matches.length + ' match' + ($matches.length !== 1 ? 'es' : ''));
        }

        $liveSearch.on('input', function() {
            clearTimeout(liveTimer);
            liveTimer = setTimeout(function() { runLiveSearch($liveSearch.val()); }, 120);
        });
        $liveSearch.on('keydown', function(e) {
            if (e.key === 'Enter')  { e.preventDefault(); scrollToMatch(matchIdx + 1); }
            if (e.key === 'Escape') { clearLiveSearch(); }
        });
        $clearBtn.on('click', clearLiveSearch);
        $('#sv-search-nav-next').on('click', function() { scrollToMatch(matchIdx + 1); });
        $('#sv-search-nav-prev').on('click', function() { scrollToMatch(matchIdx - 1); });

        // ── Folder expand / collapse ──────────────────────────────────
        // Delegated on #sv-tree-scroll so it works for both the initially
        // rendered tree and any lazy-loaded sub-tree added later.
        $('#sv-tree-scroll').on('click keydown', '.sv-folder-header', function(e) {
            if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
            e.preventDefault();
            e.stopPropagation();
            var $header  = $(this);
            var $subtree = $header.next('.sv-subtree, .sv-nested-subtree');
            if (!$subtree.length) return;
            var $toggle  = $header.find('.sv-toggle');
            if ($subtree.is(':visible')) {
                $subtree.slideUp(200, function() { $toggle.text('+'); });
            } else {
                $subtree.slideDown(200, function() { $toggle.text('−'); });
            }
        });

        // ── Keyboard navigation through the file tree ─────────────────
        // Arrow keys walk visible items; Enter toggles folders / opens files.
        $(document).on('keydown', function(e) {
            // Only engage when focus is on a tree item
            var $focused = $('.sv-folder-header:focus, .sv-file-name:focus');
            if (!$focused.length) return;
            // Skip when a search/filter result container is visible
            if ($('.sv-container').is(':visible') === false) return;

            switch (e.key) {
                case 'ArrowRight':
                    e.preventDefault();
                    if ($focused.hasClass('sv-folder-header')
                        && !$focused.next('.sv-subtree, .sv-nested-subtree').is(':visible')) {
                        $focused.trigger('click');
                    }
                    break;
                case 'ArrowLeft':
                    e.preventDefault();
                    if ($focused.hasClass('sv-folder-header')
                        && $focused.next('.sv-subtree, .sv-nested-subtree').is(':visible')) {
                        $focused.trigger('click');
                    }
                    break;
                case 'ArrowDown':
                case 'ArrowUp':
                    e.preventDefault();
                    var items = $('.sv-tree:visible').find('.sv-folder-header:visible, .sv-file-name:visible');
                    var idx   = items.index($focused);
                    var next  = e.key === 'ArrowDown' ? idx + 1 : idx - 1;
                    if (next >= 0 && next < items.length) $(items[next]).focus();
                    break;
                case 'Enter':
                    // Let natural <a> activation handle file links
                    if ($focused.hasClass('sv-folder-header')) {
                        e.preventDefault();
                        $focused.trigger('click');
                    }
                    break;
            }
        });

        // ── Expand/collapse wired to new .sv-tree-panel ───────────────
        $('#sv-tree-panel').on('click', '.sv-expand-all', function(e) {
            e.preventDefault();
            var treeClass = $(this).closest('.sv-controls').data('tree-class');
            var $scope = treeClass ? $('.' + treeClass) : $('#sv-structure-display');
            $scope.find('.sv-subtree, .sv-nested-subtree').each(function() {
                var $sub = $(this);
                if (!$sub.is(':visible')) {
                    $sub.slideDown(200, function() { $sub.prev('.sv-folder-header').find('.sv-toggle').text('−'); });
                }
            });
        });
        $('#sv-tree-panel').on('click', '.sv-collapse-all', function(e) {
            e.preventDefault();
            var treeClass = $(this).closest('.sv-controls').data('tree-class');
            var $scope = treeClass ? $('.' + treeClass) : $('#sv-structure-display');
            $scope.find('.sv-subtree, .sv-nested-subtree').each(function() {
                var $sub = $(this);
                if ($sub.is(':visible')) {
                    $sub.slideUp(200, function() { $sub.prev('.sv-folder-header').find('.sv-toggle').text('+'); });
                }
            });
        });

        // ── File viewer modal: close + fullscreen ─────────────────────
        var $fvModal = $('#sv-file-viewer-modal');

        function updateFsIcon() {
            var isFs = $fvModal.hasClass('sv-modal-fullscreen');
            $('#sv-fullscreen-modal').html(isFs
                ? '<svg viewBox="0 0 24 24" fill="none" width="15" height="15"><path d="M8 3v5H3M16 3v5h5M8 21v-5H3M16 21v-5h5" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/></svg>'
                : '<svg viewBox="0 0 24 24" fill="none" width="15" height="15"><path d="M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg>'
            );
        }
        updateFsIcon();

        $fvModal.on('click', function(e) {
            if (!$(e.target).closest('.sv-modal-content').length) {
                $fvModal.fadeOut(200);
                $fvModal.removeClass('sv-modal-fullscreen');
                updateFsIcon();
            }
        });
        $fvModal.find('.sv-modal-close').on('click', function(e) {
            e.stopPropagation();
            $fvModal.fadeOut(200);
            $fvModal.removeClass('sv-modal-fullscreen');
            updateFsIcon();
        });
        $fvModal.find('.sv-modal-content').on('click', function(e) { e.stopPropagation(); });

        $('#sv-fullscreen-modal').on('click', function(e) {
            e.stopPropagation();
            $fvModal.toggleClass('sv-modal-fullscreen');
            updateFsIcon();
        });

        // Reset fullscreen every time a new file is opened
        $(document).on('sv:file-modal-open', function() {
            $fvModal.removeClass('sv-modal-fullscreen');
            updateFsIcon();
        });

        // Patch StructureViewerFileViewer.showFileModal to fire sv:file-modal-open
        $(document).ready(function() {
            if (window.StructureViewerFileViewer && window.StructureViewerFileViewer.showFileModal) {
                var orig = window.StructureViewerFileViewer.showFileModal.bind(window.StructureViewerFileViewer);
                window.StructureViewerFileViewer.showFileModal = function(data) {
                    $(document).trigger('sv:file-modal-open');
                    orig(data);
                };
            }
        });

        // ── Export modal ──────────────────────────────────────────────
        $('#sv-export-modal .sv-export-modal-close').on('click', function() { $('#sv-export-modal').fadeOut(200); });
        $('#sv-export-modal').on('click', function(e) {
            if ($(e.target).is('#sv-export-modal')) { $(this).fadeOut(200); }
        });

        // ── Post-purchase welcome modal ───────────────────────────────
        function svOpenWelcomeModal() {
            $('#sv-welcome-modal').fadeIn(200);
        }

        // Close on X button or overlay click
        $('#sv-welcome-modal .sv-welcome-modal-close').on('click', function() {
            $('#sv-welcome-modal').fadeOut(200);
        });
        $('#sv-welcome-modal').on('click', function(e) {
            if ($(e.target).is('#sv-welcome-modal')) { $(this).fadeOut(200); }
        });

        // "What's Next?" button in the active licence card
        $(document).on('click', '#sv-whats-next-btn', function() {
            svOpenWelcomeModal();
        });

        // Auto-trigger once after licence activation (transient consumed server-side)
        if ( structureviewerSettings.show_welcome_modal ) {
            // Small delay so the page fully renders before the modal appears
            setTimeout(function() {
                svOpenWelcomeModal();
            }, 600);
        }

        // ── Settings: exclude patterns ────────────────────────────────
        $(document).on('click', '.sv-pattern-remove', function() { $(this).closest('.sv-pattern-tag').remove(); });
        $('#sv-add-pattern-btn').on('click', function() {
            var val = window.prompt('Enter a pattern to exclude (e.g. node_modules, *.log)');
            if (val && val.trim()) {
                var pattern = val.trim();
                var $tag = $(
                    '<span class="sv-pattern-tag" data-pattern="' + svEscAttr(pattern) + '">' +
                    svEscHtml(pattern) +
                    '<button type="button" class="sv-pattern-remove" aria-label="Remove">×</button></span>'
                );
                $(this).before($tag);
            }
        });

        // ── Helper: collect all settings ─────────────────────────────
        function collectAllSettings() {
            var patterns = [];
            $('#sv-exclude-patterns .sv-pattern-tag').each(function() { patterns.push($(this).data('pattern')); });
            return {
                max_file_size:          $('#sv-setting-max-file-size').val(),
                show_hidden_files:      $('#sv-setting-show-hidden').is(':checked') ? '0' : '1',
                exclude_system_dirs:    $('#sv-setting-exclude-system').is(':checked') ? '1' : '0',
                keyboard_navigation:    $('#sv-setting-keyboard-nav').is(':checked') ? '1' : '0',
                show_file_info:         $('#sv-show-file-info').is(':checked') ? '1' : '0',
                dark_mode:              $('#sv-setting-dark-mode').is(':checked') ? '1' : '0',
                accent_color:           $('#sv-setting-accent-color').val(),
                copy_format:            $('input[name="sv_copy_format"]:checked').val() || 'json',
                exclude_patterns:       patterns
            };
        }

        // ── Toast notification ────────────────────────────────────────
        var toastTimer = null;
        function showSavedToast() {
            var $toast = $('#sv-settings-toast');
            $toast.addClass('sv-toast-visible');
            clearTimeout(toastTimer);
            toastTimer = setTimeout(function() {
                $toast.removeClass('sv-toast-visible');
            }, 2500);
        }

        // ── Auto-save settings on any change ─────────────────────────
        var autoSaveTimer = null;
        function triggerAutoSave() {
            clearTimeout(autoSaveTimer);
            autoSaveTimer = setTimeout(function() {
                $.ajax({
                    url: structureviewerSettings.ajaxurl,
                    type: 'POST',
                    data: {
                        action:   'structureviewer_save_settings',
                        nonce:    structureviewerSettings.nonce,
                        settings: collectAllSettings()
                    },
                    success: function(response) {
                        if (response.success) showSavedToast();
                    }
                });
            }, 600); // 600ms debounce – waits for user to finish adjusting
        }

        // Watch every settings control for changes
        $(document).on('change', '#sv-tab-settings input, #sv-tab-settings select', function() {
            triggerAutoSave();
        });
        // Accent swatches
        $(document).on('click', '.sv-accent-swatch', function() {
            triggerAutoSave();
        });
        // Copy format pills
        $(document).on('change', 'input[name="sv_copy_format"]', function() {
            $('.sv-copy-format-pill').removeClass('active');
            $(this).closest('.sv-copy-format-pill').addClass('active');
            triggerAutoSave();
        });
        // Pattern add/remove
        $(document).on('click', '.sv-pattern-remove, #sv-add-pattern-btn', function() {
            setTimeout(triggerAutoSave, 100);
        });


        // ══════════════════════════════════════════════════════
        //  PRO COMPARATOR — SVC (Structure Viewer Comparator)
        // ══════════════════════════════════════════════════════
        if ($('#sv-tab-comparator .pc-mode-grid').length) {

        var SVC = {
            currentMode:    'zip',
            zip1Ready:      false,
            zip2Ready:      false,
            pluginZipReady: false,
            themeZipReady:  false,
            file1Ready:     false,
            file2Ready:     false,

            init: function() { this.bindEvents(); },

            bindEvents: function() {
                var self = this;

                // Mode tiles
                $(document).on('click', '.pc-mode-item', function() {
                    self.switchMode($(this).data('mode'));
                });

                // ZIP uploads
                $('#zip-file-1').on('change', function() { self.uploadZip(1, this.files[0]); });
                $('#zip-file-2').on('change', function() { self.uploadZip(2, this.files[0]); });

                // Plugin/theme ZIP
                $('#plugin-zip-file').on('change', function() { self.uploadPluginThemeZip(this.files[0], 'plugin'); });
                $('#theme-zip-file').on('change', function()  { self.uploadPluginThemeZip(this.files[0], 'theme'); });

                // Single files
                $('#single-file-1').on('change', function() { self.uploadSingleFile(1, this.files[0]); });
                $('#single-file-2').on('change', function() { self.uploadSingleFile(2, this.files[0]); });

                // Compare buttons
                $('#btn-compare-zip').on('click',   function() { self.compare('zip'); });
                $('#btn-compare-plugin').on('click', function() { self.compare('plugin'); });
                $('#btn-compare-theme').on('click',  function() { self.compare('theme'); });
                $('#btn-compare-files').on('click',  function() { self.compareFiles(); });

                // Select change
                $('#installed-plugin').on('change', function() { self.checkPluginReady(); });
                $('#installed-theme').on('change',  function() { self.checkThemeReady(); });

                // Reset
                $('#btn-reset').on('click', function() { self.reset(); });

                // Result expand/collapse
                $(document).on('click', '.pc-file-header',         function() { self.toggleDiff($(this)); });
                $(document).on('click', '.pc-added-file-header',   function() { self.toggleContent($(this), 'added'); });
                $(document).on('click', '.pc-removed-file-header', function() { self.toggleContent($(this), 'removed'); });
                $(document).on('click', '#btn-expand-all',         function() { self.expandAll(); });
                $(document).on('click', '#btn-collapse-all',       function() { self.collapseAll(); });
                $(document).on('click', '#btn-toggle-unchanged',   function() { $('#unchanged-list').toggleClass('pc-hidden'); });
            },

            switchMode: function(mode) {
                this.currentMode = mode;
                $('.pc-mode-item').removeClass('active');
                $('.pc-mode-item[data-mode="' + mode + '"]').addClass('active');
                $('.pc-upload-card').hide();
                $('#mode-' + mode).fadeIn(250);
                $('#pc-results').hide().empty();
            },

            // ── Uploads ────────────────────────────────
            uploadZip: function(num, file) {
                var self = this;
                if (!file) return;
                var $box = $('#zip-box-' + num), $status = $('#zip-status-' + num), $prog = $('#zip-progress-' + num);
                $box.removeClass('uploaded error');
                $status.html('');
                $prog.addClass('active').find('.pc-progress-bar').css('width', '0%');

                var fd = new FormData();
                fd.append('action',     'structureviewer_comp_upload');
                fd.append('nonce',      structureviewerSettings.nonce);
                fd.append('plugin_num', num);
                fd.append('plugin' + num, file);

                $.ajax({
                    url: structureviewerSettings.ajaxurl, type: 'POST',
                    data: fd, processData: false, contentType: false,
                    xhr: function() {
                        var x = new XMLHttpRequest();
                        x.upload.addEventListener('progress', function(e) {
                            if (e.lengthComputable) $prog.find('.pc-progress-bar').css('width', (e.loaded / e.total * 100) + '%');
                        });
                        return x;
                    },
                    success: function(res) {
                        $prog.removeClass('active');
                        if (res.success) {
                            $box.addClass('uploaded');
                            $status.html('<span class="pc-success">✓ ' + self.esc(res.data.name) + '</span>');
                            if (num === 1) self.zip1Ready = true;
                            if (num === 2) self.zip2Ready = true;
                            self.checkZipReady();
                        } else {
                            $box.addClass('error');
                            $status.html('<span class="pc-error">✗ ' + self.esc(res.data.message) + '</span>');
                        }
                    },
                    error: function() {
                        $prog.removeClass('active'); $box.addClass('error');
                        $status.html('<span class="pc-error">✗ Upload failed</span>');
                    }
                });
            },

            uploadPluginThemeZip: function(file, type) {
                var self = this;
                if (!file) return;
                var $box = $('#' + type + '-zip-box'), $status = $('#' + type + '-zip-status'), $prog = $('#' + type + '-zip-progress');
                $box.removeClass('uploaded error'); $status.html('');
                $prog.addClass('active').find('.pc-progress-bar').css('width', '0%');

                var fd = new FormData();
                fd.append('action',     'structureviewer_comp_upload');
                fd.append('nonce',      structureviewerSettings.nonce);
                fd.append('plugin_num', '1');
                fd.append('plugin1',    file);

                $.ajax({
                    url: structureviewerSettings.ajaxurl, type: 'POST',
                    data: fd, processData: false, contentType: false,
                    xhr: function() {
                        var x = new XMLHttpRequest();
                        x.upload.addEventListener('progress', function(e) {
                            if (e.lengthComputable) $prog.find('.pc-progress-bar').css('width', (e.loaded / e.total * 100) + '%');
                        });
                        return x;
                    },
                    success: function(res) {
                        $prog.removeClass('active');
                        if (res.success) {
                            $box.addClass('uploaded');
                            $status.html('<span class="pc-success">✓ ' + self.esc(res.data.name) + '</span>');
                            if (type === 'plugin') { self.pluginZipReady = true; self.checkPluginReady(); }
                            else                   { self.themeZipReady  = true; self.checkThemeReady(); }
                        } else {
                            $box.addClass('error');
                            $status.html('<span class="pc-error">✗ ' + self.esc(res.data.message) + '</span>');
                        }
                    },
                    error: function() {
                        $prog.removeClass('active'); $box.addClass('error');
                        $status.html('<span class="pc-error">✗ Upload failed</span>');
                    }
                });
            },

            uploadSingleFile: function(num, file) {
                var self = this;
                if (!file) return;
                var $box = $('#file-box-' + num), $status = $('#file-status-' + num), $prog = $('#file-progress-' + num);
                $box.removeClass('uploaded error'); $status.html('');
                $prog.addClass('active').find('.pc-progress-bar').css('width', '0%');

                var fd = new FormData();
                fd.append('action',   'structureviewer_comp_upload_file');
                fd.append('nonce',    structureviewerSettings.nonce);
                fd.append('file_num', num);
                fd.append('file' + num, file);

                $.ajax({
                    url: structureviewerSettings.ajaxurl, type: 'POST',
                    data: fd, processData: false, contentType: false,
                    xhr: function() {
                        var x = new XMLHttpRequest();
                        x.upload.addEventListener('progress', function(e) {
                            if (e.lengthComputable) $prog.find('.pc-progress-bar').css('width', (e.loaded / e.total * 100) + '%');
                        });
                        return x;
                    },
                    success: function(res) {
                        $prog.removeClass('active');
                        if (res.success) {
                            $box.addClass('uploaded');
                            $status.html('<span class="pc-success">✓ ' + self.esc(res.data.name) + ' (' + res.data.size + ')</span>');
                            if (num === 1) self.file1Ready = true;
                            if (num === 2) self.file2Ready = true;
                            self.checkFilesReady();
                        } else {
                            $box.addClass('error');
                            $status.html('<span class="pc-error">✗ ' + self.esc(res.data.message) + '</span>');
                        }
                    },
                    error: function() {
                        $prog.removeClass('active'); $box.addClass('error');
                        $status.html('<span class="pc-error">✗ Upload failed</span>');
                    }
                });
            },

            // ── Ready checks ───────────────────────────
            checkZipReady:    function() { $('#btn-compare-zip').prop('disabled',    !(this.zip1Ready && this.zip2Ready)); },
            checkPluginReady: function() { $('#btn-compare-plugin').prop('disabled', !(this.pluginZipReady && $('#installed-plugin').val())); },
            checkThemeReady:  function() { $('#btn-compare-theme').prop('disabled',  !(this.themeZipReady  && $('#installed-theme').val())); },
            checkFilesReady:  function() { $('#btn-compare-files').prop('disabled',  !(this.file1Ready && this.file2Ready)); },

            // ── Compare ────────────────────────────────
            compare: function(mode) {
                var self = this;
                $('#pc-loading').show(); $('#pc-results').hide().empty();

                var data = { action: 'structureviewer_comp_compare', nonce: structureviewerSettings.nonce, mode: mode };
                if (mode === 'plugin') { data.installed_slug = $('#installed-plugin').val(); data.compare_as = $('input[name="plugin-compare-as"]:checked').val(); }
                if (mode === 'theme')  { data.installed_slug = $('#installed-theme').val();  data.compare_as = $('input[name="theme-compare-as"]:checked').val(); }

                $.ajax({
                    url: structureviewerSettings.ajaxurl, type: 'POST', data: data, timeout: 300000,
                    success: function(res) {
                        $('#pc-loading').hide();
                        if (res.success) self.renderResults(res.data);
                        else self.showError((res.data && res.data.message) || 'Comparison failed.');
                    },
                    error: function(xhr, status) { $('#pc-loading').hide(); self.showError('Comparison failed: ' + status); }
                });
            },

            compareFiles: function() {
                var self = this;
                $('#pc-loading').show(); $('#pc-results').hide().empty();
                $.ajax({
                    url: structureviewerSettings.ajaxurl, type: 'POST', timeout: 120000,
                    data: { action: 'structureviewer_comp_compare_files', nonce: structureviewerSettings.nonce },
                    success: function(res) {
                        $('#pc-loading').hide();
                        if (res.success) self.renderFileResults(res.data);
                        else self.showError((res.data && res.data.message) || 'Comparison failed.');
                    },
                    error: function(xhr, status) { $('#pc-loading').hide(); self.showError('Comparison failed: ' + status); }
                });
            },

            // ── Results rendering ───────────────────────
            renderResults: function(data) {
                var self = this, html = '';

                html += '<div class="pc-results-header">';
                html += '<h2><svg viewBox="0 0 24 24" fill="none" width="18" height="18"><path d="M20 6L9 17l-5-5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg> Comparison Complete</h2>';
                html += '<p class="pc-comparing">Comparing <strong>' + self.esc(data.plugin1_name) + '</strong> with <strong>' + self.esc(data.plugin2_name) + '</strong></p>';
                html += '</div>';

                html += '<div class="pc-summary-grid">';
                html += '<div class="pc-summary-box pc-box-added"><span class="pc-number">'   + data.added.length     + '</span><span class="pc-label">Added</span></div>';
                html += '<div class="pc-summary-box pc-box-removed"><span class="pc-number">' + data.removed.length   + '</span><span class="pc-label">Removed</span></div>';
                html += '<div class="pc-summary-box pc-box-modified"><span class="pc-number">'+ data.modified.length  + '</span><span class="pc-label">Modified</span></div>';
                html += '<div class="pc-summary-box pc-box-unchanged"><span class="pc-number">'+ data.unchanged.length + '</span><span class="pc-label">Unchanged</span></div>';
                html += '</div>';

                html += '<div class="pc-stats-bar">';
                html += '<span class="pc-stat-add">+' + (data.stats.total_added   || 0) + ' lines</span>';
                html += '<span class="pc-stat-del">−' + (data.stats.total_removed || 0) + ' lines</span>';
                html += '</div>';

                if (data.modified.length || data.added.length || data.removed.length) {
                    html += '<div class="pc-actions" style="margin-bottom:20px;">';
                    html += '<button type="button" id="btn-expand-all"   class="pc-btn pc-btn-secondary">Expand All</button>';
                    html += '<button type="button" id="btn-collapse-all" class="pc-btn pc-btn-secondary">Collapse All</button>';
                    html += '</div>';
                }

                if (data.modified.length) {
                    html += '<div class="pc-section"><div class="pc-section-header"><h3><svg viewBox="0 0 24 24" fill="none" width="15" height="15"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg> Modified Files (' + data.modified.length + ')</h3></div>';
                    for (var i = 0; i < data.modified.length; i++) {
                        var f = data.modified[i];
                        html += '<div class="pc-file pc-file-modified" data-file="' + self.escAttr(f.file) + '" data-can-diff="' + (f.can_diff ? 1 : 0) + '">';
                        html += '<div class="pc-file-header"><div class="pc-file-path"><svg class="pc-toggle-icon" viewBox="0 0 24 24" fill="none" width="14" height="14"><path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg><code>' + self.esc(f.file) + '</code></div>';
                        html += '<div class="pc-file-meta">';
                        if (f.can_diff) { html += '<span class="pc-badge pc-badge-add">+</span><span class="pc-badge pc-badge-del">−</span>'; }
                        else { html += '<span class="pc-badge pc-badge-binary">' + (f.is_text ? 'Large' : 'Binary') + '</span>'; }
                        html += '<span class="pc-file-size">' + self.fmtSize(f.old_size) + ' → ' + self.fmtSize(f.new_size) + '</span>';
                        html += '</div></div><div class="pc-diff-container"></div></div>';
                    }
                    html += '</div>';
                }

                if (data.added.length) {
                    html += '<div class="pc-section"><div class="pc-section-header"><h3><svg viewBox="0 0 24 24" fill="none" width="15" height="15"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="1.8"/><path d="M12 8v8M8 12h8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg> Added Files (' + data.added.length + ')</h3></div>';
                    for (var j = 0; j < data.added.length; j++) {
                        var af = data.added[j];
                        html += '<div class="pc-file pc-file-added" data-file="' + self.escAttr(af.file) + '" data-can-view="' + (af.can_view ? 1 : 0) + '">';
                        html += '<div class="pc-added-file-header"><div class="pc-file-path"><svg class="pc-toggle-icon" viewBox="0 0 24 24" fill="none" width="14" height="14"><path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg><code>' + self.esc(af.file) + '</code></div>';
                        html += '<div class="pc-file-meta">';
                        if (af.can_view) { html += '<span class="pc-badge pc-badge-add">View</span>'; }
                        else { html += '<span class="pc-badge pc-badge-binary">' + (af.is_text ? 'Large' : 'Binary') + '</span>'; }
                        html += '<span class="pc-file-size">' + self.fmtSize(af.size) + '</span>';
                        html += '</div></div><div class="pc-content-container"></div></div>';
                    }
                    html += '</div>';
                }

                if (data.removed.length) {
                    html += '<div class="pc-section"><div class="pc-section-header"><h3><svg viewBox="0 0 24 24" fill="none" width="15" height="15"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="1.8"/><path d="M8 12h8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg> Removed Files (' + data.removed.length + ')</h3></div>';
                    for (var k = 0; k < data.removed.length; k++) {
                        var rf = data.removed[k];
                        html += '<div class="pc-file pc-file-removed" data-file="' + self.escAttr(rf.file) + '" data-can-view="' + (rf.can_view ? 1 : 0) + '">';
                        html += '<div class="pc-removed-file-header"><div class="pc-file-path"><svg class="pc-toggle-icon" viewBox="0 0 24 24" fill="none" width="14" height="14"><path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg><code>' + self.esc(rf.file) + '</code></div>';
                        html += '<div class="pc-file-meta">';
                        if (rf.can_view) { html += '<span class="pc-badge pc-badge-del">View</span>'; }
                        else { html += '<span class="pc-badge pc-badge-binary">' + (rf.is_text ? 'Large' : 'Binary') + '</span>'; }
                        html += '<span class="pc-file-size">' + self.fmtSize(rf.size) + '</span>';
                        html += '</div></div><div class="pc-content-container"></div></div>';
                    }
                    html += '</div>';
                }

                if (data.unchanged.length) {
                    html += '<div class="pc-section"><div class="pc-section-header"><h3><svg viewBox="0 0 24 24" fill="none" width="15" height="15"><path d="M20 6L9 17l-5-5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg> Unchanged Files (' + data.unchanged.length + ')</h3>';
                    html += '<button type="button" id="btn-toggle-unchanged" class="pc-btn pc-btn-secondary">Show/Hide</button></div>';
                    html += '<ul class="pc-file-list pc-hidden" id="unchanged-list">';
                    for (var l = 0; l < data.unchanged.length; l++) {
                        html += '<li><code>' + self.esc(data.unchanged[l].file) + '</code><span>' + self.fmtSize(data.unchanged[l].size) + '</span></li>';
                    }
                    html += '</ul></div>';
                }

                $('#pc-results').html(html).show();
                $('html, body').animate({ scrollTop: $('#pc-results').offset().top - 80 }, 400);
            },

            renderFileResults: function(data) {
                var self = this, html = '';
                html += '<div class="pc-results-header"><h2><svg viewBox="0 0 24 24" fill="none" width="18" height="18"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M14 2v6h6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg> File Comparison</h2></div>';
                html += '<div class="pc-file-compare-header">';
                html += '<div class="pc-file-info-box"><h4>Old File</h4><div class="pc-filename">' + self.esc(data.file1.name) + '</div><div class="pc-filesize">' + self.fmtSize(data.file1.size) + '</div></div>';
                html += '<div class="pc-file-vs">VS</div>';
                html += '<div class="pc-file-info-box"><h4>New File</h4><div class="pc-filename">' + self.esc(data.file2.name) + '</div><div class="pc-filesize">' + self.fmtSize(data.file2.size) + '</div></div>';
                html += '</div>';

                if (data.identical) {
                    html += '<div class="pc-text-center" style="margin:32px 0;"><span class="pc-identical-badge"><svg viewBox="0 0 24 24" fill="none" width="16" height="16"><path d="M20 6L9 17l-5-5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg> Files are identical</span></div>';
                } else if (data.can_diff && data.diff) {
                    html += '<div class="pc-stats-bar"><span class="pc-stat-add">+' + data.diff.added + ' lines</span><span class="pc-stat-del">−' + data.diff.removed + ' lines</span></div>';
                    html += '<div class="pc-diff-container" style="display:block;"><table class="pc-diff-table">';
                    for (var i = 0; i < data.diff.lines.length; i++) {
                        var ln = data.diff.lines[i];
                        var cls = ln.type === 'add' ? 'pc-line-add' : (ln.type === 'del' ? 'pc-line-del' : 'pc-line-same');
                        var pfx = ln.type === 'add' ? '+' : (ln.type === 'del' ? '−' : ' ');
                        html += '<tr class="' + cls + '"><td class="pc-ln">' + (ln.old_num || '') + '</td><td class="pc-ln">' + (ln.new_num || '') + '</td><td><pre>' + self.esc(pfx + ' ' + (ln.content || '')) + '</pre></td></tr>';
                    }
                    html += '</table></div>';
                } else {
                    html += '<div class="pc-text-center" style="margin:32px 0;color:var(--sv-text-3);font-size:13px;">Files differ but cannot show diff (binary or too large)</div>';
                }

                $('#pc-results').html(html).show();
                $('html, body').animate({ scrollTop: $('#pc-results').offset().top - 80 }, 400);
            },

            // ── Toggle diff / content ───────────────────
            toggleDiff: function($header) {
                var self = this, $file = $header.closest('.pc-file'), $c = $file.find('.pc-diff-container');
                var $icon = $header.find('.pc-toggle-icon'), file = $file.data('file'), canDiff = $file.data('can-diff') == 1;
                if ($c.is(':visible')) {
                    $c.slideUp(200); $icon.removeClass('open');
                } else {
                    $icon.addClass('open');
                    if ($c.html() === '' && canDiff) {
                        $c.html('<div class="pc-diff-loading"><div class="pc-spinner"></div>Loading…</div>').slideDown(200);
                        self.loadDiff(file, $c);
                    } else if (!canDiff) { $c.html('<div class="pc-diff-loading">Cannot display diff for this file</div>').slideDown(200); }
                    else { $c.slideDown(200); }
                }
            },

            toggleContent: function($header, type) {
                var self = this, $file = $header.closest('.pc-file'), $c = $file.find('.pc-content-container');
                var $icon = $header.find('.pc-toggle-icon'), file = $file.data('file'), canView = $file.data('can-view') == 1;
                if ($c.is(':visible')) {
                    $c.slideUp(200); $icon.removeClass('open');
                } else {
                    $icon.addClass('open');
                    if ($c.html() === '' && canView) {
                        $c.html('<div class="pc-diff-loading"><div class="pc-spinner"></div>Loading…</div>').slideDown(200);
                        self.loadContent(file, type, $c);
                    } else if (!canView) { $c.html('<div class="pc-diff-loading">Cannot view this file</div>').slideDown(200); }
                    else { $c.slideDown(200); }
                }
            },

            loadDiff: function(file, $c) {
                var self = this;
                $.ajax({
                    url: structureviewerSettings.ajaxurl, type: 'POST', timeout: 120000,
                    data: { action: 'structureviewer_comp_get_diff', nonce: structureviewerSettings.nonce, file: file },
                    success: function(res) {
                        if (res.success) self.renderDiff(res.data, $c);
                        else $c.html('<div class="pc-diff-loading">' + self.esc((res.data && res.data.message) || 'Failed to load diff') + '</div>');
                    },
                    error: function() { $c.html('<div class="pc-diff-loading">Failed to load diff</div>'); }
                });
            },

            loadContent: function(file, type, $c) {
                var self = this;
                $.ajax({
                    url: structureviewerSettings.ajaxurl, type: 'POST', timeout: 120000,
                    data: { action: 'structureviewer_comp_get_file_content', nonce: structureviewerSettings.nonce, file: file, type: type },
                    success: function(res) {
                        if (res.success) self.renderContent(res.data, $c, type);
                        else $c.html('<div class="pc-diff-loading">' + self.esc((res.data && res.data.message) || 'Failed to load content') + '</div>');
                    },
                    error: function() { $c.html('<div class="pc-diff-loading">Failed to load content</div>'); }
                });
            },

            renderDiff: function(data, $c) {
                var self = this;
                if (data.too_large) { $c.html('<div class="pc-diff-loading">' + self.esc(data.message) + '</div>'); return; }
                var html = '<table class="pc-diff-table">';
                for (var i = 0; i < data.lines.length; i++) {
                    var ln = data.lines[i];
                    var cls = ln.type === 'add' ? 'pc-line-add' : (ln.type === 'del' ? 'pc-line-del' : 'pc-line-same');
                    var pfx = ln.type === 'add' ? '+' : (ln.type === 'del' ? '−' : ' ');
                    html += '<tr class="' + cls + '"><td class="pc-ln">' + (ln.old_num || '') + '</td><td class="pc-ln">' + (ln.new_num || '') + '</td><td><pre>' + self.esc(pfx + ' ' + (ln.content || '')) + '</pre></td></tr>';
                }
                html += '</table>';
                $c.closest('.pc-file').find('.pc-badge-add').text('+' + data.added);
                $c.closest('.pc-file').find('.pc-badge-del').text('−' + data.removed);
                $c.html(html);
            },

            renderContent: function(data, $c, type) {
                var self = this;
                if (data.too_large) { $c.html('<div class="pc-diff-loading">' + self.esc(data.message) + '</div>'); return; }
                var cls = type === 'added' ? 'pc-line-add' : 'pc-line-del';
                var pfx = type === 'added' ? '+' : '−';
                var html = '<table class="pc-diff-table">';
                for (var i = 0; i < data.lines.length; i++) {
                    html += '<tr class="' + cls + '"><td class="pc-ln">' + data.lines[i].num + '</td><td><pre>' + self.esc(pfx + ' ' + (data.lines[i].content || '')) + '</pre></td></tr>';
                }
                html += '</table>';
                var $badge = $c.closest('.pc-file').find(type === 'added' ? '.pc-badge-add' : '.pc-badge-del');
                $badge.text((type === 'added' ? '+' : '−') + data.total_lines + ' lines');
                $c.html(html);
            },

            expandAll: function() {
                var self = this;
                $('.pc-file-modified').each(function() {
                    var $f = $(this), $c = $f.find('.pc-diff-container'), file = $f.data('file');
                    $f.find('.pc-toggle-icon').addClass('open');
                    if ($c.html() === '' && $f.data('can-diff') == 1) { $c.html('<div class="pc-diff-loading">Loading…</div>').show(); self.loadDiff(file, $c); }
                    else { $c.show(); }
                });
                $('.pc-file-added').each(function() {
                    var $f = $(this), $c = $f.find('.pc-content-container'), file = $f.data('file');
                    $f.find('.pc-toggle-icon').addClass('open');
                    if ($c.html() === '' && $f.data('can-view') == 1) { $c.html('<div class="pc-diff-loading">Loading…</div>').show(); self.loadContent(file, 'added', $c); }
                    else { $c.show(); }
                });
                $('.pc-file-removed').each(function() {
                    var $f = $(this), $c = $f.find('.pc-content-container'), file = $f.data('file');
                    $f.find('.pc-toggle-icon').addClass('open');
                    if ($c.html() === '' && $f.data('can-view') == 1) { $c.html('<div class="pc-diff-loading">Loading…</div>').show(); self.loadContent(file, 'removed', $c); }
                    else { $c.show(); }
                });
            },

            collapseAll: function() {
                $('.pc-diff-container, .pc-content-container').hide();
                $('.pc-toggle-icon').removeClass('open');
            },

            reset: function() {
                var self = this;
                $.post(structureviewerSettings.ajaxurl, { action: 'structureviewer_comp_cleanup', nonce: structureviewerSettings.nonce });
                self.zip1Ready = self.zip2Ready = self.pluginZipReady = self.themeZipReady = self.file1Ready = self.file2Ready = false;
                $('input[type="file"]').val('');
                $('.pc-upload-box').removeClass('uploaded error');
                $('.pc-upload-status').html('');
                $('.pc-progress').removeClass('active');
                $('select').val('');
                $('input[type="radio"][value="old"]').prop('checked', true);
                $('button[id^="btn-compare"]').prop('disabled', true);
                $('#pc-results').hide().empty();
            },

            showError: function(msg) {
                $('#pc-results').html('<div class="pc-notice pc-notice-error">✗ ' + this.esc(msg) + '</div>').show();
            },

            esc: function(str) {
                if (!str) return '';
                return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
            },

            escAttr: function(str) { return this.esc(str).replace(/'/g, '&#39;'); },

            fmtSize: function(bytes) {
                if (!bytes) return '0 B';
                if (bytes >= 1048576) return (bytes / 1048576).toFixed(2) + ' MB';
                if (bytes >= 1024)    return (bytes / 1024).toFixed(2) + ' KB';
                return bytes + ' B';
            }
        };

        SVC.init();

        } // end comparator guard

    }); // end ready