/**
 * Blaminhor Essentials Module-Specific JavaScript
 *
 * @package BlaminhorEssentials
 */

(function($) {
    'use strict';

    // Utility: escape HTML for safe insertion into DOM strings.
    function self_escHtml(str) {
        if (!str) return '';
        var div = document.createElement('div');
        div.appendChild(document.createTextNode(String(str)));
        return div.innerHTML;
    }

    // Utility: escape for use in HTML attribute values.
    function self_escAttr(str) {
        if (!str) return '';
        return String(str).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    }

    /**
     * Truncate text for SERP preview using DOM measurement in the same CSS context.
     * Creates a hidden clone inside the element's parent so inherited styles match exactly.
     *
     * @param {HTMLElement} el        The visible element to update.
     * @param {string}      text      Full text.
     * @param {number}      maxLines  Maximum number of lines (1, 2, or 3).
     * @param {boolean}     wordBound True to cut at word boundaries (descriptions).
     */
    function serpTruncate(el, text, maxLines, wordBound) {
        if (!text) { el.textContent = ''; return; }

        // Find the SERP preview container to determine viewport
        var container = el.closest('.ap-serp-preview');
        var isMobile = container && container.classList.contains('mobile');

        // Google SERP widths (empirically measured): 520px desktop, 310px mobile
        var w = isMobile ? 310 : 520;

        var m = document.createElement('div');
        m.style.cssText =
            'position:absolute;visibility:hidden;pointer-events:none;' +
            'top:0;left:0;' +
            'height:auto;max-height:none;' +
            'width:' + w + 'px;' +
            'font:400 14px/1.58 arial,sans-serif;' +
            'letter-spacing:normal;word-spacing:normal;' +
            'padding:0;margin:0;border:0;' +
            'white-space:normal;overflow:visible;display:block;' +
            'box-sizing:content-box;';
        document.body.appendChild(m);

        m.textContent = 'Xg\u00C0q|';
        var lineH = m.scrollHeight;
        var maxH  = lineH * maxLines + 1;

        m.textContent = text;
        if (m.scrollHeight <= maxH) {
            document.body.removeChild(m);
            el.textContent = text;
            return;
        }

        var result = text;
        if (wordBound) {
            var words = text.split(' ');
            var lo = 1, hi = words.length, best = 0;
            while (lo <= hi) {
                var mid = (lo + hi) >> 1;
                m.textContent = words.slice(0, mid).join(' ') + '...';
                if (m.scrollHeight <= maxH) {
                    best = mid;
                    lo = mid + 1;
                } else {
                    hi = mid - 1;
                }
            }
            result = best > 0 ?
                words.slice(0, best).join(' ').replace(/\s+$/, '') + '...' : text;
        } else {
            var lo2 = 0, hi2 = text.length, best2 = 0;
            while (lo2 <= hi2) {
                var mid2 = (lo2 + hi2) >> 1;
                m.textContent = text.substring(0, mid2) + '...';
                if (m.scrollHeight <= maxH) {
                    best2 = mid2;
                    lo2 = mid2 + 1;
                } else {
                    hi2 = mid2 - 1;
                }
            }
            result = best2 > 0 ?
                text.substring(0, best2).replace(/\s+$/, '') + '...' : text;
        }

        document.body.removeChild(m);
        el.textContent = result;
    }

    /**
     * Image Sizes Module
     */
    window.BlaminhorEssentialsImageSizes = {
        nonce: '',
        ajaxurl: '',
        strings: {},
        images: [],
        currentIndex: 0,
        processed: 0,
        errors: 0,
        skipped: 0,
        isRunning: false,

        // Cleanup variables
        cleanupImages: [],
        cleanupIndex: 0,
        cleanupProcessed: 0,
        cleanupDeleted: 0,
        cleanupErrors: 0,
        isCleanupRunning: false,
        initialized: false,

        init: function(config) {
            if (this.initialized) return;
            this.initialized = true;

            this.nonce = config.nonce || '';
            this.ajaxurl = config.ajaxurl || ajaxurl;
            this.strings = config.strings || {};
            this.bindEvents();
        },

        bindEvents: function() {
            var self = this;
            var ns = '.blaminhorImageSizes';

            // Start regeneration
            $('#ap-start-regen').off('click' + ns).on('click' + ns, function() {
                self.startRegeneration();
            });

            // Stop regeneration
            $('#ap-stop-regen').off('click' + ns).on('click' + ns, function() {
                self.isRunning = false;
                $(this).hide();
                $('#ap-progress-status').text(self.strings.stopped || 'Stopped');
                self.finishRegeneration();
            });

            // Start cleanup
            $('#ap-start-cleanup').off('click' + ns).on('click' + ns, function() {
                self.startCleanup();
            });

            // Stop cleanup
            $('#ap-stop-cleanup').off('click' + ns).on('click' + ns, function() {
                self.isCleanupRunning = false;
                $(this).hide();
                $('#ap-cleanup-status').text(self.strings.stopped || 'Stopped');
                self.finishCleanup([]);
            });

            // Dismiss notice buttons
            $('.ap-dismiss-notice').off('click' + ns).on('click' + ns, function() {
                var action = $(this).data('action');
                var $notice = $(this).closest('.ap-action-notice');

                $.post(self.ajaxurl, {
                    action: action,
                    nonce: self.nonce
                });

                $notice.fadeOut();
            });
        },

        startRegeneration: function() {
            if (this.isRunning) return;

            var self = this;
            this.isRunning = true;
            this.currentIndex = 0;
            this.processed = 0;
            this.errors = 0;
            this.skipped = 0;

            $('#ap-start-regen').prop('disabled', true).find('.dashicons').addClass('ap-spin');
            $('#ap-stop-regen').show();
            $('#ap-regen-progress').show();
            $('#ap-regen-results').hide();
            $('#ap-progress-log').html('');
            $('#ap-progress-bar').css('width', '0%');
            $('#ap-progress-status').text(this.strings.loading || 'Loading images...');

            // Get all image IDs
            $.post(this.ajaxurl, {
                action: 'blaminhor_essentials_get_images',
                nonce: this.nonce
            }, function(response) {
                if (response.success) {
                    self.images = response.data.images;
                    $('#ap-progress-count').text('0 / ' + self.images.length);

                    if (self.images.length === 0) {
                        self.logMessage(self.strings.noImages || 'No images found in the library.', 'skip');
                        self.finishRegeneration();
                    } else {
                        self.processNextImage();
                    }
                } else {
                    self.logMessage(self.strings.errorLoading || 'Error loading images.', 'error');
                    self.finishRegeneration();
                }
            }).fail(function() {
                self.logMessage(self.strings.ajaxError || 'AJAX error.', 'error');
                self.finishRegeneration();
            });
        },

        processNextImage: function() {
            if (!this.isRunning || this.currentIndex >= this.images.length) {
                this.finishRegeneration();
                return;
            }

            var self = this;
            var imageId = this.images[this.currentIndex];
            var onlyMissing = $('#ap-regen-only-missing').is(':checked');

            $('#ap-progress-status').text(this.strings.processing || 'Processing...');

            $.post(this.ajaxurl, {
                action: 'blaminhor_essentials_regenerate_image',
                nonce: this.nonce,
                attachment_id: imageId,
                only_missing: onlyMissing
            }, function(response) {
                self.currentIndex++;
                self.processed++;

                var progress = Math.round((self.currentIndex / self.images.length) * 100);
                $('#ap-progress-bar').css('width', progress + '%');
                $('#ap-progress-count').text(self.currentIndex + ' / ' + self.images.length);

                if (response.success) {
                    if (response.data.skipped) {
                        self.skipped++;
                        self.logMessage(response.data.message, 'skip');
                    } else {
                        self.logMessage(response.data.message, 'success');
                    }
                } else {
                    self.errors++;
                    self.logMessage('ID ' + imageId + ': ' + (response.data ? response.data.message : 'Error'), 'error');
                }

                // Process next with a small delay to prevent overwhelming the server
                setTimeout(function() { self.processNextImage(); }, 100);
            }).fail(function() {
                self.currentIndex++;
                self.errors++;
                self.logMessage('ID ' + imageId + ': ' + (self.strings.requestFailed || 'Request failed'), 'error');
                setTimeout(function() { self.processNextImage(); }, 100);
            });
        },

        logMessage: function(message, type) {
            var className = 'log-' + type;
            var $log = $('#ap-progress-log');
            $log.append('<div class="' + className + '">' + message + '</div>');
            $log.scrollTop($log[0].scrollHeight);
        },

        finishRegeneration: function() {
            this.isRunning = false;
            $('#ap-start-regen').prop('disabled', false).find('.dashicons').removeClass('ap-spin');
            $('#ap-stop-regen').hide();
            $('#ap-progress-status').text(this.strings.complete || 'Complete');

            if (this.processed > 0) {
                $('#ap-regen-results').show();
                var summary = this.strings.summary || 'Processed: %1$s | Skipped: %2$s | Errors: %3$s';
                summary = summary.replace('%1$s', this.processed - this.skipped - this.errors);
                summary = summary.replace('%2$s', this.skipped);
                summary = summary.replace('%3$s', this.errors);
                $('#ap-results-summary').text(summary);
            }

            // Clear regeneration notice
            $.post(this.ajaxurl, {
                action: 'blaminhor_essentials_dismiss_regen_notice',
                nonce: this.nonce
            });
            $('#ap-regen-notice').fadeOut();
        },

        startCleanup: function() {
            if (this.isCleanupRunning) return;

            var self = this;
            var selectedSizes = [];
            $('.ap-cleanup-size:checked').each(function() {
                selectedSizes.push($(this).val());
            });

            if (selectedSizes.length === 0) {
                alert(this.strings.selectSize || 'Please select at least one size to clean up.');
                return;
            }

            this.isCleanupRunning = true;
            this.cleanupIndex = 0;
            this.cleanupProcessed = 0;
            this.cleanupDeleted = 0;
            this.cleanupErrors = 0;

            $('#ap-start-cleanup').prop('disabled', true).find('.dashicons').addClass('ap-spin');
            $('#ap-stop-cleanup').show();
            $('#ap-cleanup-progress').show();
            $('#ap-cleanup-results').hide();
            $('#ap-cleanup-log').html('');
            $('#ap-cleanup-bar').css('width', '0%');
            $('#ap-cleanup-status').text(this.strings.loading || 'Loading images...');

            // Get all image IDs
            $.post(this.ajaxurl, {
                action: 'blaminhor_essentials_get_images',
                nonce: this.nonce
            }, function(response) {
                if (response.success) {
                    self.cleanupImages = response.data.images;
                    $('#ap-cleanup-count').text('0 / ' + self.cleanupImages.length);

                    if (self.cleanupImages.length === 0) {
                        self.cleanupLogMessage(self.strings.noImages || 'No images found in the library.', 'skip');
                        self.finishCleanup(selectedSizes);
                    } else {
                        self.processNextCleanup(selectedSizes);
                    }
                } else {
                    self.cleanupLogMessage(self.strings.errorLoading || 'Error loading images.', 'error');
                    self.finishCleanup(selectedSizes);
                }
            });
        },

        processNextCleanup: function(sizes) {
            if (!this.isCleanupRunning || this.cleanupIndex >= this.cleanupImages.length) {
                this.finishCleanup(sizes);
                return;
            }

            var self = this;
            var imageId = this.cleanupImages[this.cleanupIndex];

            $('#ap-cleanup-status').text(this.strings.processing || 'Processing...');

            $.post(this.ajaxurl, {
                action: 'blaminhor_essentials_cleanup_disabled_thumbnails',
                nonce: this.nonce,
                attachment_id: imageId,
                sizes: sizes
            }, function(response) {
                self.cleanupIndex++;
                self.cleanupProcessed++;

                var progress = Math.round((self.cleanupIndex / self.cleanupImages.length) * 100);
                $('#ap-cleanup-bar').css('width', progress + '%');
                $('#ap-cleanup-count').text(self.cleanupIndex + ' / ' + self.cleanupImages.length);

                if (response.success) {
                    if (response.data.skipped) {
                        self.cleanupLogMessage(response.data.message, 'skip');
                    } else {
                        self.cleanupDeleted += response.data.deleted || 0;
                        self.cleanupLogMessage(response.data.message, 'success');
                    }
                } else {
                    self.cleanupErrors++;
                    self.cleanupLogMessage('ID ' + imageId + ': ' + (response.data ? response.data.message : 'Error'), 'error');
                }

                setTimeout(function() { self.processNextCleanup(sizes); }, 100);
            }).fail(function() {
                self.cleanupIndex++;
                self.cleanupErrors++;
                self.cleanupLogMessage('ID ' + imageId + ': ' + (self.strings.requestFailed || 'Request failed'), 'error');
                setTimeout(function() { self.processNextCleanup(sizes); }, 100);
            });
        },

        cleanupLogMessage: function(message, type) {
            var className = 'log-' + type;
            var $log = $('#ap-cleanup-log');
            $log.append('<div class="' + className + '">' + message + '</div>');
            $log.scrollTop($log[0].scrollHeight);
        },

        finishCleanup: function(sizes) {
            this.isCleanupRunning = false;
            $('#ap-start-cleanup').prop('disabled', false).find('.dashicons').removeClass('ap-spin');
            $('#ap-stop-cleanup').hide();
            $('#ap-cleanup-status').text(this.strings.complete || 'Complete');

            if (this.cleanupProcessed > 0) {
                $('#ap-cleanup-results').show();
                var summary = this.strings.cleanupSummary || 'Images processed: %1$s | Thumbnails deleted: %2$s | Errors: %3$s';
                summary = summary.replace('%1$s', this.cleanupProcessed);
                summary = summary.replace('%2$s', this.cleanupDeleted);
                summary = summary.replace('%3$s', this.cleanupErrors);
                $('#ap-cleanup-summary').text(summary);
            }

            // Clear cleanup notice
            $.post(this.ajaxurl, {
                action: 'blaminhor_essentials_dismiss_cleanup_notice',
                nonce: this.nonce
            });
            $('#ap-cleanup-notice').fadeOut();
        }
    };

    /**
     * Broken Links Module
     */
    window.BlaminhorEssentialsBrokenLinks = {
        nonce: '',
        ajaxurl: '',
        strings: {},
        isScanning: false,
        totalBroken: 0,
        initialized: false,

        init: function(config) {
            if (this.initialized) return;
            this.initialized = true;

            this.nonce = config.nonce || '';
            this.ajaxurl = config.ajaxurl || ajaxurl;
            this.strings = config.strings || {};
            this.totalBroken = config.totalBroken || 0;
            this.bindEvents();
        },

        bindEvents: function() {
            var self = this;
            var ns = '.blaminhorBrokenLinks';

            // Warn user before leaving during scan
            $(window).off('beforeunload' + ns).on('beforeunload' + ns, function(e) {
                if (self.isScanning) {
                    var message = self.strings.leaveWarning || 'A scan is in progress. If you leave this page, the scan will be interrupted.';
                    e.preventDefault();
                    e.returnValue = message;
                    return message;
                }
            });

            // Start scan
            $('#start-scan').off('click' + ns).on('click' + ns, function() {
                self.startScan();
            });

            // Stop scan
            $('#stop-scan').off('click' + ns).on('click' + ns, function() {
                self.isScanning = false;
                $(this).hide();
                $('#scan-status').text(self.strings.stopped || 'Stopped');
            });

            // Recheck link
            $(document).off('click' + ns, '.recheck-link').on('click' + ns, '.recheck-link', function() {
                self.recheckLink($(this));
            });

            // Dismiss link
            $(document).off('click' + ns, '.dismiss-link').on('click' + ns, '.dismiss-link', function() {
                self.dismissLink($(this));
            });

            // Clear results
            $('#clear-results').off('click' + ns).on('click' + ns, function() {
                self.clearResults();
            });

            // Sortable table
            this.bindSortable();
        },

        startScan: function() {
            if (this.isScanning) return;

            var self = this;
            this.isScanning = true;

            var $btn = $('#start-scan');
            $btn.prop('disabled', true).find('.dashicons').addClass('ap-spin');
            $('#stop-scan').show();
            $('#scan-progress').show();
            $('#scan-log').show().html('');
            $('#no-broken-links').hide();

            this.runScan(0);
        },

        runScan: function(offset) {
            if (!this.isScanning) {
                this.finishScan();
                return;
            }

            var self = this;

            $.ajax({
                url: this.ajaxurl,
                type: 'POST',
                data: {
                    action: 'blaminhor_essentials_scan_links',
                    nonce: this.nonce,
                    offset: offset
                },
                success: function(response) {
                    if (response.success) {
                        var data = response.data;

                        // Update progress
                        $('#scan-bar').css('width', data.progress + '%');
                        $('#scan-count').text(data.offset + ' / ' + data.total);
                        $('#scan-status').text(self.strings.scanning || 'Scanning...');

                        // Add to log
                        if (data.post_title) {
                            var logClass = data.broken_found > 0 ? 'log-broken' : 'log-ok';
                            var logIcon = data.broken_found > 0 ? '\u2717' : '\u2713';
                            var logEntry = '<div class="log-item">' +
                                '<span class="' + logClass + '">' + logIcon + '</span> ' +
                                '<span class="log-title">' + self.escapeHtml(data.post_title) + '</span>' +
                                '<span class="log-meta">(' + data.post_type + ') \u2014 ' + data.links_found + ' ' + (self.strings.links || 'links');

                            if (data.broken_found > 0) {
                                logEntry += ', <span class="log-broken">' + data.broken_found + ' ' + (self.strings.broken || 'broken') + '</span>';
                            }
                            logEntry += '</span></div>';

                            $('#scan-log').append(logEntry);
                            $('#scan-log').scrollTop($('#scan-log')[0].scrollHeight);

                            // Add broken links to table
                            if (data.broken_links && data.broken_links.length > 0) {
                                self.addBrokenLinks(data.broken_links);
                            }
                        }

                        if (data.complete) {
                            self.finishScan();
                        } else {
                            self.runScan(data.offset);
                        }
                    } else {
                        self.logError(response.data.message || self.strings.scanFailed || 'Scan failed.');
                        self.finishScan();
                    }
                },
                error: function() {
                    self.logError(self.strings.requestFailed || 'Request failed.');
                    self.finishScan();
                }
            });
        },

        addBrokenLinks: function(links) {
            var self = this;
            $('#broken-links-section').show();
            $('#no-broken-links').hide();

            links.forEach(function(link) {
                // Check if already exists
                if ($('#broken-links-body tr[data-link-id="' + link.id + '"]').length === 0) {
                    var row = '<tr data-link-id="' + link.id + '" class="ap-new-row">' +
                        '<td><a href="' + link.edit_url + '" target="_blank">' + self.escapeHtml(link.post_title) + '</a></td>' +
                        '<td><code class="ap-url-code">' + self.escapeHtml(link.url) + '</code>' +
                        (link.anchor_text ? '<br><small class="ap-anchor-text">' + self.escapeHtml(link.anchor_text) + '</small>' : '') + '</td>' +
                        '<td><span class="ap-status-badge ap-status-error">' + self.escapeHtml(link.error_message) + '</span></td>' +
                        '<td>' +
                        '<button type="button" class="button button-small recheck-link">' + (self.strings.recheck || 'Recheck') + '</button> ' +
                        '<a href="' + link.edit_url + '" class="button button-small" target="_blank">' + (self.strings.edit || 'Edit') + '</a> ' +
                        '<button type="button" class="button button-small dismiss-link">' + (self.strings.dismiss || 'Dismiss') + '</button>' +
                        '</td></tr>';
                    $('#broken-links-body').prepend(row);
                    self.totalBroken++;
                }
            });

            this.updateBrokenCount();
        },

        logError: function(message) {
            $('#scan-log').append('<div class="log-item log-broken">\u2717 ' + message + '</div>');
        },

        finishScan: function() {
            this.isScanning = false;
            $('#start-scan').prop('disabled', false).find('.dashicons').removeClass('ap-spin');
            $('#stop-scan').hide();
            $('#scan-status').text(this.strings.complete || 'Complete');

            if (this.totalBroken === 0) {
                $('#no-broken-links').show();
            }
        },

        recheckLink: function($btn) {
            var self = this;
            var $row = $btn.closest('tr');
            var linkId = $row.data('link-id');

            $btn.prop('disabled', true).text(this.strings.checking || 'Checking...');

            $.ajax({
                url: this.ajaxurl,
                type: 'POST',
                data: {
                    action: 'blaminhor_essentials_recheck_link',
                    nonce: this.nonce,
                    link_id: linkId
                },
                success: function(response) {
                    if (response.success) {
                        if (response.data.still_broken) {
                            $btn.prop('disabled', false).text(self.strings.recheck || 'Recheck');
                            $row.find('.ap-status-badge').text(response.data.error);
                        } else {
                            $row.fadeOut(function() {
                                $(this).remove();
                                self.totalBroken--;
                                self.updateBrokenCount();
                                if (self.totalBroken === 0) {
                                    $('#broken-links-section').hide();
                                    $('#no-broken-links').show();
                                }
                            });
                        }
                    }
                }
            });
        },

        dismissLink: function($btn) {
            var self = this;
            var $row = $btn.closest('tr');
            var linkId = $row.data('link-id');

            $.ajax({
                url: this.ajaxurl,
                type: 'POST',
                data: {
                    action: 'blaminhor_essentials_dismiss_link',
                    nonce: this.nonce,
                    link_id: linkId
                },
                success: function(response) {
                    if (response.success) {
                        $row.fadeOut(function() {
                            $(this).remove();
                            self.totalBroken--;
                            self.updateBrokenCount();
                            if (self.totalBroken === 0) {
                                $('#broken-links-section').hide();
                                $('#no-broken-links').show();
                            }
                        });
                    }
                }
            });
        },

        clearResults: function() {
            var self = this;
            if (!confirm(this.strings.confirmClear || 'Are you sure? This will clear all scan results.')) {
                return;
            }

            $.ajax({
                url: this.ajaxurl,
                type: 'POST',
                data: {
                    action: 'blaminhor_essentials_clear_broken_links',
                    nonce: this.nonce
                },
                success: function(response) {
                    if (response.success) {
                        $('#broken-links-body').empty();
                        $('#broken-links-section').hide();
                        self.totalBroken = 0;
                        self.updateBrokenCount();
                    }
                }
            });
        },

        updateBrokenCount: function() {
            var count = $('#broken-links-body tr').length;
            this.totalBroken = count;
            var text = count === 1 ? (this.strings.singularTitle || '%d Broken Link') : (this.strings.pluralTitle || '%d Broken Links');
            $('#broken-links-title').text(text.replace('%d', count));

            // Update tab badge
            var $tab = $('[data-tab="scan"]');
            $tab.find('.ap-badge-error').remove();
            if (count > 0) {
                $tab.append('<span class="ap-badge-error">' + count + '</span>');
            }
        },

        bindSortable: function() {
            var ns = '.blaminhorBrokenLinks';
            $('.ap-sortable-table th.ap-sortable').off('click' + ns).on('click' + ns, function() {
                var $th = $(this);
                var $table = $th.closest('table');
                var $tbody = $table.find('tbody');
                var colIndex = $th.index();
                var isAsc = !$th.hasClass('asc');

                // Remove sort classes from all headers
                $table.find('th.ap-sortable').removeClass('asc desc');

                // Add sort class to current header
                $th.addClass(isAsc ? 'asc' : 'desc');

                // Get rows and sort
                var rows = $tbody.find('tr').toArray();
                var dataAttrs = ['source', 'url', 'status'];

                rows.sort(function(a, b) {
                    var aVal, bVal;
                    var dataAttr = dataAttrs[colIndex];

                    if ($(a).data(dataAttr) !== undefined) {
                        aVal = String($(a).data(dataAttr)).toLowerCase();
                        bVal = String($(b).data(dataAttr)).toLowerCase();
                    } else {
                        aVal = $(a).find('td').eq(colIndex).text().toLowerCase();
                        bVal = $(b).find('td').eq(colIndex).text().toLowerCase();
                    }

                    if (aVal < bVal) return isAsc ? -1 : 1;
                    if (aVal > bVal) return isAsc ? 1 : -1;
                    return 0;
                });

                // Reorder rows
                $.each(rows, function(i, row) {
                    $tbody.append(row);
                });
            });
        },

        escapeHtml: function(text) {
            var div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }
    };

    /**
     * Favicon Module
     */
    window.BlaminhorEssentialsFavicon = {
        strings: {},
        initialized: false,
        frame: null,

        init: function(config) {
            if (this.initialized) return;
            this.initialized = true;

            this.strings = config.strings || {};
            this.bindEvents();
        },

        bindEvents: function() {
            var self = this;
            var ns = '.blaminhorFavicon';

            $('#ap-select-image').off('click' + ns).on('click' + ns, function(e) {
                e.preventDefault();

                if (self.frame) {
                    self.frame.open();
                    return;
                }

                self.frame = wp.media({
                    title: self.strings.selectTitle || 'Select Favicon Image',
                    button: { text: self.strings.useImage || 'Use this image' },
                    multiple: false,
                    library: { type: 'image' }
                });

                self.frame.on('select', function() {
                    var attachment = self.frame.state().get('selection').first().toJSON();
                    $('#source_image_id').val(attachment.id);
                    var url = attachment.sizes && attachment.sizes.thumbnail ? attachment.sizes.thumbnail.url : attachment.url;
                    $('#ap-image-preview').removeClass('ap-no-image').html('<img src="' + url + '" alt="">');
                    $('#ap-remove-image').show();
                });

                self.frame.open();
            });

            $('#ap-remove-image').off('click' + ns).on('click' + ns, function(e) {
                e.preventDefault();
                $('#source_image_id').val('');
                $('#ap-image-preview').addClass('ap-no-image').html('<span class="dashicons dashicons-format-image"></span>');
                $(this).hide();
            });

        }
    };

    /**
     * Mute Core Emails - Unsaved Changes Detection
     */
    window.BlaminhorEssentialsMuteEmails = {
        strings: {},
        isDirty: false,
        initialState: '',
        pendingUrl: null,

        init: function(config) {
            this.strings = config.strings || {};
            this.bindEvents();
        },

        bindEvents: function() {
            var self = this;
            var form = document.querySelector('.blaminhor-essentials-card form');
            if (!form) return;

            var modal = document.getElementById('ap-unsaved-modal');
            var submitBtn = form.querySelector('.submit button');

            this.initialState = this.getFormState(form);

            // Listen for changes on all checkboxes
            form.querySelectorAll('input[type="checkbox"]').forEach(function(input) {
                input.addEventListener('change', function() {
                    self.checkDirty(form, submitBtn);
                });
            });

            // Intercept clicks on admin links
            document.addEventListener('click', function(e) {
                if (!self.isDirty) return;

                var link = e.target.closest('a');
                if (!link) return;

                var href = link.getAttribute('href');
                if (!href || href.startsWith('#') || href.startsWith('javascript:')) return;

                // Check if it's an external link
                if (link.target === '_blank') return;

                e.preventDefault();
                self.showModal(modal, href);
            });

            // Modal buttons
            if (modal) {
                modal.querySelector('.ap-modal-cancel').addEventListener('click', function() {
                    self.hideModal(modal);
                });
                modal.querySelector('.ap-modal-backdrop').addEventListener('click', function() {
                    self.hideModal(modal);
                });
                modal.querySelector('.ap-modal-leave').addEventListener('click', function() {
                    self.isDirty = false;
                    if (self.pendingUrl) {
                        window.location.href = self.pendingUrl;
                    }
                });
            }

            // Escape key closes modal
            document.addEventListener('keydown', function(e) {
                if (e.key === 'Escape' && modal && modal.style.display === 'flex') {
                    self.hideModal(modal);
                }
            });

            // Browser's native beforeunload
            window.addEventListener('beforeunload', function(e) {
                if (self.isDirty) {
                    e.preventDefault();
                    e.returnValue = '';
                    return '';
                }
            });

            // Reset dirty flag on form submit
            form.addEventListener('submit', function() {
                self.setDirty(false, submitBtn);
            });
        },

        getFormState: function(form) {
            var state = {};
            var inputs = form.querySelectorAll('input[type="checkbox"]');
            inputs.forEach(function(input) {
                state[input.name] = input.checked;
            });
            return JSON.stringify(state);
        },

        checkDirty: function(form, submitBtn) {
            this.setDirty(this.getFormState(form) !== this.initialState, submitBtn);
        },

        setDirty: function(dirty, submitBtn) {
            this.isDirty = dirty;
            if (dirty) {
                submitBtn.classList.add('ap-btn-pulse');
                if (!document.querySelector('.ap-unsaved-notice')) {
                    var notice = document.createElement('span');
                    notice.className = 'ap-unsaved-notice';
                    notice.textContent = this.strings.unsavedChanges || 'You have unsaved changes';
                    submitBtn.parentNode.insertBefore(notice, submitBtn);
                }
            } else {
                submitBtn.classList.remove('ap-btn-pulse');
                var notice = document.querySelector('.ap-unsaved-notice');
                if (notice) notice.remove();
            }
        },

        showModal: function(modal, url) {
            this.pendingUrl = url;
            modal.style.display = 'flex';
        },

        hideModal: function(modal) {
            modal.style.display = 'none';
            this.pendingUrl = null;
        }
    };

    /**
     * Backup Module
     */
    window.BlaminhorEssentialsBackup = {
        nonce: '',
        ajaxurl: '',
        strings: {},
        initialized: false,

        init: function(config) {
            if (this.initialized) return;
            this.initialized = true;

            this.nonce = config.nonce || '';
            this.ajaxurl = config.ajaxurl || ajaxurl;
            this.strings = config.strings || {};
            this.maxUploadSize = config.maxUploadSize || 0;
            this.maxUploadLabel = config.maxUploadLabel || '';
            this.bindEvents();
            this.checkAutoBackup();
        },

        bindEvents: function() {
            var self = this;
            var ns = '.blaminhorBackup'; // Namespace for events

            // Unbind previous events and rebind (prevents double binding)
            $('#ap-create-backup').off('click' + ns).on('click' + ns, function() {
                self.createBackup();
            });

            $(document).off('click' + ns, '.ap-backup-download').on('click' + ns, '.ap-backup-download', function() {
                self.downloadBackupBundle($(this));
            });

            $(document).off('click' + ns, '.ap-backup-delete').on('click' + ns, '.ap-backup-delete', function() {
                self.deleteBackup($(this));
            });

            // View backup log
            $(document).off('click' + ns, '.ap-backup-view-log').on('click' + ns, '.ap-backup-view-log', function() {
                self.viewBackupLog($(this).data('prefix'));
            });

            $('#ap-backup-log-modal-close, #ap-backup-log-modal .ap-db-modal-overlay').off('click' + ns).on('click' + ns, function() {
                $('#ap-backup-log-modal').fadeOut(200, function() { $(this).addClass('ap-hidden'); });
            });

            // Select all / deselect all (delegated: the element is recreated when the table is refreshed via AJAX)
            $(document).off('change' + ns, '#ap-backup-select-all').on('change' + ns, '#ap-backup-select-all', function() {
                var checked = $(this).is(':checked');
                $('.ap-backup-checkbox').prop('checked', checked);
                self.updateBulkActions();
            });

            // Individual checkbox
            $(document).off('change' + ns, '.ap-backup-checkbox').on('change' + ns, '.ap-backup-checkbox', function() {
                var total = $('.ap-backup-checkbox').length;
                var checked = $('.ap-backup-checkbox:checked').length;
                $('#ap-backup-select-all').prop('checked', checked === total);
                self.updateBulkActions();
            });

            // Bulk delete
            $(document).off('click' + ns, '.ap-bulk-delete-btn').on('click' + ns, '.ap-bulk-delete-btn', function() {
                self.bulkDeleteBackups();
            });

            $(document).off('click' + ns, '.ap-backup-restore').on('click' + ns, '.ap-backup-restore', function() {
                self.openRestoreModal($(this));
            });

            $('#ap-restore-cancel, #ap-restore-modal .ap-db-modal-overlay').off('click' + ns).on('click' + ns, function() {
                $('#ap-restore-modal').fadeOut(200, function() { $(this).addClass('ap-hidden'); });
            });

            $('#ap-restore-confirm').off('click' + ns).on('click' + ns, function() {
                self.restoreBackup();
            });

            $('#ap-undo-restore').off('click' + ns).on('click' + ns, function() {
                self.undoRestore($(this));
            });

            $('#ap-delete-pre-restore').off('click' + ns).on('click' + ns, function() {
                self.deletePreRestoreBackup($(this));
            });

            $('#ap-dismiss-pre-restore').off('click' + ns).on('click' + ns, function() {
                self.dismissPreRestoreNotice($(this));
            });

            // Upload backup - drag and drop
            var $dropzone = $('#ap-backup-dropzone');
            var $fileInput = $('#ap-backup-file-input');

            $dropzone.off('dragover' + ns).on('dragover' + ns, function(e) {
                e.preventDefault();
                e.stopPropagation();
                $(this).addClass('dragover');
            });

            $dropzone.off('dragleave' + ns).on('dragleave' + ns, function(e) {
                e.preventDefault();
                e.stopPropagation();
                $(this).removeClass('dragover');
            });

            $dropzone.off('drop' + ns).on('drop' + ns, function(e) {
                e.preventDefault();
                e.stopPropagation();
                $(this).removeClass('dragover');

                var files = e.originalEvent.dataTransfer.files;
                if (files.length > 0) {
                    self.addFilesToQueue(files);
                }
            });

            $dropzone.off('click' + ns).on('click' + ns, function(e) {
                if (e.target.tagName !== 'INPUT') {
                    $fileInput.trigger('click');
                }
            });

            $fileInput.off('change' + ns).on('change' + ns, function() {
                if (this.files.length > 0) {
                    self.addFilesToQueue(this.files);
                    this.value = ''; // Reset to allow selecting same files again
                }
            });

            // Start upload button
            $('#ap-start-upload').off('click' + ns).on('click' + ns, function() {
                self.processUploadQueue();
            });

            // Clear queue button
            $('#ap-clear-queue').off('click' + ns).on('click' + ns, function() {
                self.clearUploadQueue();
            });

            // Remove file from queue (delegated event)
            $('#ap-file-list').off('click' + ns, '.ap-remove-file').on('click' + ns, '.ap-remove-file', function() {
                var index = $(this).data('index');
                self.removeFileFromQueue(index);
            });

            // Schedule toggle
            $('#schedule_enabled').off('change' + ns).on('change' + ns, function() {
                self.toggleScheduleOptions($(this).is(':checked'));
            });
            // Initialize schedule options visibility
            this.toggleScheduleOptions($('#schedule_enabled').is(':checked'));

            // Toggle backup log panel
            $(document).off('click' + ns, '#ap-backup-log-toggle').on('click' + ns, '#ap-backup-log-toggle', function() {
                var $content = $('#ap-backup-log-content');
                var $icon = $(this).find('.dashicons');
                $content.slideToggle(200);
                if ($icon.hasClass('dashicons-arrow-down-alt2')) {
                    $icon.removeClass('dashicons-arrow-down-alt2').addClass('dashicons-arrow-up-alt2');
                } else {
                    $icon.removeClass('dashicons-arrow-up-alt2').addClass('dashicons-arrow-down-alt2');
                }
            });

            // Sortable table
            this.bindSortable();
        },

        toggleScheduleOptions: function(enabled) {
            if (enabled) {
                $('.schedule-option').show();
            } else {
                $('.schedule-option').hide();
            }
        },

        checkAutoBackup: function() {
            var urlParams = new URLSearchParams(window.location.search);
            var autoBackup = urlParams.get('auto_backup');
            if (autoBackup === 'database') {
                // Uncheck all except database
                $('#ap-backup-database').prop('checked', true);
                $('#ap-backup-plugins').prop('checked', false);
                $('#ap-backup-themes').prop('checked', false);
                $('#ap-backup-uploads').prop('checked', false);
                $('#ap-backup-wp-content').prop('checked', false);
                $('#ap-backup-wp-core').prop('checked', false);

                // Trigger backup after a small delay
                var self = this;
                setTimeout(function() {
                    self.createBackup();
                }, 500);

                // Clean up URL
                if (window.history.replaceState) {
                    var cleanUrl = window.location.href.split('?')[0] + '?page=blaminhor-essentials-backup';
                    window.history.replaceState({}, document.title, cleanUrl);
                }
            }
        },

        switchToBackupsTab: function() {
            var $tabContent = $('[data-tab-content="backups"]');
            if (typeof window.BlaminhorEssentialsActivateTab === 'function') {
                window.BlaminhorEssentialsActivateTab('backups');
            } else {
                $('.blaminhor-essentials-tab').removeClass('active');
                $('[data-tab="backups"]').addClass('active');
                $('.blaminhor-essentials-tab-content').removeClass('active');
                $tabContent.addClass('active');
            }
        },

        insertBackupInProgressRow: function(initialLabel) {
            var rowHtml = '<tr class="ap-backup-in-progress">' +
                '<td class="ap-col-checkbox" style="text-align: center;">' +
                    '<span class="spinner is-active" style="margin: 0; float: none;"></span>' +
                '</td>' +
                '<td colspan="5">' +
                    '<div class="ap-backup-progress-row-inner">' +
                        '<strong class="ap-backup-progress-status"></strong>' +
                        '<span class="ap-backup-progress-percent">0%</span>' +
                    '</div>' +
                    '<div class="ap-mini-progress-bar">' +
                        '<div class="ap-mini-progress-fill" style="width: 0%;"></div>' +
                    '</div>' +
                '</td>' +
            '</tr>';

            var $tabContent = $('[data-tab-content="backups"]');
            var $table = $tabContent.find('#ap-backups-table');

            if ($table.length) {
                $table.find('tbody').prepend(rowHtml);
            } else {
                // Empty state: hide the "no backups" notice and inject a minimal placeholder table.
                $tabContent.find('.blaminhor-essentials-notice.warning').hide();
                var $h3 = $tabContent.find('h3').first();
                $h3.after('<table class="widefat striped" id="ap-backups-table-temp"><tbody>' + rowHtml + '</tbody></table>');
            }

            // Use .text() to safely set the label (avoids any HTML injection risk).
            $('.ap-backup-in-progress .ap-backup-progress-status').text(initialLabel);
        },

        updateBackupInProgressRow: function(percent, label) {
            var $row = $('.ap-backup-in-progress');
            if (!$row.length) {
                return;
            }
            $row.find('.ap-mini-progress-fill').css('width', percent + '%');
            $row.find('.ap-backup-progress-status').text(label);
            $row.find('.ap-backup-progress-percent').text(Math.round(percent) + '%');
        },

        removeBackupInProgressRow: function() {
            $('.ap-backup-in-progress').remove();
            $('#ap-backups-table-temp').remove();
        },

        createBackup: function() {
            var self = this;
            var $btn = $('#ap-create-backup');
            $btn.prop('disabled', true);

            // Hide the legacy create-tab progress panel (now superseded by the in-list row)
            // and any previous result, then move the user to the backups tab where the
            // progress row will be inserted.
            $('#ap-backup-progress').addClass('ap-hidden').hide();
            $('#ap-backup-result').addClass('ap-hidden').hide();

            // Clear any leftover notice from a previous run — it will reappear on success.
            $('[data-tab-content="backups"] .ap-backup-success-notice, [data-tab-content="backups"] .ap-backup-error-notice').remove();

            this.switchToBackupsTab();
            this.insertBackupInProgressRow(this.strings.initializing || 'Initializing...');

            // Step 0: Initialize and get steps plan
            $.post(this.ajaxurl, {
                action: 'ap_backup_create_step',
                nonce: this.nonce,
                step: 0,
                database: $('#ap-backup-database').is(':checked'),
                plugins: $('#ap-backup-plugins').is(':checked'),
                themes: $('#ap-backup-themes').is(':checked'),
                uploads: $('#ap-backup-uploads').is(':checked'),
                wp_content: $('#ap-backup-wp-content').is(':checked'),
                wp_core: $('#ap-backup-wp-core').is(':checked')
            }, function(response) {
                if (response.success) {
                    // Use steps if available (new split format), fallback to components.
                    var steps = response.data.steps;
                    if (!steps) {
                        // Backward compatibility: build steps from components.
                        steps = [];
                        var comps = response.data.components || [];
                        for (var i = 0; i < comps.length; i++) {
                            steps.push({ component: comps[i], part: 0, total_parts: 1 });
                        }
                    }
                    self.backupState = {
                        prefix: response.data.prefix,
                        steps: steps,
                        total: steps.length,
                        current: 0,
                        totalSize: 0
                    };
                    self.startLogPolling();
                    self.createBackupStep();
                } else {
                    self.backupFailed(response.data);
                }
            }).fail(function() {
                self.backupFailed(self.strings.error || 'An error occurred. Please try again.');
            });
        },

        createBackupStep: function() {
            var self = this;

            if (this.backupState.current >= this.backupState.total) {
                // All steps complete
                this.backupComplete();
                return;
            }

            var stepInfo = this.backupState.steps[this.backupState.current];
            var component = stepInfo.component;
            var part = stepInfo.part;
            var totalParts = stepInfo.total_parts;
            var percent = Math.round(((this.backupState.current) / this.backupState.total) * 100);

            var label = component;
            if (totalParts > 1) {
                label = component + ' (' + part + '/' + totalParts + ')';
            }

            var statusText = (this.strings.creating || 'Creating') + ' ' + label + '... (' + (this.backupState.current + 1) + '/' + this.backupState.total + ')';
            this.updateBackupInProgressRow(percent, statusText);

            $.post(this.ajaxurl, {
                action: 'ap_backup_create_step',
                nonce: this.nonce,
                step: this.backupState.current + 1,
                prefix: this.backupState.prefix,
                component: component,
                part: part,
                total_parts: totalParts,
                total: this.backupState.total
            }, function(response) {
                if (response.success) {
                    self.backupState.current++;
                    self.createBackupStep();
                } else {
                    self.backupFailed(response.data);
                }
            }).fail(function() {
                self.backupFailed(self.strings.error || 'An error occurred. Please try again.');
            });
        },

        logPollingInterval: null,
        logOffset: 0,

        startLogPolling: function() {
            var self = this;
            this.logOffset = 0;

            // Show log panel.
            $('#ap-backup-log-panel').show();
            $('#ap-backup-log-content').empty();

            this.logPollingInterval = setInterval(function() {
                self.pollBackupLog();
            }, 2000);
        },

        stopLogPolling: function() {
            if (this.logPollingInterval) {
                clearInterval(this.logPollingInterval);
                this.logPollingInterval = null;
            }
            // Final poll to get remaining lines.
            this.pollBackupLog();
        },

        pollBackupLog: function() {
            var self = this;
            if (!this.backupState || !this.backupState.prefix) {
                return;
            }

            $.post(this.ajaxurl, {
                action: 'ap_backup_get_log',
                nonce: this.nonce,
                prefix: this.backupState.prefix,
                offset: this.logOffset
            }, function(response) {
                if (response.success && response.data.lines.length > 0) {
                    var $content = $('#ap-backup-log-content');
                    for (var i = 0; i < response.data.lines.length; i++) {
                        var line = response.data.lines[i];
                        var cls = 'ap-log-' + line.level;
                        $content.append('<div class="ap-log-line ' + cls + '">' + $('<span>').text(line.text).html() + '</div>');
                    }
                    // Auto-scroll to bottom.
                    $content.scrollTop($content[0].scrollHeight);
                    self.logOffset = response.data.new_offset;
                }
            });
        },

        backupComplete: function() {
            var self = this;
            var $btn = $('#ap-create-backup');

            this.stopLogPolling();

            // Show 100% briefly before the row is replaced by the refreshed table.
            this.updateBackupInProgressRow(100, this.strings.complete || 'Complete!');

            // Fetch updated backup list and rebuild the table content.
            $.post(this.ajaxurl, {
                action: 'ap_backup_get_list',
                nonce: this.nonce
            }, function(response) {
                $btn.prop('disabled', false);

                if (response.success) {
                    self.updateBackupCount(response.data.count);

                    var $tabContent = $('[data-tab-content="backups"]');
                    var $h3 = $tabContent.find('h3').first();

                    // Detach the restore modal so we can re-attach it after the swap.
                    var $restoreModal = $tabContent.find('#ap-restore-modal').detach();

                    // Remove the previous table (including the temp empty-state one),
                    // the empty-state notice and the in-progress row.
                    $tabContent.find('#ap-backups-table, #ap-backups-table-temp, .blaminhor-essentials-notice.warning').remove();

                    // Insert the freshly rendered table/notice after the h3.
                    $h3.after(response.data.html);

                    if ($restoreModal.length) {
                        $tabContent.append($restoreModal);
                    }

                    // Re-bind sortable table events on the new DOM.
                    self.bindSortable();

                    // Make sure the backups tab is the active one (defensive: createBackup
                    // already switched, but a user might have navigated away during the run).
                    self.switchToBackupsTab();

                    // Success notice at the top of the backups tab.
                    var successHtml = '<div class="blaminhor-essentials-notice success ap-backup-success-notice" style="display: flex; align-items: center; gap: 10px; margin-bottom: 15px;">' +
                        '<span class="dashicons dashicons-yes-alt" style="color: #00a32a; font-size: 20px;"></span>' +
                        '<span>' + (self.strings.backupSuccess || 'Backup created successfully!') + '</span>' +
                        '</div>';
                    $tabContent.find('.ap-backup-success-notice').remove();
                    $h3.after(successHtml);

                    $('#ap-backup-result').addClass('ap-hidden').hide();
                } else {
                    location.reload();
                }
            }).fail(function() {
                location.reload();
            });
        },

        backupFailed: function(message) {
            var $btn = $('#ap-create-backup');
            $btn.prop('disabled', false);
            this.stopLogPolling();

            // Hide the legacy panel and remove the in-progress row.
            $('#ap-backup-progress').addClass('ap-hidden').hide();
            this.removeBackupInProgressRow();

            // Restore the empty-state notice if we had hidden it.
            var $tabContent = $('[data-tab-content="backups"]');
            $tabContent.find('.blaminhor-essentials-notice.warning').show();

            // Show the error notice in the backups tab (where the user currently is).
            var $h3 = $tabContent.find('h3').first();
            var errorHtml = '<div class="blaminhor-essentials-notice error ap-backup-error-notice" style="display: flex; align-items: center; gap: 10px; margin-bottom: 15px;">' +
                '<span class="dashicons dashicons-warning"></span>' +
                '<span></span>' +
            '</div>';
            $tabContent.find('.ap-backup-error-notice').remove();
            $h3.after(errorHtml);
            // Use .text() on the message to avoid HTML injection (server-supplied strings).
            $tabContent.find('.ap-backup-error-notice span').last().text(message);
        },

        viewBackupLog: function(prefix) {
            var self = this;
            var $modal = $('#ap-backup-log-modal');
            var $content = $('#ap-backup-log-modal-content');
            $content.empty().html('<div class="ap-log-line ap-log-info">' + (this.strings.loading || 'Loading...') + '</div>');
            $modal.removeClass('ap-hidden').fadeIn(200);

            $.post(this.ajaxurl, {
                action: 'ap_backup_get_log',
                nonce: this.nonce,
                prefix: prefix,
                offset: 0
            }, function(response) {
                $content.empty();
                if (response.success && response.data.lines.length > 0) {
                    for (var i = 0; i < response.data.lines.length; i++) {
                        var line = response.data.lines[i];
                        var cls = 'ap-log-' + line.level;
                        $content.append('<div class="ap-log-line ' + cls + '">' + $('<span>').text(line.text).html() + '</div>');
                    }
                } else {
                    $content.html('<div class="ap-log-line ap-log-info">' + (self.strings.noLogs || 'No log entries found.') + '</div>');
                }
            }).fail(function() {
                $content.html('<div class="ap-log-line ap-log-error">' + (self.strings.error || 'An error occurred.') + '</div>');
            });
        },

        backupState: null,
        uploadQueue: [],

        addFilesToQueue: function(files) {
            var self = this;
            var validExtensions = ['.zip'];
            var skippedTooLarge = [];

            for (var i = 0; i < files.length; i++) {
                var file = files[i];
                var ext = file.name.toLowerCase().substring(file.name.lastIndexOf('.'));

                if (validExtensions.indexOf(ext) === -1) {
                    continue; // Skip non-ZIP files
                }

                // Check file size against server limit.
                if (this.maxUploadSize && file.size > this.maxUploadSize) {
                    skippedTooLarge.push(file.name + ' (' + this.formatFileSize(file.size) + ')');
                    continue;
                }

                // Check if file is already in queue
                var exists = false;
                for (var j = 0; j < this.uploadQueue.length; j++) {
                    if (this.uploadQueue[j].name === file.name && this.uploadQueue[j].size === file.size) {
                        exists = true;
                        break;
                    }
                }

                if (!exists) {
                    this.uploadQueue.push(file);
                }
            }

            // Show warning for files that exceed the server upload limit.
            if (skippedTooLarge.length > 0) {
                var $tabContent = $('[data-tab-content="upload"]');
                $tabContent.find('.ap-upload-size-notice').remove();
                var notice = '<div class="blaminhor-essentials-notice error ap-upload-size-notice" style="margin-bottom: 15px;">' +
                    '<span class="dashicons dashicons-warning"></span> ' +
                    (this.strings.fileTooLarge || 'The following file(s) exceed the server upload limit') +
                    ' (' + (this.maxUploadLabel || '') + '):<br>' +
                    skippedTooLarge.join('<br>') +
                    '</div>';
                $tabContent.prepend(notice);
            }

            this.renderUploadQueue();
        },

        removeFileFromQueue: function(index) {
            this.uploadQueue.splice(index, 1);
            this.renderUploadQueue();
        },

        clearUploadQueue: function() {
            this.uploadQueue = [];
            this.renderUploadQueue();
        },

        renderUploadQueue: function() {
            var $queue = $('#ap-upload-queue');
            var $list = $('#ap-file-list');
            var $dropzone = $('#ap-backup-dropzone');

            if (this.uploadQueue.length === 0) {
                $queue.hide();
                $dropzone.show();
                return;
            }

            $dropzone.hide();
            $queue.show();

            var html = '';
            for (var i = 0; i < this.uploadQueue.length; i++) {
                var file = this.uploadQueue[i];
                var size = this.formatFileSize(file.size);
                html += '<li style="display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; background: #f6f7f7; border-radius: 4px; margin-bottom: 5px;">' +
                    '<span><span class="dashicons dashicons-media-archive" style="color: #2271b1; margin-right: 8px;"></span>' + file.name + ' <small style="color: #646970;">(' + size + ')</small></span>' +
                    '<button type="button" class="ap-remove-file" data-index="' + i + '" style="background: none; border: none; cursor: pointer; color: #d63638; padding: 0;"><span class="dashicons dashicons-no-alt"></span></button>' +
                    '</li>';
            }
            $list.html(html);
        },

        formatFileSize: function(bytes) {
            if (bytes === 0) return '0 B';
            var k = 1024;
            var sizes = ['B', 'KB', 'MB', 'GB'];
            var i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        },

        processUploadQueue: function() {
            if (this.uploadQueue.length === 0) {
                return;
            }

            var self = this;
            this.uploadQueueIndex = 0;
            this.uploadResults = [];

            // Hide queue, show progress
            $('#ap-upload-queue').hide();
            $('#ap-upload-progress').show();
            $('#ap-upload-result').hide();

            this.uploadNextInQueue();
        },

        uploadNextInQueue: function() {
            var self = this;

            if (this.uploadQueueIndex >= this.uploadQueue.length) {
                // All done - switch to backups tab
                this.onUploadQueueComplete();
                return;
            }

            var file = this.uploadQueue[this.uploadQueueIndex];
            var $progressFill = $('#ap-upload-progress-fill');
            var $status = $('#ap-upload-status');
            var $currentFile = $('#ap-upload-current-file');

            $currentFile.text((this.strings.uploading || 'Uploading') + ' ' + file.name + ' (' + (this.uploadQueueIndex + 1) + '/' + this.uploadQueue.length + ')');
            $progressFill.css('width', '0%');

            var formData = BlaminhorEssentials.buildFormData('ap_backup_upload', { backup_file: file }, this.nonce);

            $.ajax({
                url: this.ajaxurl,
                type: 'POST',
                data: formData,
                processData: false,
                contentType: false,
                xhr: function() {
                    var xhr = new window.XMLHttpRequest();
                    xhr.upload.addEventListener('progress', function(e) {
                        if (e.lengthComputable) {
                            var percent = (e.loaded / e.total) * 100;
                            $progressFill.css('width', percent + '%');
                            if (percent >= 100) {
                                $status.text(self.strings.processing || 'Processing...');
                            }
                        }
                    }, false);
                    return xhr;
                },
                success: function(response) {
                    self.uploadResults.push({
                        file: file.name,
                        success: response.success,
                        data: response.data
                    });
                    self.uploadQueueIndex++;
                    self.uploadNextInQueue();
                },
                error: function() {
                    self.uploadResults.push({
                        file: file.name,
                        success: false,
                        data: self.strings.error || 'Upload failed'
                    });
                    self.uploadQueueIndex++;
                    self.uploadNextInQueue();
                }
            });
        },

        onUploadQueueComplete: function() {
            var self = this;

            // Clear the queue
            this.uploadQueue = [];

            // Fetch updated backup list and switch to backups tab
            $.post(this.ajaxurl, {
                action: 'ap_backup_get_list',
                nonce: this.nonce
            }, function(response) {
                $('#ap-upload-progress').hide();
                $('#ap-backup-dropzone').show();

                if (response.success) {
                    // Update the backup count in the tab
                    self.updateBackupCount(response.data.count);

                    // Update the backup table content
                    var $tabContent = $('[data-tab-content="backups"]');
                    var $h3 = $tabContent.find('h3').first();

                    // Remove existing table/notice and restore modal
                    var $restoreModal = $tabContent.find('#ap-restore-modal').detach();
                    $tabContent.find('#ap-backups-table, .blaminhor-essentials-notice.warning').remove();

                    // Insert new table/notice after h3
                    $h3.after(response.data.html);

                    // Re-attach restore modal at the end
                    if ($restoreModal.length) {
                        $tabContent.append($restoreModal);
                    }

                    // Re-bind sortable table events
                    self.bindSortable();

                    // Switch to the backups tab
                    if (typeof window.BlaminhorEssentialsActivateTab === 'function') {
                        window.BlaminhorEssentialsActivateTab('backups');
                    } else {
                        $('.blaminhor-essentials-tab').removeClass('active');
                        $('[data-tab="backups"]').addClass('active');
                        $('.blaminhor-essentials-tab-content').removeClass('active');
                        $tabContent.addClass('active');
                    }

                    // Build success message
                    var successCount = 0;
                    var errorCount = 0;
                    for (var i = 0; i < self.uploadResults.length; i++) {
                        if (self.uploadResults[i].success) {
                            successCount++;
                        } else {
                            errorCount++;
                        }
                    }

                    var successHtml = '';
                    if (successCount > 0) {
                        var msg = successCount === 1
                            ? (self.strings.uploadSuccess || 'Backup uploaded successfully!')
                            : successCount + ' ' + (self.strings.backupsUploaded || 'backups uploaded successfully!');
                        successHtml = '<div class="blaminhor-essentials-notice success ap-backup-success-notice" style="display: flex; align-items: center; gap: 10px; margin-bottom: 15px;">' +
                            '<span class="dashicons dashicons-yes-alt" style="color: #00a32a; font-size: 20px;"></span>' +
                            '<span>' + msg + '</span>' +
                            '</div>';
                    }

                    if (errorCount > 0) {
                        successHtml += '<div class="blaminhor-essentials-notice error ap-backup-error-notice" style="margin-bottom: 15px;">' +
                            '<span class="dashicons dashicons-warning"></span> ' +
                            errorCount + ' ' + (self.strings.uploadsFailed || 'file(s) failed to upload.') +
                            '</div>';
                    }

                    // Remove any existing notices and add new ones
                    $tabContent.find('.ap-backup-success-notice, .ap-backup-error-notice, .ap-backup-domain-notice').remove();
                    if (successHtml) {
                        $h3.after(successHtml);
                    }
                } else {
                    location.reload();
                }
            }).fail(function() {
                location.reload();
            });
        },

        downloadBackup: function(filename) {
            var self = this;
            $.post(this.ajaxurl, {
                action: 'ap_backup_download',
                nonce: this.nonce,
                filename: filename
            }, function(response) {
                if (response.success) {
                    window.location.href = response.data.url;
                } else {
                    alert(response.data);
                }
            });
        },

        downloadBackupBundle: function($btn) {
            var label = $btn.text();
            $btn.prop('disabled', true).html('<span class="spinner is-active" style="float: none; margin: 0 5px 0 0;"></span>' + (this.strings.preparing || 'Preparing...'));
            $.post(this.ajaxurl, {
                action: 'ap_backup_download_bundle',
                nonce: this.nonce,
                prefix: $btn.data('prefix')
            }, function(response) {
                $btn.prop('disabled', false).text(label);
                if (response.success) {
                    window.location.href = response.data.url;
                } else {
                    alert(response.data);
                }
            }).fail(function() {
                $btn.prop('disabled', false).text(label);
            });
        },

        deleteBackup: function($btn) {
            var self = this;
            if (!confirm(this.strings.confirmDelete || 'Are you sure you want to delete this backup?')) {
                return;
            }

            var $row = $btn.closest('tr');
            var prefix = $btn.data('prefix');

            // Disable button to prevent double-click
            $btn.prop('disabled', true);

            $.post(this.ajaxurl, {
                action: 'ap_backup_delete',
                nonce: this.nonce,
                prefix: prefix
            }, function(response) {
                if (response.success) {
                    $row.fadeOut(function() {
                        $(this).remove();
                        // Update backup count in tab
                        if (response.data && typeof response.data.count !== 'undefined') {
                            self.updateBackupCount(response.data.count);
                        }
                    });
                } else {
                    alert(response.data || self.strings.error);
                    $btn.prop('disabled', false);
                }
            }).fail(function() {
                alert(self.strings.error || 'An error occurred.');
                $btn.prop('disabled', false);
            });
        },

        updateBulkActions: function() {
            var checked = $('.ap-backup-checkbox:checked').length;
            var $bar = $('#ap-backup-bulk-actions');
            if (checked > 0) {
                var text = (this.strings.selectedCount || '%d selected').replace('%d', checked);
                $bar.find('.ap-bulk-count').text(text);
                $bar.css('display', 'flex');
            } else {
                $bar.hide();
            }
        },

        bulkDeleteBackups: function() {
            var self = this;
            var prefixes = [];
            $('.ap-backup-checkbox:checked').each(function() {
                prefixes.push($(this).val());
            });

            if (prefixes.length === 0) return;

            if (!confirm(this.strings.confirmBulkDelete || 'Are you sure you want to delete the selected backups?')) {
                return;
            }

            var $btn = $('.ap-bulk-delete-btn');
            var originalHtml = $btn.html();
            $btn.prop('disabled', true).text(this.strings.deleting || 'Deleting...');

            $.post(this.ajaxurl, {
                action: 'ap_backup_bulk_delete',
                nonce: this.nonce,
                prefixes: prefixes
            }, function(response) {
                if (response.success) {
                    // Remove deleted rows
                    prefixes.forEach(function(prefix) {
                        $('#ap-backups-table tr[data-prefix="' + prefix + '"]').fadeOut(function() {
                            $(this).remove();
                        });
                    });
                    // Update count and reset UI
                    if (typeof response.data.count !== 'undefined') {
                        self.updateBackupCount(response.data.count);
                    }
                    $('#ap-backup-select-all').prop('checked', false);
                    self.updateBulkActions();
                } else {
                    alert(response.data || self.strings.error);
                }
                $btn.prop('disabled', false).html(originalHtml);
            }).fail(function() {
                alert(self.strings.error || 'An error occurred.');
                $btn.prop('disabled', false).html(originalHtml);
            });
        },

        deletePreRestoreBackup: function($btn) {
            var self = this;
            if (!confirm(this.strings.confirmDelete || 'Are you sure you want to delete this backup?')) {
                return;
            }

            var filename = $btn.data('filename');
            // Extract prefix from filename (remove .zip extension)
            var prefix = filename.replace(/\.zip$/, '');

            $btn.prop('disabled', true);

            $.post(this.ajaxurl, {
                action: 'ap_backup_delete',
                nonce: this.nonce,
                prefix: prefix
            }, function(response) {
                if (response.success) {
                    // Hide the pre-restore notice
                    $btn.closest('.blaminhor-essentials-notice').fadeOut(function() {
                        $(this).remove();
                    });
                } else {
                    alert(response.data || self.strings.error);
                    $btn.prop('disabled', false);
                }
            }).fail(function() {
                alert(self.strings.error || 'An error occurred.');
                $btn.prop('disabled', false);
            });
        },

        dismissPreRestoreNotice: function($btn) {
            var self = this;
            var filename = $btn.data('filename');

            $btn.prop('disabled', true);

            $.post(this.ajaxurl, {
                action: 'ap_backup_dismiss_pre_restore',
                nonce: this.nonce,
                filename: filename
            }, function(response) {
                if (response.success) {
                    $btn.closest('.notice, .blaminhor-essentials-notice').fadeOut(function() {
                        $(this).remove();
                    });
                } else {
                    alert(response.data || self.strings.error);
                    $btn.prop('disabled', false);
                }
            }).fail(function() {
                alert(self.strings.error || 'An error occurred.');
                $btn.prop('disabled', false);
            });
        },

        uploadBackup: function(file) {
            var self = this;
            var $progress = $('#ap-upload-progress');
            var $progressFill = $('#ap-upload-progress-fill');
            var $status = $('#ap-upload-status');
            var $result = $('#ap-upload-result');
            var $success = $('#ap-upload-success');
            var $error = $('#ap-upload-error');
            var $domainWarning = $('#ap-upload-domain-warning');
            var $dropzone = $('#ap-backup-dropzone');

            // Validate file extension
            if (!file.name.toLowerCase().endsWith('.zip')) {
                $result.show();
                $error.show().find('#ap-upload-error-message').text(this.strings.onlyZip || 'Only ZIP files are allowed.');
                return;
            }

            // Hide previous results
            $result.hide();
            $success.hide();
            $error.hide();
            $domainWarning.hide();

            // Show progress
            $dropzone.hide();
            $progress.show();
            $progressFill.css('width', '0%');

            var formData = BlaminhorEssentials.buildFormData('ap_backup_upload', { backup_file: file }, this.nonce);

            $.ajax({
                url: this.ajaxurl,
                type: 'POST',
                data: formData,
                processData: false,
                contentType: false,
                xhr: function() {
                    var xhr = new window.XMLHttpRequest();
                    xhr.upload.addEventListener('progress', function(e) {
                        if (e.lengthComputable) {
                            var percent = (e.loaded / e.total) * 100;
                            $progressFill.css('width', percent + '%');
                            if (percent >= 100) {
                                $status.text(self.strings.processing || 'Processing...');
                            }
                        }
                    }, false);
                    return xhr;
                },
                success: function(response) {
                    $progress.hide();
                    $dropzone.show();
                    $result.show();

                    if (response.success) {
                        // Fetch updated backup list and switch to backups tab
                        $.post(self.ajaxurl, {
                            action: 'ap_backup_get_list',
                            nonce: self.nonce
                        }, function(listResponse) {
                            if (listResponse.success) {
                                // Update the backup count in the tab
                                self.updateBackupCount(listResponse.data.count);

                                // Update the backup table content
                                var $tabContent = $('[data-tab-content="backups"]');
                                var $h3 = $tabContent.find('h3').first();

                                // Remove existing table/notice and restore modal
                                var $restoreModal = $tabContent.find('#ap-restore-modal').detach();
                                $tabContent.find('#ap-backups-table, .blaminhor-essentials-notice.warning').remove();

                                // Insert new table/notice after h3
                                $h3.after(listResponse.data.html);

                                // Re-attach restore modal at the end
                                if ($restoreModal.length) {
                                    $tabContent.append($restoreModal);
                                }

                                // Re-bind sortable table events
                                self.bindSortable();

                                // Switch to the backups tab
                                if (typeof window.BlaminhorEssentialsActivateTab === 'function') {
                                    window.BlaminhorEssentialsActivateTab('backups');
                                } else {
                                    // Fallback: manually switch tabs
                                    $('.blaminhor-essentials-tab').removeClass('active');
                                    $('[data-tab="backups"]').addClass('active');
                                    $('.blaminhor-essentials-tab-content').removeClass('active');
                                    $tabContent.addClass('active');
                                }

                                // Build success message with domain warning if needed
                                var successMsg = self.strings.uploadSuccess || 'Backup uploaded successfully!';
                                var successHtml = '<div class="blaminhor-essentials-notice success ap-backup-success-notice" style="display: flex; align-items: center; gap: 10px; margin-bottom: 15px;">' +
                                    '<span class="dashicons dashicons-yes-alt" style="color: #00a32a; font-size: 20px;"></span>' +
                                    '<span>' + successMsg + ' (' + response.data.filename + ')</span>' +
                                    '</div>';

                                // Add domain warning if needed
                                if (response.data.domain_differs) {
                                    successHtml += '<div class="blaminhor-essentials-notice warning ap-backup-domain-notice" style="margin-bottom: 15px;">' +
                                        '<span class="dashicons dashicons-info" style="color: #dba617;"></span> ' +
                                        (self.strings.backupFrom || 'This backup is from') + ' <strong>' + response.data.backup_domain + '</strong>. ' +
                                        (self.strings.currentDomain || 'Current domain is') + ' <strong>' + response.data.current_domain + '</strong>.' +
                                        '</div>';
                                }

                                // Remove any existing notices and add new ones
                                $tabContent.find('.ap-backup-success-notice, .ap-backup-domain-notice').remove();
                                $h3.after(successHtml);

                                // Reset upload area
                                $result.hide();
                                $success.hide();
                                $domainWarning.hide();
                            } else {
                                // Fallback to page reload
                                location.reload();
                            }
                        }).fail(function() {
                            location.reload();
                        });
                    } else {
                        $error.show();
                        $('#ap-upload-error-message').text(response.data || self.strings.error);
                    }
                },
                error: function() {
                    $progress.hide();
                    $dropzone.show();
                    $result.show();
                    $error.show();
                    $('#ap-upload-error-message').text(self.strings.error || 'An error occurred.');
                }
            });
        },

        updateBackupCount: function(count) {
            var $tab = $('[data-tab="backups"]');
            if ($tab.length) {
                var text = $tab.text().replace(/\(\d+\)/, '(' + count + ')');
                $tab.text(text);
            }
        },

        openRestoreModal: function($btn) {
            var self = this;
            var prefix = $btn.data('prefix');
            var type = $btn.data('type');
            var components = $btn.data('components') || [];

            $('#ap-restore-filename').val(prefix);

            // Check checkboxes based on available components
            var hasDb = components.indexOf('database') !== -1;
            var hasFiles = components.indexOf('plugins') !== -1 || components.indexOf('themes') !== -1 ||
                           components.indexOf('uploads') !== -1 || components.indexOf('wp-content') !== -1 ||
                           components.indexOf('wp-core') !== -1;

            // For uploaded backups, also check if it's a single archive
            if (type === 'uploaded' || components.indexOf('uploaded') !== -1) {
                hasDb = true;
                hasFiles = true;
            }

            $('#ap-restore-database').prop('checked', hasDb).prop('disabled', !hasDb);
            $('#ap-restore-files').prop('checked', hasFiles).prop('disabled', !hasFiles);
            $('#ap-restore-progress').hide();
            $('#ap-restore-confirm').prop('disabled', false);

            // Hide domain option by default
            $('#ap-restore-domain-option').hide();
            $('#ap-restore-change-domain').prop('checked', true);
            $('#ap-restore-old-domain').val('');
            $('#ap-restore-new-domain').val('');

            // Check domain info for all backup types
            $.post(this.ajaxurl, {
                action: 'ap_backup_get_domain_info',
                nonce: this.nonce,
                prefix: prefix
            }, function(response) {
                if (response.success && response.data.domain_differs) {
                    $('#ap-restore-domain-option').show();
                    $('#ap-restore-domain-info').html(
                        (self.strings.backupFrom || 'This backup is from') + ' <strong>' + response.data.backup_domain + '</strong>. ' +
                        (self.strings.currentDomain || 'Current domain is') + ' <strong>' + response.data.current_domain + '</strong>.'
                    );
                    $('#ap-restore-old-domain').val(response.data.backup_url);
                    $('#ap-restore-new-domain').val(response.data.current_url);
                }
            });

            $('#ap-restore-modal').removeClass('ap-hidden').fadeIn(200);
        },

        restoreBackup: function() {
            var self = this;
            var $btn = $('#ap-restore-confirm');
            $btn.prop('disabled', true);
            $('#ap-restore-progress').show();

            var changeDomain = $('#ap-restore-domain-option').is(':visible') && $('#ap-restore-change-domain').is(':checked');
            var oldDomain = $('#ap-restore-old-domain').val();
            var newDomain = $('#ap-restore-new-domain').val();

            $.ajax({
                url: this.ajaxurl,
                type: 'POST',
                timeout: 600000, // 10 minutes timeout for large restores
                data: {
                    action: 'ap_backup_restore',
                    nonce: this.nonce,
                    filename: $('#ap-restore-filename').val(),
                    restore_database: $('#ap-restore-database').is(':checked'),
                    restore_files: $('#ap-restore-files').is(':checked'),
                    change_domain: changeDomain,
                    old_domain: oldDomain,
                    new_domain: newDomain
                },
                success: function(response) {
                    $('#ap-restore-modal').fadeOut(200, function() { $(this).addClass('ap-hidden'); });
                    if (response.success) {
                        alert(response.data.message || response.data);
                        // If domain was changed, redirect to new domain
                        if (response.data.redirect_url) {
                            window.location.href = response.data.redirect_url;
                        } else {
                            location.reload();
                        }
                    } else {
                        alert(response.data || self.strings.error);
                        $btn.prop('disabled', false);
                        $('#ap-restore-progress').hide();
                    }
                },
                error: function(xhr, status, error) {
                    $('#ap-restore-modal').fadeOut(200, function() { $(this).addClass('ap-hidden'); });
                    var msg = self.strings.error || 'An error occurred. Please try again.';
                    if (status === 'timeout') {
                        msg = 'The restore operation timed out. Please check if the restore completed successfully.';
                    }
                    alert(msg);
                    $btn.prop('disabled', false);
                    $('#ap-restore-progress').hide();
                }
            });
        },

        undoRestore: function($btn) {
            var self = this;

            // Prevent double clicks
            if ($btn.data('processing')) {
                return;
            }

            if (!confirm(this.strings.confirmUndo || 'Are you sure you want to undo the last restore? This will revert your site to its previous state.')) {
                return;
            }

            $btn.data('processing', true);
            var originalText = $btn.html();
            $btn.prop('disabled', true).html('<span class="spinner is-active" style="float: none; margin: 0 5px 0 0;"></span>' + (this.strings.undoing || 'Undoing...'));

            $.post(this.ajaxurl, {
                action: 'ap_backup_undo_restore',
                nonce: this.nonce
            }, function(response) {
                if (response.success) {
                    alert(response.data);
                    location.reload();
                } else {
                    // Don't show error if it's just "no backup found" - likely means it was already processed
                    $btn.data('processing', false);
                    $btn.prop('disabled', false).html(originalText);
                }
            }).fail(function() {
                $btn.data('processing', false);
                $btn.prop('disabled', false).html(originalText);
            });
        },

        formatSize: function(bytes) {
            if (!bytes || bytes === 0) return '0 B';
            var units = ['B', 'KB', 'MB', 'GB'];
            var i = Math.floor(Math.log(bytes) / Math.log(1024));
            return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + units[i];
        },

        showNotice: function(message, type) {
            var $target = $('.wrap > h1, .wrap > h2').first();
            if (!$target.length || !window.BlaminhorEssentials) return;
            BlaminhorEssentials.showNotice(message, type === 'success' ? 'success' : 'error', {
                container: $target,
                position: 'after',
                style: 'wp',
                timeout: 5000
            });
        },

        bindSortable: function() {
            var ns = '.blaminhorBackup';
            $('.ap-sortable-table th.ap-sortable').off('click' + ns).on('click' + ns, function() {
                var $th = $(this);
                var $table = $th.closest('table');
                var $tbody = $table.find('tbody');
                var colIndex = $th.index();
                var sortType = $th.data('sort') || 'string';
                var isAsc = !$th.hasClass('asc');

                $table.find('th.ap-sortable').removeClass('asc desc');
                $th.addClass(isAsc ? 'asc' : 'desc');

                var rows = $tbody.find('tr').toArray();
                var dataAttrs = ['filename', 'type', 'size', 'date'];

                rows.sort(function(a, b) {
                    var aVal, bVal;

                    if (sortType === 'number') {
                        var dataAttr = dataAttrs[colIndex];
                        aVal = parseFloat($(a).data(dataAttr)) || 0;
                        bVal = parseFloat($(b).data(dataAttr)) || 0;
                    } else {
                        var dataAttr = dataAttrs[colIndex];
                        if ($(a).data(dataAttr) !== undefined) {
                            aVal = String($(a).data(dataAttr)).toLowerCase();
                            bVal = String($(b).data(dataAttr)).toLowerCase();
                        } else {
                            aVal = $(a).find('td').eq(colIndex).text().toLowerCase();
                            bVal = $(b).find('td').eq(colIndex).text().toLowerCase();
                        }
                    }

                    if (aVal < bVal) return isAsc ? -1 : 1;
                    if (aVal > bVal) return isAsc ? 1 : -1;
                    return 0;
                });

                $.each(rows, function(i, row) {
                    $tbody.append(row);
                });
            });
        }
    };

    /**
     * Database Optimizer Module
     */
    window.BlaminhorEssentialsDbOptimizer = {
        nonce: '',
        ajaxurl: '',
        strings: {},
        pendingAction: null,
        initialized: false,

        init: function(config) {
            if (this.initialized) return;
            this.initialized = true;

            this.nonce = config.nonce || '';
            this.ajaxurl = config.ajaxurl || ajaxurl;
            this.strings = config.strings || {};
            this.bindEvents();
        },

        bindEvents: function() {
            var self = this;
            var ns = '.blaminhorDbOptimizer';

            // Modal functions
            $('#ap-db-modal-cancel, .ap-db-modal-overlay').off('click' + ns).on('click' + ns, function() {
                self.hideBackupModal();
            });

            // Dismiss backup notice
            $('#ap-db-dismiss-notice').off('click' + ns).on('click' + ns, function() {
                $('.ap-db-backup-notice').slideUp(200);
            });

            // Refresh button
            $('#ap-db-refresh-stats').off('click' + ns).on('click' + ns, function() {
                self.handleRefresh($(this));
            });

            // Clean individual type
            $('.ap-db-clean-btn').off('click' + ns).on('click' + ns, function() {
                self.cleanType($(this));
            });

            // Clean all - show backup modal first
            $('#ap-db-clean-all').off('click' + ns).on('click' + ns, function() {
                self.showBackupModal('clean');
            });

            // Optimize all tables - show backup modal first
            $('#ap-db-optimize-all-tables').off('click' + ns).on('click' + ns, function() {
                self.showBackupModal('optimize');
            });

            // Proceed after backup confirmation
            $('#ap-db-modal-proceed').off('click' + ns).on('click' + ns, function() {
                self.proceedAfterBackup();
            });

            // Optimize individual tables
            $('#ap-db-optimize-tables').off('click' + ns).on('click' + ns, function() {
                self.optimizeTables($(this));
            });

            // Create backup directly (notice + modal)
            $('#ap-db-create-backup-btn, #ap-db-modal-create-backup').off('click' + ns).on('click' + ns, function() {
                self.createDatabaseBackup($(this));
            });

            // Sortable table
            this.bindSortable();
        },

        showBackupModal: function(action) {
            this.pendingAction = action;
            if (action === 'optimize') {
                $('#ap-db-modal-text-clean').hide();
                $('#ap-db-modal-text-optimize').show();
            } else {
                $('#ap-db-modal-text-clean').show();
                $('#ap-db-modal-text-optimize').hide();
            }
            $('#ap-db-backup-modal').removeClass('ap-hidden').fadeIn(200);
        },

        hideBackupModal: function() {
            $('#ap-db-backup-modal').fadeOut(200, function() { $(this).addClass('ap-hidden'); });
            this.pendingAction = null;
        },

        refreshStats: function() {
            var self = this;
            $.post(this.ajaxurl, {
                action: 'ap_db_get_stats',
                nonce: this.nonce
            }, function(response) {
                if (response.success) {
                    $('#ap-db-total-items').text(response.data.total_items.toLocaleString());
                    $.each(response.data, function(key, value) {
                        if (typeof value === 'number') {
                            var $count = $('[data-count="' + key + '"]');
                            var itemText = value === 1 ? self.strings.itemSingular : self.strings.itemPlural;
                            $count.text(itemText.replace('%s', value.toLocaleString()));
                            $('.ap-db-clean-btn[data-type="' + key + '"]').prop('disabled', value === 0);
                        }
                    });
                    $('#ap-db-clean-all').prop('disabled', response.data.total_items === 0);
                }
            });
        },

        handleRefresh: function($btn) {
            var self = this;
            $btn.prop('disabled', true).find('.dashicons').addClass('spin');
            this.refreshStats();
            setTimeout(function() {
                $btn.prop('disabled', false).find('.dashicons').removeClass('spin');
            }, 1000);
        },

        cleanType: function($btn) {
            var self = this;
            var type = $btn.data('type');

            if (!confirm(this.strings.confirmClean)) {
                return;
            }

            $btn.prop('disabled', true).text(this.strings.cleaning);

            $.post(this.ajaxurl, {
                action: 'ap_db_optimize',
                nonce: this.nonce,
                type: type
            }, function(response) {
                if (response.success) {
                    alert(self.strings.cleanupCompleted + ' ' + response.data.deleted + ' ' + self.strings.itemsDeleted);
                    self.refreshStats();
                } else {
                    alert(self.strings.error + ' ' + response.data);
                }
                $btn.text(self.strings.clean);
            });
        },

        proceedAfterBackup: function() {
            var self = this;
            var action = this.pendingAction;
            this.hideBackupModal();

            if (action === 'clean') {
                var $btn = $('#ap-db-clean-all');
                $btn.prop('disabled', true).html('<span class="dashicons dashicons-update spin"></span> ' + this.strings.cleaning);

                $.post(this.ajaxurl, {
                    action: 'ap_db_optimize',
                    nonce: this.nonce,
                    type: 'all'
                }, function(response) {
                    if (response.success) {
                        alert(self.strings.cleanupCompleted + ' ' + response.data.total + ' ' + self.strings.itemsDeleted);
                        self.refreshStats();
                    } else {
                        alert(self.strings.error + ' ' + response.data);
                    }
                    $btn.html('<span class="dashicons dashicons-yes-alt"></span> ' + self.strings.cleanAll);
                });
            } else if (action === 'optimize') {
                var $btn = $('#ap-db-optimize-all-tables');
                $btn.prop('disabled', true).html('<span class="dashicons dashicons-update spin"></span> ' + this.strings.optimizing);

                $.post(this.ajaxurl, {
                    action: 'ap_db_optimize_tables',
                    nonce: this.nonce
                }, function(response) {
                    if (response.success) {
                        alert(self.strings.tablesOptimized);
                        location.reload();
                    } else {
                        alert(self.strings.error + ' ' + response.data);
                    }
                    $btn.prop('disabled', false);
                });
            }
        },

        optimizeTables: function($btn) {
            var self = this;
            $btn.prop('disabled', true).html('<span class="dashicons dashicons-update spin"></span> ' + this.strings.optimizing);

            $.post(this.ajaxurl, {
                action: 'ap_db_optimize_tables',
                nonce: this.nonce
            }, function(response) {
                if (response.success) {
                    alert(self.strings.tablesOptimized);
                    location.reload();
                } else {
                    alert(self.strings.error + ' ' + response.data);
                }
                $btn.prop('disabled', false);
            });
        },

        createDatabaseBackup: function($btn) {
            var self = this;
            var isModal = $btn.attr('id') === 'ap-db-modal-create-backup';
            var originalHtml = $btn.html();

            $btn.prop('disabled', true).html(
                '<span class="dashicons dashicons-update spin"></span> ' + this.strings.creatingBackup
            );

            $.post(this.ajaxurl, {
                action: 'ap_dboptimizer_create_backup',
                nonce: this.nonce
            }, function(response) {
                if (response.success) {
                    if (isModal) {
                        self.proceedAfterBackup();
                    } else {
                        $('#ap-db-backup-result').html(
                            '<p class="description" style="color: #46b450;">' +
                            '<span class="dashicons dashicons-yes-alt"></span> ' +
                            self.strings.backupCreated + ' ' + response.data.filename +
                            '</p>'
                        );
                        $btn.html(
                            '<span class="dashicons dashicons-yes-alt"></span> ' +
                            response.data.filename
                        );
                    }
                } else {
                    $btn.prop('disabled', false).html(originalHtml);
                    alert(self.strings.errorCreatingBackup + (response.data ? ' ' + response.data : ''));
                }
            }).fail(function() {
                $btn.prop('disabled', false).html(originalHtml);
                alert(self.strings.errorCreatingBackup);
            });
        },

        bindSortable: function() {
            var ns = '.blaminhorDbOptimizer';
            $('.ap-sortable-table th.ap-sortable').off('click' + ns).on('click' + ns, function() {
                var $th = $(this);
                var $table = $th.closest('table');
                var $tbody = $table.find('tbody');
                var colIndex = $th.index();
                var sortType = $th.data('sort') || 'string';
                var isAsc = !$th.hasClass('asc');

                $table.find('th.ap-sortable').removeClass('asc desc');
                $th.addClass(isAsc ? 'asc' : 'desc');

                var rows = $tbody.find('tr').toArray();
                rows.sort(function(a, b) {
                    var aVal, bVal;

                    if (sortType === 'number' || sortType === 'size') {
                        var dataAttr = ['name', 'rows', 'size', 'overhead'][colIndex];
                        aVal = parseFloat($(a).data(dataAttr)) || 0;
                        bVal = parseFloat($(b).data(dataAttr)) || 0;
                    } else {
                        aVal = $(a).find('td').eq(colIndex).text().toLowerCase();
                        bVal = $(b).find('td').eq(colIndex).text().toLowerCase();
                    }

                    if (aVal < bVal) return isAsc ? -1 : 1;
                    if (aVal > bVal) return isAsc ? 1 : -1;
                    return 0;
                });

                $.each(rows, function(i, row) {
                    $tbody.append(row);
                });
            });
        }
    };

    /**
     * Redirections Module
     */
    window.BlaminhorEssentialsRedirections = {
        nonce: '',
        ajaxurl: '',
        strings: {},
        currentPage: 1,
        searchTimeout: null,
        initialized: false,

        init: function(config) {
            if (this.initialized) return;
            this.initialized = true;

            this.nonce = config.nonce || '';
            this.ajaxurl = config.ajaxurl || ajaxurl;
            this.strings = config.strings || {};
            this.bindEvents();
            this.loadRedirections();
        },

        bindEvents: function() {
            var self = this;
            var ns = '.blaminhorRedirections';

            // Search
            $('#ap-redirections-search').off('input' + ns).on('input' + ns, function() {
                clearTimeout(self.searchTimeout);
                var search = $(this).val();
                self.searchTimeout = setTimeout(function() {
                    self.loadRedirections(1, search);
                }, 300);
            });

            // Pagination
            $(document).off('click' + ns, '.ap-page-btn').on('click' + ns, '.ap-page-btn', function() {
                var page = $(this).data('page');
                var search = $('#ap-redirections-search').val();
                self.loadRedirections(page, search);
            });

            // Toggle redirect
            $(document).off('click' + ns, '.ap-toggle-btn').on('click' + ns, '.ap-toggle-btn', function() {
                self.toggleRedirect($(this));
            });

            // Delete redirect
            $(document).off('click' + ns, '.ap-delete-btn').on('click' + ns, '.ap-delete-btn', function() {
                self.deleteRedirect($(this));
            });

            // Refresh
            $('#ap-redirections-refresh').off('click' + ns).on('click' + ns, function() {
                var search = $('#ap-redirections-search').val();
                self.loadRedirections(self.currentPage || 1, search);
            });

            // Add redirect button
            $('#ap-add-redirection-btn').off('click' + ns).on('click' + ns, function() {
                self.addRedirect();
            });

            // Edit redirect
            $(document).off('click' + ns, '.ap-edit-btn').on('click' + ns, '.ap-edit-btn', function() {
                self.openEditModal($(this));
            });

            // Save edit
            $('#ap-edit-save').off('click' + ns).on('click' + ns, function() {
                self.saveEdit();
            });

            // Close modal
            $('.ap-modal-close, .ap-db-modal-overlay').off('click' + ns).on('click' + ns, function() {
                $('#ap-edit-modal').hide();
            });

            // Import CSV
            $('#ap-import-btn').off('click' + ns).on('click' + ns, function() {
                self.importCsv();
            });

            // Import from plugin
            $(document).off('click' + ns, '.ap-import-plugin-btn').on('click' + ns, '.ap-import-plugin-btn', function() {
                var $btn = $(this);
                var source = $btn.data('source');
                var skipDuplicates = $('.ap-import-skip-duplicates[data-source="' + source + '"]').is(':checked');
                self.startPluginImport(source, skipDuplicates, 0);
            });

            // Select all / deselect all
            $('#ap-redirections-select-all').off('change' + ns).on('change' + ns, function() {
                var checked = $(this).is(':checked');
                $('.ap-redirect-checkbox').prop('checked', checked);
                self.updateRedirectionsBulkActions();
            });

            // Individual checkbox
            $(document).off('change' + ns, '.ap-redirect-checkbox').on('change' + ns, '.ap-redirect-checkbox', function() {
                var total = $('.ap-redirect-checkbox').length;
                var checked = $('.ap-redirect-checkbox:checked').length;
                $('#ap-redirections-select-all').prop('checked', checked === total && total > 0);
                self.updateRedirectionsBulkActions();
            });

            // Bulk delete
            $(document).off('click' + ns, '.ap-redirections-bulk-delete-btn').on('click' + ns, '.ap-redirections-bulk-delete-btn', function() {
                self.bulkDeleteRedirections();
            });
        },

        loadRedirections: function(page, search) {
            var self = this;
            page = page || 1;
            search = search || '';

            $('#ap-redirections-tbody').html(
                '<tr><td colspan="6" style="text-align: center; padding: 20px;">' +
                '<span class="dashicons dashicons-update spin"></span> ' + this.strings.loading +
                '</td></tr>'
            );

            $.post(this.ajaxurl, {
                action: 'ap_redirections_get',
                nonce: this.nonce,
                page: page,
                search: search
            }, function(response) {
                if (response.success && response.data && response.data.redirections) {
                    var html = '';

                    if (response.data.redirections.length === 0) {
                        html = '<tr><td colspan="6" style="text-align: center; padding: 20px;">' + self.strings.noRedirections + '</td></tr>';
                    } else {
                        response.data.redirections.forEach(function(r) {
                            var statusText = r.is_active == 1 ? self.strings.active : self.strings.inactive;
                            var typeText = r.is_regex == 1 ? 'Regex' : self.strings.simple;
                            var isLoop = r.is_loop == 1;

                            html += '<tr data-id="' + r.id + '"' + (isLoop ? ' class="ap-redirect-loop-row"' : '') + '>';
                            html += '<td style="text-align: center;"><input type="checkbox" class="ap-redirect-checkbox" value="' + r.id + '"></td>';
                            html += '<td style="word-break: break-all;"><code style="font-size: 12px;">' + self.escapeHtml(r.source_url) + '</code>';
                            if (isLoop) {
                                html += ' <span class="ap-loop-badge" title="' + self.escapeAttr(self.strings.loopTooltip || '') + '"><span class="dashicons dashicons-warning"></span> ' + (self.strings.loopWarning || 'Loop') + '</span>';
                            }
                            html += '</td>';
                            html += '<td style="word-break: break-all;"><code style="font-size: 12px;">' + self.escapeHtml(r.target_url) + '</code></td>';
                            html += '<td><span class="' + (r.is_regex == 1 ? 'ap-seo-status-no' : '') + '">' + typeText + '</span></td>';
                            html += '<td>' + parseInt(r.hit_count).toLocaleString() + '</td>';
                            html += '<td>';
                            html += '<button type="button" class="button button-small ap-toggle-btn" data-id="' + r.id + '" title="' + self.strings.toggle + '">';
                            html += '<span class="dashicons dashicons-' + (r.is_active == 1 ? 'visibility' : 'hidden') + '" style="font-size: 14px; width: 14px; height: 14px; margin-top: 3px;"></span>';
                            html += '</button> ';
                            html += '<button type="button" class="button button-small ap-edit-btn" data-id="' + r.id + '" data-source="' + self.escapeAttr(r.source_url) + '" data-target="' + self.escapeAttr(r.target_url) + '" data-regex="' + r.is_regex + '" title="' + self.strings.edit + '">';
                            html += '<span class="dashicons dashicons-edit" style="font-size: 14px; width: 14px; height: 14px; margin-top: 3px;"></span>';
                            html += '</button> ';
                            html += '<button type="button" class="button button-small ap-delete-btn" data-id="' + r.id + '" title="' + self.strings.deleteTitle + '">';
                            html += '<span class="dashicons dashicons-trash" style="font-size: 14px; width: 14px; height: 14px; margin-top: 3px; color: #d63638;"></span>';
                            html += '</button>';
                            html += '</td>';
                            html += '</tr>';
                        });
                    }

                    $('#ap-redirections-tbody').html(html);

                    // Pagination
                    var pagination = '';
                    if (response.data.pages > 1) {
                        for (var i = 1; i <= response.data.pages; i++) {
                            pagination += '<button type="button" class="button ' + (i === response.data.current_page ? 'button-primary' : 'button-secondary') + ' ap-page-btn" data-page="' + i + '">' + i + '</button> ';
                        }
                    }
                    $('#ap-redirections-pagination').html(pagination);

                    self.currentPage = response.data.current_page;

                    // Update summary cards
                    if (typeof response.data.total_redirections !== 'undefined') {
                        $('#ap-total-redirections').text(response.data.total_redirections);
                        $('#ap-active-redirections').text(response.data.active_count);
                        $('#ap-total-hits').text(parseInt(response.data.total_hits || 0).toLocaleString());
                    }

                    // Loop banner
                    $('#ap-loop-banner').remove();
                    var loopCount = parseInt(response.data.loop_count || 0);
                    if (loopCount > 0) {
                        var bannerText = (self.strings.loopBanner || '%d redirection(s) detected as loops.').replace('%d', loopCount);
                        var banner = '<div id="ap-loop-banner" class="blaminhor-essentials-notice warning" style="margin-bottom: 15px;">' +
                            '<span class="dashicons dashicons-warning"></span> ' + bannerText + '</div>';
                        $('#ap-redirections-table').before(banner);
                    }

                    // Reset bulk selection
                    $('#ap-redirections-select-all').prop('checked', false);
                    self.updateRedirectionsBulkActions();
                } else {
                    // Handle error response
                    $('#ap-redirections-tbody').html(
                        '<tr><td colspan="6" style="text-align: center; padding: 20px;">' + self.strings.noRedirections + '</td></tr>'
                    );
                }
            }).fail(function() {
                // Handle AJAX failure
                $('#ap-redirections-tbody').html(
                    '<tr><td colspan="6" style="text-align: center; padding: 20px; color: #d63638;">' +
                    (self.strings.error || 'An error occurred.') + '</td></tr>'
                );
            });
        },

        updateRedirectionsBulkActions: function() {
            var checked = $('.ap-redirect-checkbox:checked').length;
            var $bar = $('#ap-redirections-bulk-actions');
            if (checked > 0) {
                var text = (this.strings.selectedCount || '%d selected').replace('%d', checked);
                $bar.find('.ap-bulk-count').text(text);
                $bar.css('display', 'flex');
            } else {
                $bar.hide();
            }
        },

        bulkDeleteRedirections: function() {
            var self = this;
            var ids = [];
            $('.ap-redirect-checkbox:checked').each(function() {
                ids.push($(this).val());
            });

            if (ids.length === 0) return;

            if (!confirm(this.strings.confirmBulkDelete)) {
                return;
            }

            var $btn = $('.ap-redirections-bulk-delete-btn');
            var originalHtml = $btn.html();
            $btn.prop('disabled', true).text(this.strings.deleting);

            $.post(this.ajaxurl, {
                action: 'ap_redirections_bulk_delete',
                nonce: this.nonce,
                ids: ids
            }, function(response) {
                $btn.prop('disabled', false).html(originalHtml);
                if (response.success) {
                    $('#ap-redirections-select-all').prop('checked', false);
                    self.loadRedirections(self.currentPage);
                } else {
                    alert(response.data || self.strings.error);
                }
            }).fail(function() {
                alert(self.strings.error);
                $btn.prop('disabled', false).html(originalHtml);
            });
        },

        toggleRedirect: function($btn) {
            var self = this;
            var id = $btn.data('id');

            $.post(this.ajaxurl, {
                action: 'ap_redirections_toggle',
                nonce: this.nonce,
                id: id
            }, function(response) {
                if (response.success) {
                    if (response.data.warning) {
                        alert(response.data.warning);
                    }
                    var $icon = $btn.find('.dashicons');
                    if (response.data.is_active) {
                        $icon.removeClass('dashicons-hidden').addClass('dashicons-visibility');
                    } else {
                        $icon.removeClass('dashicons-visibility').addClass('dashicons-hidden');
                    }
                    self.loadRedirections(self.currentPage, $('#ap-redirections-search').val());
                }
            });
        },

        deleteRedirect: function($btn) {
            var self = this;
            if (!confirm(this.strings.confirmDelete)) {
                return;
            }

            var id = $btn.data('id');
            var $row = $btn.closest('tr');

            $.post(this.ajaxurl, {
                action: 'ap_redirections_delete',
                nonce: this.nonce,
                id: id
            }, function(response) {
                if (response.success) {
                    $row.fadeOut(function() {
                        $(this).remove();
                        self.loadRedirections(self.currentPage, $('#ap-redirections-search').val());
                    });
                }
            });
        },

        addRedirect: function() {
            var self = this;
            var $btn = $('#ap-add-redirection-btn');
            var originalText = $btn.text();
            $btn.prop('disabled', true).text(this.strings.adding);

            $.post(this.ajaxurl, {
                action: 'ap_redirections_add',
                nonce: this.nonce,
                source_url: $('#ap-add-source').val(),
                target_url: $('#ap-add-target').val(),
                is_regex: $('#ap-add-regex').is(':checked') ? 1 : 0
            }, function(response) {
                $btn.prop('disabled', false).text(originalText);
                var resultHtml = '';
                if (response.success) {
                    $('#ap-add-source, #ap-add-target').val('');
                    $('#ap-add-regex').prop('checked', false);
                    self.loadRedirections();
                    if (response.data.warning) {
                        resultHtml = '<div class="blaminhor-essentials-notice warning"><p><span class="dashicons dashicons-warning"></span> ' + response.data.warning + '</p></div>';
                    } else {
                        resultHtml = '<div class="blaminhor-essentials-notice success"><p>' + (response.data.message || '') + '</p></div>';
                    }
                } else {
                    resultHtml = '<div class="blaminhor-essentials-notice error"><p>' + (response.data || self.strings.error) + '</p></div>';
                }
                $('#ap-add-result').html(resultHtml).show();
            });
        },

        openEditModal: function($btn) {
            $('#ap-edit-id').val($btn.data('id'));
            $('#ap-edit-source').val($btn.data('source'));
            $('#ap-edit-target').val($btn.data('target'));
            $('#ap-edit-regex').prop('checked', $btn.data('regex') == 1);
            $('#ap-edit-modal').show();
        },

        saveEdit: function() {
            var self = this;
            var $btn = $('#ap-edit-save');
            $btn.prop('disabled', true).text(this.strings.saving);

            $.post(this.ajaxurl, {
                action: 'ap_redirections_edit',
                nonce: this.nonce,
                id: $('#ap-edit-id').val(),
                source_url: $('#ap-edit-source').val(),
                target_url: $('#ap-edit-target').val(),
                is_regex: $('#ap-edit-regex').is(':checked') ? 1 : 0
            }, function(response) {
                $btn.prop('disabled', false).text(self.strings.saveChanges);
                if (response.success) {
                    if (response.data.warning) {
                        alert(response.data.warning);
                    }
                    $('#ap-edit-modal').hide();
                    self.loadRedirections(self.currentPage);
                } else {
                    alert(response.data || self.strings.error);
                }
            });
        },

        importCsv: function() {
            var self = this;
            var csvContent = $('#ap-import-csv').val().trim();
            var fileInput = $('#ap-import-file')[0];
            var file = fileInput && fileInput.files ? fileInput.files[0] : null;

            if (!csvContent && !file) {
                alert(this.strings.selectFile);
                return;
            }

            var sendImport = function(content) {
                $.post(self.ajaxurl, {
                    action: 'ap_redirections_import',
                    nonce: self.nonce,
                    csv_content: content
                }, function(response) {
                    var resultHtml = '';
                    if (response.success) {
                        if (response.data.imported > 0) {
                            resultHtml = '<div class="blaminhor-essentials-notice success"><p>' + self.strings.importSuccess.replace('%d', response.data.imported) + '</p></div>';
                        }
                        if (response.data.skipped > 0) {
                            resultHtml += '<div class="blaminhor-essentials-notice error"><p>' + self.strings.duplicateExists + '</p></div>';
                        }
                        if (response.data.errors > 0) {
                            resultHtml += '<div class="blaminhor-essentials-notice error"><p>' + response.data.errors + ' ' + self.strings.someErrors + '</p></div>';
                        }
                        if (response.data.imported === 0 && response.data.skipped === 0 && response.data.errors === 0) {
                            resultHtml = '<div class="blaminhor-essentials-notice error"><p>' + self.strings.importFailed + '</p></div>';
                        }
                        $('#ap-import-csv').val('');
                        if (fileInput) fileInput.value = '';
                        self.loadRedirections();
                    } else {
                        resultHtml = '<div class="blaminhor-essentials-notice error"><p>' + (response.data || self.strings.importFailed) + '</p></div>';
                    }

                    $('#ap-import-result').html(resultHtml).show();
                });
            };

            if (csvContent) {
                sendImport(csvContent);
            } else {
                var reader = new FileReader();
                reader.onload = function(e) {
                    sendImport(e.target.result);
                };
                reader.readAsText(file);
            }
        },

        escapeHtml: function(text) {
            var div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        },

        escapeAttr: function(text) {
            return text.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
        },

        startPluginImport: function(source, skipDuplicates, offset) {
            var self = this;
            var $card = $('.ap-import-source-card:has([data-source="' + source + '"])');
            var $btn = $card.find('.ap-import-plugin-btn');
            var $progress = $card.find('.ap-import-plugin-progress[data-source="' + source + '"]');
            var $progressBar = $progress.find('.ap-import-progress-bar');
            var $status = $progress.find('.ap-import-status');
            var $result = $card.find('.ap-import-plugin-result[data-source="' + source + '"]');

            // Initialize UI
            if (offset === 0) {
                $btn.prop('disabled', true);
                $progress.show();
                $result.hide().empty();
                $progressBar.css('width', '0%');
                $status.text(this.strings.loading);
                this.importStats = { imported: 0, skipped: 0 };
            }

            $.post(this.ajaxurl, {
                action: 'ap_redirections_import_plugin',
                nonce: this.nonce,
                source: source,
                skip_duplicates: skipDuplicates ? 'true' : 'false',
                offset: offset
            }, function(response) {
                if (response.success) {
                    var data = response.data;
                    self.importStats.imported += data.imported;
                    self.importStats.skipped += data.skipped;

                    var percent = Math.round((data.processed / data.total) * 100);
                    $progressBar.css('width', percent + '%');
                    $status.text(data.processed + ' / ' + data.total);

                    if (data.has_more) {
                        self.startPluginImport(source, skipDuplicates, data.next_offset);
                    } else {
                        // Complete
                        $progress.hide();
                        $btn.prop('disabled', false);

                        var resultHtml = '<div class="blaminhor-essentials-notice success">';
                        resultHtml += '<strong>' + self.importStats.imported + '</strong> ' + (self.strings.redirectionsImported || 'redirections imported');
                        if (self.importStats.skipped > 0) {
                            resultHtml += ', <strong>' + self.importStats.skipped + '</strong> ' + (self.strings.duplicatesSkipped || 'duplicates skipped');
                        }
                        resultHtml += '</div>';
                        $result.html(resultHtml).show();

                        // Reload the redirections list
                        self.loadRedirections();
                    }
                } else {
                    $progress.hide();
                    $btn.prop('disabled', false);
                    $result.html('<div class="blaminhor-essentials-notice error">' + (response.data.message || 'Import failed') + '</div>').show();
                }
            }).fail(function() {
                $progress.hide();
                $btn.prop('disabled', false);
                $result.html('<div class="blaminhor-essentials-notice error">Import failed</div>').show();
            });
        },

        importStats: { imported: 0, skipped: 0 }
    };

    /**
     * SEO Manager Metabox Module
     * Handles the SEO meta box in the post editor
     */
    window.BlaminhorEssentialsSeoMetabox = {
        defaultDesc: '',
        defaultTitle: '',
        initialized: false,
        isDirty: false,
        initialState: null,

        init: function(config) {
            if (this.initialized) return;
            this.initialized = true;

            this.defaultDesc = config.defaultDesc || '';
            this.defaultTitle = config.defaultTitle || '';
            this.variables = config.variables || {};
            this.nonce = config.nonce || '';
            this.ajaxurl = config.ajaxurl || '';
            this.postId = config.postId || 0;
            this.strings = config.strings || {};
            this.bindEvents();
            this.initFields();
            this.initKeywordsTags();
            this.initKeyTakeaways();
            this.initSeoScore();
            this.initGeoScore();
            this.captureInitialState();
            this.bindDirtyTracking();
        },

        bindEvents: function() {
            var self = this;
            var ns = '.blaminhorSeoMetabox';

            // Tabs
            $('.ap-seo-tab').off('click' + ns).on('click' + ns, function() {
                var tab = $(this).data('tab');
                $('.ap-seo-tab').removeClass('active');
                $(this).addClass('active');
                $('.ap-seo-tab-content').removeClass('active');
                $('[data-tab-content="' + tab + '"]').addClass('active');
            });

            // Click on SERP preview to focus input
            $('.ap-serp-title-sync-desktop, .ap-serp-title-sync-mobile').off('click' + ns).on('click' + ns, function() {
                $('#ap_seo_title').focus();
            });
            $('.ap-serp-desc-sync-desktop, .ap-serp-desc-sync-mobile').off('click' + ns).on('click' + ns, function() {
                $('#ap_seo_description').focus();
            });

            // Character counters
            $('#ap_seo_title').off('input' + ns).on('input' + ns, function() {
                self.updateCharCount(this, '#ap-title-count', 60, 55);
                var text = $(this).val() || self.defaultTitle || $(this).attr('placeholder');
                self.updateSerpTitle(text);
            });

            $('#ap_seo_description').off('input' + ns).on('input' + ns, function() {
                self.updateCharCount(this, '#ap-desc-count', 160, 155);
                var text = $(this).val() || self.defaultDesc;
                self.updateSerpDesc(text);

                // Sync to Schema Description placeholder if it's empty.
                var $schemaDesc = $('#ap_schema_description');
                if ($schemaDesc.length && !$schemaDesc.val()) {
                    $schemaDesc.attr('placeholder', $(this).val() || $schemaDesc.data('default-placeholder') || '');
                }
            });

            // Keywords tags
            $('#ap_seo_keywords_input').off('keydown' + ns).on('keydown' + ns, function(e) {
                if (e.key === 'Enter' || e.key === ',') {
                    e.preventDefault();
                    self.addKeyword($(this).val().trim());
                    $(this).val('');
                }
            });

            // Remove keyword tag (use delegation for dynamically added elements)
            $(document).off('click' + ns, '.ap-seo-keyword-remove').on('click' + ns, '.ap-seo-keyword-remove', function() {
                $(this).closest('.ap-seo-keyword-tag').remove();
                self.updateKeywordsHidden();
            });

            // Variable buttons insertion (Title, Separator, Site Name, etc.)
            $(document).off('click' + ns, '.seo-var-btn').on('click' + ns, '.seo-var-btn', function() {
                var $btn = $(this);
                var target = $btn.closest('.seo-title-variables').data('target');
                if (!target) return;
                var $input = $(target);
                if (!$input.length) return;

                var input = $input[0];
                var varText = $btn.data('var');
                var start = input.selectionStart;
                var end = input.selectionEnd;
                var val = $input.val();

                if (start > 0 && val.charAt(start - 1) !== ' ') {
                    varText = ' ' + varText;
                }

                $input.val(val.substring(0, start) + varText + val.substring(end));
                input.focus();
                var newPos = start + varText.length;
                input.setSelectionRange(newPos, newPos);
                $input.trigger('input');
            });

            // Schema JSON-LD validation
            $('#ap_schema_custom_json').off('input' + ns).on('input' + ns, function() {
                var val = $(this).val().trim();
                var $status = $('#ap-schema-json-status');
                if (!val) {
                    $status.text('').removeClass('valid invalid');
                    return;
                }
                try {
                    JSON.parse(val);
                    $status.text('✓ Valid JSON').removeClass('invalid').addClass('valid');
                } catch (e) {
                    $status.text('✗ Invalid JSON: ' + e.message).removeClass('valid').addClass('invalid');
                }
            });
        },

        initFields: function() {
            // Use requestAnimationFrame to ensure DOM is fully rendered
            var self = this;
            requestAnimationFrame(function() {
                $('#ap_seo_title').trigger('input');
                $('#ap_seo_description').trigger('input');

                // Store default placeholder for Schema Description, then sync from meta desc.
                var $schemaDesc = $('#ap_schema_description');
                if ($schemaDesc.length) {
                    $schemaDesc.data('default-placeholder', $schemaDesc.attr('placeholder') || '');
                    var metaDesc = $('#ap_seo_description').val();
                    if (!$schemaDesc.val() && metaDesc) {
                        $schemaDesc.attr('placeholder', metaDesc);
                    }
                }
            });
        },

        initKeywordsTags: function() {
            // Keywords are already rendered by PHP
            // Just ensure the hidden field is in sync
            this.updateKeywordsHidden();
        },

        addKeyword: function(keyword) {
            if (!keyword) return;

            // Check for duplicates
            var existing = [];
            $('#ap_seo_keywords_tags .ap-seo-keyword-tag').each(function() {
                existing.push($(this).text().replace('×', '').trim().toLowerCase());
            });

            if (existing.indexOf(keyword.toLowerCase()) !== -1) return;

            // Add the tag
            var tag = $('<span class="ap-seo-keyword-tag">' + keyword + '<button type="button" class="ap-seo-keyword-remove">&times;</button></span>');
            $('#ap_seo_keywords_tags').append(tag);
            this.updateKeywordsHidden();
        },

        updateKeywordsHidden: function() {
            var keywords = [];
            $('#ap_seo_keywords_tags .ap-seo-keyword-tag').each(function() {
                keywords.push($(this).text().replace('×', '').trim());
            });
            $('#ap_seo_keywords').val(keywords.join(', '));
        },

        replaceVariables: function(text) {
            if (!text || !this.variables) return text;
            var result = text;
            $.each(this.variables, function(varName, val) {
                result = result.split(varName).join(val);
            });
            return result.replace(/\s*\{[a-z_]+\}/g, '').trim();
        },

        updateSerpTitle: function(text) {
            text = this.replaceVariables(text);
            // Desktop: CSS handles single-line truncation (nowrap + text-overflow: ellipsis)
            $('.ap-serp-title-sync-desktop').text(text);
            // Mobile: JS truncates to 2 lines at character boundary
            var mobileEl = document.querySelector('.ap-serp-title-sync-mobile');
            if (mobileEl) serpTruncate(mobileEl, text, 2, false);
        },

        updateSerpDesc: function(text) {
            text = this.replaceVariables(text);
            var desktopEl = document.querySelector('.ap-serp-desc-sync-desktop .ap-serp-desc-text');
            if (desktopEl) serpTruncate(desktopEl, text, 2, true);
            var mobileEl = document.querySelector('.ap-serp-desc-sync-mobile .ap-serp-desc-text');
            if (mobileEl) serpTruncate(mobileEl, text, 3, true);
        },

        updateCharCount: function(input, counter, max, warnAt) {
            var len = $(input).val().length;
            $(counter).text(len + '/' + max);
            $(counter).removeClass('warning error');
            if (len > max) {
                $(counter).addClass('error');
            } else if (len > warnAt) {
                $(counter).addClass('warning');
            }
        },

        getFieldsState: function() {
            return {
                title: $('#ap_seo_title').val() || '',
                description: $('#ap_seo_description').val() || '',
                keywords: $('#ap_seo_keywords').val() || '',
                noindex: $('[name="ap_seo_noindex"]').is(':checked'),
                nofollow: $('[name="ap_seo_nofollow"]').is(':checked'),
                canonical: $('#ap_seo_canonical').val() || ''
            };
        },

        captureInitialState: function() {
            var self = this;
            requestAnimationFrame(function() {
                self.initialState = self.getFieldsState();
                self.isDirty = false;
            });
        },

        checkDirty: function() {
            if (!this.initialState) return;
            var current = this.getFieldsState();
            var initial = this.initialState;
            this.isDirty = (
                current.title !== initial.title ||
                current.description !== initial.description ||
                current.keywords !== initial.keywords ||
                current.noindex !== initial.noindex ||
                current.nofollow !== initial.nofollow ||
                current.canonical !== initial.canonical
            );
        },

        bindDirtyTracking: function() {
            var self = this;
            var ns = '.blaminhorSeoDirty';

            $('#ap_seo_title, #ap_seo_description, #ap_seo_canonical').off('input' + ns).on('input' + ns, function() {
                self.checkDirty();
            });

            $('[name="ap_seo_noindex"], [name="ap_seo_nofollow"]').off('change' + ns).on('change' + ns, function() {
                self.checkDirty();
            });

            $(document).off('click' + ns, '.ap-seo-keyword-remove').on('click' + ns, '.ap-seo-keyword-remove', function() {
                setTimeout(function() { self.checkDirty(); }, 50);
            });

            $('#ap_seo_keywords_input').off('keydown' + ns).on('keydown' + ns, function(e) {
                if (e.key === 'Enter' || e.key === ',') {
                    setTimeout(function() { self.checkDirty(); }, 50);
                }
            });

            $(window).off('beforeunload' + ns).on('beforeunload' + ns, function() {
                if (self.isDirty) {
                    return true;
                }
            });

            // Reset dirty flag on form submit (save).
            $('form#post, #publish, #save-post').off('click' + ns).on('click' + ns, function() {
                self.isDirty = false;
            });
        },

        initKeyTakeaways: function() {
            var ns = '.blaminhorKeyTakeaways';

            // Add takeaway
            $('#ap-key-takeaway-add').off('click' + ns).on('click' + ns, function() {
                var html = '<div class="ap-key-takeaway-item">' +
                    '<input type="text" name="ap_key_takeaways[]" value="" placeholder="' + self_escAttr('Add a key takeaway...') + '">' +
                    '<button type="button" class="button ap-key-takeaway-remove" title="Remove">&times;</button>' +
                    '</div>';
                $('#ap-key-takeaways-list').append(html);
            });

            // Remove takeaway
            $(document).off('click' + ns, '.ap-key-takeaway-remove').on('click' + ns, '.ap-key-takeaway-remove', function() {
                var $list = $('#ap-key-takeaways-list');
                if ($list.find('.ap-key-takeaway-item').length > 1) {
                    $(this).closest('.ap-key-takeaway-item').remove();
                } else {
                    $(this).closest('.ap-key-takeaway-item').find('input').val('');
                }
            });
        },

        getEditorContent: function() {
            if (wp && wp.data && wp.data.select('core/editor')) {
                return wp.data.select('core/editor').getEditedPostContent();
            }
            if (typeof tinyMCE !== 'undefined' && tinyMCE.get('content')) {
                return tinyMCE.get('content').getContent();
            }
            var $textarea = $('#content');
            return $textarea.length ? $textarea.val() : '';
        },

        getLiveMetabox: function() {
            var data = {};
            data.seo_title = $('#ap_seo_title').val() || '';
            data.seo_description = $('#ap_seo_description').val() || '';
            data.seo_keywords = $('#ap_seo_keywords').val() || '';
            data.noindex = $('#ap_noindex').is(':checked') ? '1' : '';
            data.schema_type = $('#ap_schema_type').val() || '';
            data.schema_description = $('#ap_schema_description').val() || '';
            data.key_takeaways = [];
            $('input[name="ap_key_takeaways[]"]').each(function() {
                var v = $(this).val();
                if (v) data.key_takeaways.push(v);
            });
            data.content = this.getEditorContent();
            return data;
        },

        initSeoScore: function() {
            if (!this.postId || !this.ajaxurl) return;
            this.loadSeoScore();
        },

        loadSeoScore: function(keepDetailsOpen) {
            var self = this;
            var $container = $('#ap-seo-score-container');
            var isRefresh = $container.find('.ap-score-widget').length > 0;
            if (isRefresh) {
                $container.css('min-height', $container.outerHeight());
                $container.find('.ap-score-refresh-btn').addClass('ap-spinning');
            } else {
                $container.html('<div class="ap-score-loading">' + (this.strings && this.strings.analyzing ? this.strings.analyzing : 'Analyzing content...') + '</div>');
            }

            var ajaxData = {
                action: 'ap_seo_seo_score',
                nonce: this.nonce,
                post_id: this.postId
            };
            if (isRefresh) {
                ajaxData.live_meta = this.getLiveMetabox();
            }

            $.post(this.ajaxurl, ajaxData, function(response) {
                if (response.success) {
                    self.renderScoreWidget(response.data, '#ap-seo-score-container', keepDetailsOpen);
                } else {
                    $container.html('<p class="description">Unable to calculate score.</p>');
                }
            }).fail(function() {
                $container.html('<p class="description">Unable to calculate score.</p>');
            });
        },

        initGeoScore: function() {
            if (!this.postId || !this.ajaxurl) return;
            this.loadGeoScore();
        },

        loadGeoScore: function(keepDetailsOpen) {
            var self = this;
            var $container = $('#ap-geo-score-container');
            var isRefresh = $container.find('.ap-score-widget').length > 0;
            if (isRefresh) {
                $container.css('min-height', $container.outerHeight());
                $container.find('.ap-score-refresh-btn').addClass('ap-spinning');
            } else {
                $container.html('<div class="ap-score-loading">' + (this.strings && this.strings.analyzing ? this.strings.analyzing : 'Analyzing content...') + '</div>');
            }

            var ajaxData = {
                action: 'ap_seo_geo_score',
                nonce: this.nonce,
                post_id: this.postId
            };
            if (isRefresh) {
                ajaxData.live_meta = this.getLiveMetabox();
            }

            $.post(this.ajaxurl, ajaxData, function(response) {
                if (response.success) {
                    self.renderGeoScore(response.data, keepDetailsOpen);
                } else {
                    $container.html('<p class="description">Unable to calculate score.</p>');
                }
            }).fail(function() {
                $container.html('<p class="description">Unable to calculate score.</p>');
            });
        },

        renderGeoScore: function(data, keepDetailsOpen) {
            this.renderScoreWidget(data, '#ap-geo-score-container', keepDetailsOpen);
        },

        renderScoreWidget: function(data, containerSelector, keepDetailsOpen) {
            var total = data.total;
            var max = data.max;
            var pct = Math.round((total / max) * 100);
            var color = total <= 40 ? 'var(--ap-danger, #d63638)' : (total <= 70 ? 'var(--ap-warning, #dba617)' : 'var(--ap-success, #00a32a)');

            var html = '<div class="ap-score-widget">';
            html += '<div class="ap-score-header">';
            html += '<span class="ap-score-value" style="color:' + color + ';">' + total + '/' + max + '</span>';
            html += '<div class="ap-score-bar"><div class="ap-score-bar-fill" style="width:' + pct + '%;background:' + color + ';"></div></div>';
            html += '</div>';

            var toggleLabel = this.strings && this.strings.viewDetails ? this.strings.viewDetails : 'View details';
            var refreshLabel = this.strings && this.strings.refreshScore ? this.strings.refreshScore : 'Refresh';
            html += '<div class="ap-score-actions">';
            html += '<a href="#" class="ap-score-toggle"><span class="dashicons dashicons-arrow-right-alt2"></span>' + toggleLabel + '</a>';
            html += '<button type="button" class="ap-score-refresh-btn"><span class="dashicons dashicons-update"></span>' + refreshLabel + '</button>';
            html += '</div>';
            html += '<div class="ap-score-details">';

            for (var i = 0; i < data.sections.length; i++) {
                var section = data.sections[i];
                html += '<div class="ap-score-section">';
                html += '<div class="ap-score-section-header">';
                html += '<span>' + self_escHtml(section.label) + '</span>';
                html += '<span class="ap-score-section-pts">' + section.score + '/' + section.max + '</span>';
                html += '</div>';

                for (var j = 0; j < section.items.length; j++) {
                    var item = section.items[j];
                    var icon = item.pass ? '<span class="ap-score-pass">&#10003;</span>' : '<span class="ap-score-fail">&#10007;</span>';
                    var sourceLink = item.url ? ' <a href="' + self_escHtml(item.url) + '" target="_blank" rel="noopener noreferrer" class="ap-score-source" title="Source"><span class="dashicons dashicons-external"></span></a>' : '';
                    html += '<div class="ap-score-item">' + icon + ' ' + self_escHtml(item.label) + sourceLink + '</div>';
                }
                html += '</div>';
            }

            var scoreNote = this.strings && this.strings.scoreNote ? this.strings.scoreNote : 'Not all criteria apply to every page. Aim for the highest relevant score, not necessarily 100.';
            html += '<p class="ap-score-note">' + self_escHtml(scoreNote) + '</p>';
            html += '</div>';
            html += '</div>';
            var $c = $(containerSelector);
            $c.html(html);
            $c.css('min-height', '');
            var self = this;
            if (keepDetailsOpen) {
                $c.find('.ap-score-details').show();
                $c.find('.ap-score-toggle .dashicons').addClass('ap-rotated');
            }
            $c.find('.ap-score-toggle').on('click', function(e) {
                e.preventDefault();
                var $details = $(this).closest('.ap-score-widget').find('.ap-score-details');
                var $icon = $(this).find('.dashicons');
                $details.slideToggle(200);
                $icon.toggleClass('ap-rotated');
            });
            $c.find('.ap-score-refresh-btn').on('click', function() {
                var isGeo = containerSelector.indexOf('geo') !== -1;
                var detailsOpen = $c.find('.ap-score-details').is(':visible');
                if (isGeo) {
                    self.loadGeoScore(detailsOpen);
                } else {
                    self.loadSeoScore(detailsOpen);
                }
            });
        }
    };

    /**
     * SEO Manager Admin Module
     * Handles the SEO content list in admin settings
     */
    window.BlaminhorEssentialsSeoAdmin = {
        nonce: '',
        ajaxurl: '',
        strings: {},
        titlePreview: {},
        currentPage: 1,
        currentPostType: 'page',
        editPostTitle: '',
        editDefaultDesc: '',
        editInitialState: null,
        initialized: false,

        init: function(config) {
            if (this.initialized) return;
            this.initialized = true;

            this.nonce = config.nonce || '';
            this.ajaxurl = config.ajaxurl || ajaxurl;
            this.strings = config.strings || {};
            this.titlePreview = config.titlePreview || {};
            this.bindEvents();
            this.loadContent(1, 'page');
            this.checkIndexable();
            this.toggleFormSubmit();
            this.initTitleTemplates();
        },

        bindEvents: function() {
            var self = this;
            var ns = '.blaminhorSeoAdmin';

            // Filter change
            $('#ap-seo-post-type-filter').off('change' + ns).on('change' + ns, function() {
                self.loadContent(1, $(this).val());
                self.checkIndexable();
            });

            // Refresh
            $('#ap-seo-refresh').off('click' + ns).on('click' + ns, function() {
                self.loadContent(self.currentPage, self.currentPostType);
            });

            // Pagination
            $(document).off('click' + ns, '.ap-seo-page-btn').on('click' + ns, '.ap-seo-page-btn', function() {
                self.loadContent($(this).data('page'), self.currentPostType);
            });

            // Click on row to edit
            $(document).off('click' + ns, '#ap-seo-content-tbody tr').on('click' + ns, '#ap-seo-content-tbody tr', function(e) {
                if ($(e.target).is('a') || $(e.target).closest('a').length) return;
                if ($(e.target).is('button') || $(e.target).closest('button').length) return;
                var postId = $(this).data('post-id');
                if (postId) self.openEditModal(postId);
            });

            // Edit button
            $(document).off('click' + ns, '.ap-seo-edit-btn').on('click' + ns, '.ap-seo-edit-btn', function(e) {
                e.stopPropagation();
                self.openEditModal($(this).data('post-id'));
            });

            // Edit modal inputs
            $('#ap-seo-edit-title, #ap-seo-edit-desc').off('input' + ns).on('input' + ns, function() {
                self.updateEditPreview();
            });

            // Edit modal keywords
            $('#ap-seo-edit-keywords-input').off('keydown' + ns).on('keydown' + ns, function(e) {
                if (e.key === 'Enter' || e.key === ',') {
                    e.preventDefault();
                    self.addEditKeyword($(this).val().trim());
                    $(this).val('');
                }
            });

            // Remove keyword in edit modal
            $(document).off('click' + ns, '#ap-seo-edit-keywords-tags .ap-seo-keyword-remove').on('click' + ns, '#ap-seo-edit-keywords-tags .ap-seo-keyword-remove', function() {
                $(this).closest('.ap-seo-keyword-tag').remove();
                self.updateEditKeywordsHidden();
            });

            // Click on SERP preview to focus input (edit modal)
            $(document).off('click' + ns, '.ap-seo-edit-title-sync-desktop, .ap-seo-edit-title-sync-mobile').on('click' + ns, '.ap-seo-edit-title-sync-desktop, .ap-seo-edit-title-sync-mobile', function() {
                $('#ap-seo-edit-title').focus();
            });
            $(document).off('click' + ns, '.ap-seo-edit-desc-sync-desktop, .ap-seo-edit-desc-sync-mobile').on('click' + ns, '.ap-seo-edit-desc-sync-desktop, .ap-seo-edit-desc-sync-mobile', function() {
                $('#ap-seo-edit-desc').focus();
            });

            // Cancel edit - track mousedown to prevent close on text selection
            var modalMouseDownTarget = null;
            $('#ap-seo-edit-modal .ap-db-modal-overlay').off('mousedown' + ns).on('mousedown' + ns, function(e) {
                modalMouseDownTarget = e.target;
            });
            $('#ap-seo-edit-modal .ap-db-modal-overlay').off('click' + ns).on('click' + ns, function(e) {
                if (modalMouseDownTarget === e.target) {
                    self.closeSeoEditModal();
                }
                modalMouseDownTarget = null;
            });
            $('#ap-seo-edit-cancel').off('click' + ns).on('click' + ns, function() {
                self.closeSeoEditModal();
            });

            // Save edit
            $('#ap-seo-edit-save').off('click' + ns).on('click' + ns, function() {
                self.saveEdit();
            });

            // SEO Import button
            $(document).off('click' + ns, '.ap-import-seo-btn').on('click' + ns, '.ap-import-seo-btn', function() {
                var $btn = $(this);
                var source = $btn.data('source');
                var overwrite = $('.ap-import-overwrite[data-source="' + source + '"]').is(':checked');
                self.startSeoImport(source, overwrite, 0);
            });

            // Toggle submit button on tab change
            $(document).off('click' + ns, '.blaminhor-essentials-tab').on('click' + ns, '.blaminhor-essentials-tab', function() {
                setTimeout(function() { self.toggleFormSubmit(); }, 0);
            });
        },

        toggleFormSubmit: function() {
            var activeTab = $('.blaminhor-essentials-tab.active').data('tab');
            if (activeTab === 'overview' || activeTab === 'import') {
                $('.ap-form-submit').hide();
            } else {
                $('.ap-form-submit').show();
            }
        },

        loadContent: function(page, postType) {
            var self = this;
            this.currentPage = page || 1;
            this.currentPostType = postType || this.currentPostType;

            $('#ap-seo-content-tbody').html('<tr><td colspan="5" style="text-align: center; padding: 20px;">' + this.strings.loading + '</td></tr>');

            $.post(this.ajaxurl, {
                action: 'ap_seo_get_content_list',
                nonce: this.nonce,
                post_type: this.currentPostType,
                page: this.currentPage
            }, function(response) {
                if (response.success) {
                    self.renderTable(response.data);
                }
            });
        },

        renderTable: function(data) {
            var self = this;
            var html = '';

            if (data.items.length === 0) {
                html = '<tr><td colspan="6" style="text-align: center; padding: 20px;">' + this.strings.noContent + '</td></tr>';
            } else {
                data.items.forEach(function(item) {
                    html += '<tr data-post-id="' + item.id + '">';
                    html += '<td><a href="' + item.edit_url + '" target="_blank">' + item.title + '</a></td>';
                    html += '<td class="ap-seo-edit-inline" data-field="seo_title">' + (item.seo_title || '<em style="color:#999;">' + self.strings.notSet + '</em>') + '</td>';

                    var descDisplay = item.seo_desc
                        ? item.seo_desc.substring(0, 60) + '...'
                        : (item.default_desc
                            ? '<span style="color:#999; font-style:italic;">' + item.default_desc.substring(0, 60) + '...</span>'
                            : '<em style="color:#999;">' + self.strings.notSet + '</em>');
                    html += '<td class="ap-seo-edit-inline" data-field="seo_description">' + descDisplay + '</td>';

                    // Keywords column
                    html += '<td>';
                    if (item.seo_keywords && item.seo_keywords.length > 0) {
                        html += '<div class="ap-seo-keywords-list">';
                        item.seo_keywords.forEach(function(keyword) {
                            html += '<span class="ap-seo-keyword-badge">' + keyword + '</span>';
                        });
                        html += '</div>';
                    } else {
                        html += '<em style="color:#999;">' + self.strings.notSet + '</em>';
                    }
                    html += '</td>';

                    html += '<td>' + (item.noindex ? '<span class="ap-seo-status-no">' + self.strings.no + '</span>' : '<span class="ap-seo-status-yes">' + self.strings.yes + '</span>') + '</td>';
                    html += '<td><button type="button" class="button button-small ap-seo-edit-btn" data-post-id="' + item.id + '">' + self.strings.edit + '</button></td>';
                    html += '</tr>';
                });
            }
            $('#ap-seo-content-tbody').html(html);

            // Pagination
            var paginationHtml = '';
            if (data.pages > 1) {
                for (var i = 1; i <= data.pages; i++) {
                    paginationHtml += '<button type="button" class="button' + (i === data.current ? ' button-primary' : '') + ' ap-seo-page-btn" data-page="' + i + '">' + i + '</button> ';
                }
            }
            $('#ap-seo-pagination').html(paginationHtml);
        },

        checkIndexable: function() {
            var $selected = $('#ap-seo-post-type-filter option:selected');
            var isIndexable = $selected.data('indexable') === 1;
            if (isIndexable) {
                $('#ap-seo-noindex-warning').hide();
            } else {
                $('#ap-seo-noindex-warning').show();
            }
        },

        openEditModal: function(postId) {
            var self = this;
            $('#ap-seo-edit-post-id').val(postId);

            $.post(this.ajaxurl, {
                action: 'ap_seo_get_meta',
                nonce: this.nonce,
                post_id: postId
            }, function(response) {
                if (response.success) {
                    self.editPostTitle = response.data.title;
                    self.editDefaultTitle = response.data.default_title || response.data.title;
                    self.editDefaultDesc = response.data.default_desc || '';
                    self.editVariables = response.data.variables || {};
                    $('#ap-seo-edit-title').val(response.data.meta.seo_title);
                    $('#ap-seo-edit-desc').val(response.data.meta.seo_description);
                    var url = response.data.url;
                    var a = document.createElement('a');
                    a.href = url;
                    var host = a.hostname;
                    var path = a.pathname.replace(/^\/|\/$/g, '');
                    var breadcrumb = path ? host + ' \u203a ' + path.split('/').join(' \u203a ') : host;
                    $('.ap-seo-edit-url-sync').text(breadcrumb);
                    $('.ap-seo-edit-url-sync-domain').text(host);
                    $('#ap-seo-edit-noindex').prop('checked', response.data.meta.noindex === '1');
                    $('#ap-seo-edit-nofollow').prop('checked', response.data.meta.nofollow === '1');

                    // Social fields
                    $('#ap-seo-edit-og-title').val(response.data.meta.og_title || '');
                    $('#ap-seo-edit-og-desc').val(response.data.meta.og_description || '');
                    $('#ap-seo-edit-og-image').val(response.data.meta.og_image || '');
                    $('#ap-seo-edit-twitter-title').val(response.data.meta.twitter_title || '');
                    $('#ap-seo-edit-twitter-desc').val(response.data.meta.twitter_description || '');

                    // Advanced fields
                    $('#ap-seo-edit-noai').prop('checked', response.data.meta.noai === '1');
                    $('#ap-seo-edit-canonical').val(response.data.meta.canonical_url || '');

                    // Schema fields
                    $('#ap-seo-edit-schema-type').val(response.data.meta.schema_type || '');
                    $('#ap-seo-edit-schema-desc').val(response.data.meta.schema_description || '');
                    $('#ap-seo-edit-schema-json').val(response.data.meta.schema_custom_json || '');

                    // Load keywords
                    self.loadEditKeywords(response.data.meta.seo_keywords || '');

                    // Date
                    var dateText = response.data.post_date ? response.data.post_date + ' \u2014 ' : '';
                    $('.ap-seo-edit-date').text(dateText);

                    // Thumbnail
                    if (response.data.thumbnail_url) {
                        $('.ap-seo-edit-thumbnail').attr('src', response.data.thumbnail_url).show();
                    } else {
                        $('.ap-seo-edit-thumbnail').hide();
                    }

                    $('#ap-seo-edit-modal').removeClass('ap-hidden').fadeIn(200, function() {
                        self.updateEditPreview();
                        self.editInitialState = {
                            title: $('#ap-seo-edit-title').val(),
                            desc: $('#ap-seo-edit-desc').val(),
                            keywords: $('#ap-seo-edit-keywords').val(),
                            noindex: $('#ap-seo-edit-noindex').is(':checked'),
                            nofollow: $('#ap-seo-edit-nofollow').is(':checked')
                        };
                    });
                }
            });
        },

        loadEditKeywords: function(keywordsStr) {
            var $container = $('#ap-seo-edit-keywords-tags');
            $container.empty();

            if (keywordsStr) {
                var keywords = keywordsStr.split(',');
                keywords.forEach(function(keyword) {
                    keyword = keyword.trim();
                    if (keyword) {
                        $container.append('<span class="ap-seo-keyword-tag">' + keyword + '<button type="button" class="ap-seo-keyword-remove">&times;</button></span>');
                    }
                });
            }

            this.updateEditKeywordsHidden();
        },

        addEditKeyword: function(keyword) {
            if (!keyword) return;

            // Check for duplicates
            var existing = [];
            $('#ap-seo-edit-keywords-tags .ap-seo-keyword-tag').each(function() {
                existing.push($(this).text().replace('×', '').trim().toLowerCase());
            });

            if (existing.indexOf(keyword.toLowerCase()) !== -1) return;

            $('#ap-seo-edit-keywords-tags').append('<span class="ap-seo-keyword-tag">' + keyword + '<button type="button" class="ap-seo-keyword-remove">&times;</button></span>');
            this.updateEditKeywordsHidden();
        },

        updateEditKeywordsHidden: function() {
            var keywords = [];
            $('#ap-seo-edit-keywords-tags .ap-seo-keyword-tag').each(function() {
                keywords.push($(this).text().replace('×', '').trim());
            });
            $('#ap-seo-edit-keywords').val(keywords.join(', '));
        },

        replaceEditVariables: function(text) {
            if (!text || !this.editVariables) return text;
            var result = text;
            $.each(this.editVariables, function(varName, val) {
                result = result.split(varName).join(val);
            });
            return result.replace(/\s*\{[a-z_]+\}/g, '').trim();
        },

        updateEditPreview: function() {
            var title = this.replaceEditVariables($('#ap-seo-edit-title').val() || this.editDefaultTitle || this.editPostTitle);
            var desc = this.replaceEditVariables($('#ap-seo-edit-desc').val() || this.editDefaultDesc);
            // Desktop title: CSS handles single-line truncation
            $('.ap-seo-edit-title-sync-desktop').text(title);
            // Mobile title: JS truncates to 2 lines
            var mobileTitleEl = document.querySelector('.ap-seo-edit-title-sync-mobile');
            if (mobileTitleEl) serpTruncate(mobileTitleEl, title, 2, false);
            // Descriptions: JS truncates on .ap-serp-desc-text spans only (preserve date)
            var desktopDescEl = document.querySelector('.ap-seo-edit-desc-sync-desktop .ap-serp-desc-text');
            if (desktopDescEl) serpTruncate(desktopDescEl, desc, 2, true);
            var mobileDescEl = document.querySelector('.ap-seo-edit-desc-sync-mobile .ap-serp-desc-text');
            if (mobileDescEl) serpTruncate(mobileDescEl, desc, 3, true);
            this.updateEditCharCount('#ap-seo-edit-title', '#ap-seo-edit-title-count', '#ap-seo-edit-title-bar', 60);
            this.updateEditCharCount('#ap-seo-edit-desc', '#ap-seo-edit-desc-count', '#ap-seo-edit-desc-bar', 160);
        },

        updateEditCharCount: function(input, counter, bar, max) {
            var len = $(input).val().length;
            var percent = Math.min((len / max) * 100, 100);
            var color = '#00a32a';
            if (len > max) {
                color = '#d63638';
                percent = 100;
            } else if (len > max * 0.9) {
                color = '#dba617';
            } else if (len > max * 0.7) {
                color = '#2271b1';
            }
            $(counter).text(len + '/' + max);
            $(counter).css('color', len > max ? '#d63638' : (len > max * 0.9 ? '#dba617' : '#646970'));
            $(bar).css({ width: percent + '%', background: color });
        },

        saveEdit: function() {
            var self = this;
            var postId = $('#ap-seo-edit-post-id').val();
            var fields = [
                { field: 'seo_title', value: $('#ap-seo-edit-title').val() },
                { field: 'seo_description', value: $('#ap-seo-edit-desc').val() },
                { field: 'seo_keywords', value: $('#ap-seo-edit-keywords').val() },
                { field: 'noindex', value: $('#ap-seo-edit-noindex').is(':checked') ? '1' : '' },
                { field: 'nofollow', value: $('#ap-seo-edit-nofollow').is(':checked') ? '1' : '' },
                { field: 'og_title', value: $('#ap-seo-edit-og-title').val() },
                { field: 'og_description', value: $('#ap-seo-edit-og-desc').val() },
                { field: 'og_image', value: $('#ap-seo-edit-og-image').val() },
                { field: 'twitter_title', value: $('#ap-seo-edit-twitter-title').val() },
                { field: 'twitter_description', value: $('#ap-seo-edit-twitter-desc').val() },
                { field: 'noai', value: $('#ap-seo-edit-noai').is(':checked') ? '1' : '' },
                { field: 'canonical_url', value: $('#ap-seo-edit-canonical').val() },
                { field: 'schema_type', value: $('#ap-seo-edit-schema-type').val() },
                { field: 'schema_description', value: $('#ap-seo-edit-schema-desc').val() },
                { field: 'schema_custom_json', value: $('#ap-seo-edit-schema-json').val() }
            ];

            var saved = 0;
            fields.forEach(function(f) {
                $.post(self.ajaxurl, {
                    action: 'ap_seo_save_meta',
                    nonce: self.nonce,
                    post_id: postId,
                    field: f.field,
                    value: f.value
                }, function() {
                    saved++;
                    if (saved === fields.length) {
                        $('#ap-seo-edit-modal').fadeOut(200, function() { $(this).addClass('ap-hidden'); });
                        self.loadContent(self.currentPage, self.currentPostType);
                    }
                });
            });
        },

        startSeoImport: function(source, overwrite, offset) {
            var self = this;
            var $card = $('.ap-import-source-card:has([data-source="' + source + '"])');
            var $btn = $card.find('.ap-import-seo-btn');
            var $progress = $card.find('.ap-import-progress[data-source="' + source + '"]');
            var $progressBar = $progress.find('.ap-import-progress-bar');
            var $status = $progress.find('.ap-import-status');
            var $result = $card.find('.ap-import-result[data-source="' + source + '"]');

            // Initialize UI
            if (offset === 0) {
                $btn.prop('disabled', true);
                $progress.show();
                $result.hide().empty();
                $progressBar.css('width', '0%');
                $status.text(self.strings.loading || 'Importing...');
                self.importStats = { imported: 0, skipped: 0 };
            }

            $.post(this.ajaxurl, {
                action: 'ap_seo_import',
                nonce: this.nonce,
                source: source,
                overwrite: overwrite ? 'true' : 'false',
                offset: offset
            }, function(response) {
                if (response.success) {
                    var data = response.data;
                    self.importStats.imported += data.imported;
                    self.importStats.skipped += data.skipped;

                    var percent = Math.round((data.processed / data.total) * 100);
                    $progressBar.css('width', percent + '%');
                    $status.text(data.processed + ' / ' + data.total);

                    if (data.has_more) {
                        self.startSeoImport(source, overwrite, data.next_offset);
                    } else {
                        // Complete
                        $progress.hide();
                        $btn.prop('disabled', false);

                        var resultHtml = '<div class="blaminhor-essentials-notice success">';
                        resultHtml += '<strong>' + self.importStats.imported + '</strong> ' + (self.strings.itemsImported || 'items imported');
                        if (self.importStats.skipped > 0) {
                            resultHtml += ', <strong>' + self.importStats.skipped + '</strong> ' + (self.strings.itemsSkipped || 'skipped (existing data)');
                        }
                        resultHtml += '</div>';
                        $result.html(resultHtml).show();

                        // Refresh content overview table
                        self.loadContent(self.currentPage, self.currentPostType);
                    }
                } else {
                    $progress.hide();
                    $btn.prop('disabled', false);
                    $result.html('<div class="blaminhor-essentials-notice error">' + (response.data.message || 'Import failed') + '</div>').show();
                }
            }).fail(function() {
                $progress.hide();
                $btn.prop('disabled', false);
                $result.html('<div class="blaminhor-essentials-notice error">Import failed</div>').show();
            });
        },

        hasUnsavedChanges: function() {
            if (!this.editInitialState) return false;
            return (
                $('#ap-seo-edit-title').val() !== this.editInitialState.title ||
                $('#ap-seo-edit-desc').val() !== this.editInitialState.desc ||
                $('#ap-seo-edit-keywords').val() !== this.editInitialState.keywords ||
                $('#ap-seo-edit-noindex').is(':checked') !== this.editInitialState.noindex ||
                $('#ap-seo-edit-nofollow').is(':checked') !== this.editInitialState.nofollow
            );
        },

        closeSeoEditModal: function() {
            if (this.hasUnsavedChanges()) {
                if (!confirm(this.strings.unsavedChanges || 'You have unsaved changes. Are you sure you want to close?')) {
                    return;
                }
            }
            this.editInitialState = null;
            $('#ap-seo-edit-modal').fadeOut(200, function() { $(this).addClass('ap-hidden'); });
        },

        importStats: { imported: 0, skipped: 0 },

        // --- Title Templates: accordion, variable insertion, live preview ---

        initTitleTemplates: function() {
            var self = this;
            var ns = '.blaminhorSeoTitles';

            // Accordion toggle.
            $(document).off('click' + ns, '[data-accordion-toggle]').on('click' + ns, '[data-accordion-toggle]', function() {
                $(this).closest('.seo-title-accordion').toggleClass('open');
            });

            // Variable button insertion.
            $(document).off('click' + ns, '.seo-var-btn').on('click' + ns, '.seo-var-btn', function() {
                var $btn = $(this);
                var $container = $btn.closest('.seo-title-variables');
                var $input;

                // Check if parent has a data-target attribute (metabox/modal fields).
                var target = $container.data('target');
                if (target) {
                    $input = $(target);
                } else {
                    $input = $btn.closest('.seo-title-accordion-body, .seo-title-field').find('.seo-title-input');
                }
                if (!$input.length) return;

                var input = $input[0];
                var varText = $btn.data('var');
                var start = input.selectionStart;
                var end = input.selectionEnd;
                var val = $input.val();

                // Add a leading space if cursor is not at the start and previous char is not a space.
                if (start > 0 && val.charAt(start - 1) !== ' ') {
                    varText = ' ' + varText;
                }

                $input.val(val.substring(0, start) + varText + val.substring(end));
                input.focus();
                var newPos = start + varText.length;
                input.setSelectionRange(newPos, newPos);
                $input.trigger('input');
            });

            // Live preview on input.
            $(document).off('input' + ns, '.seo-title-input').on('input' + ns, '.seo-title-input', function() {
                self.updateTitlePreview($(this));
            });

            // Separator change updates all previews.
            $('#title_separator').off('change' + ns).on('change' + ns, function() {
                self.titlePreview.sep = $(this).val();
                $('.seo-title-input').each(function() {
                    self.updateTitlePreview($(this));
                });
            });

            // Initial preview for all fields.
            $('.seo-title-input').each(function() {
                self.updateTitlePreview($(this));
            });
        },

        updateTitlePreview: function($input) {
            var p = this.titlePreview;
            var template = $input.val() || $input.attr('placeholder') || '';
            var $container = $input.closest('.seo-title-accordion-body, .seo-title-field');
            var context = $input.closest('[data-preview-context]').data('preview-context') || '';

            // Build replacement map based on context.
            var replacements = {
                '{site_name}':    p.site_name || '',
                '{tagline}':      p.tagline || '',
                '{sep}':          p.sep || '-',
                '{year}':         p.year || '',
                '{month}':        p.month || '',
                '{day}':          p.day || '',
                '{page_number}':  p.page_number || '2'
            };

            if (context === 'post_type') {
                replacements['{title}']     = p.sample_title || '';
                replacements['{author}']    = p.sample_author || '';
                replacements['{category}']  = p.sample_term || '';
                replacements['{post_type}'] = 'Post';
            } else if (context === 'taxonomy') {
                replacements['{term_name}'] = p.sample_term || '';
                replacements['{title}']     = p.sample_term || '';
            } else if (context === 'home') {
                // Only site_name, tagline, sep — already set.
            } else if (context === 'search') {
                replacements['{search_query}'] = p.sample_search || 'WordPress';
            } else if (context === '404') {
                replacements['{text_404}'] = p.text_404 || 'Page not found';
            } else if (context === 'author') {
                replacements['{author}'] = p.sample_author || 'John Doe';
            } else if (context === 'date') {
                replacements['{date}'] = p.date || '';
            }

            var preview = template;
            $.each(replacements, function(varName, val) {
                preview = preview.split(varName).join(val);
            });

            // Update inline preview span.
            $container.find('.seo-title-preview span').text(preview);

            // Update accordion header preview.
            var $accordion = $input.closest('.seo-title-accordion');
            if ($accordion.length) {
                var headerPreview = $input.val() || $input.attr('placeholder') || '';
                $accordion.find('.seo-title-accordion-preview').text(headerPreview);
            }
        }
    };

    /**
     * SMTP Module
     * Handles SMTP relay configuration and testing
     */
    window.BlaminhorEssentialsSmtp = {
        nonce: '',
        ajaxurl: '',
        providers: {},
        tutorials: {},
        strings: {},
        initialized: false,

        init: function(config) {
            if (this.initialized) return;
            this.initialized = true;

            this.nonce = config.nonce || '';
            this.ajaxurl = config.ajaxurl || ajaxurl;
            this.providers = config.providers || {};
            this.tutorials = JSON.parse($('#smtp-tutorials-data').text() || '{}');
            this.strings = config.strings || {};
            this.bindEvents();
        },

        bindEvents: function() {
            var self = this;
            var ns = '.blaminhorSmtp';

            // Handle Provider Selection (dropdown)
            $(document).off('change' + ns, '#smtp-provider-select').on('change' + ns, '#smtp-provider-select', function() {
                var providerKey = $(this).val();
                if (providerKey) {
                    self.selectProvider(providerKey);
                } else {
                    self.clearProviderSelection();
                }
            });

            // Handle Edit Relay
            $('.edit-relay').off('click' + ns).on('click' + ns, function() {
                self.editRelay($(this));
            });

            // Handle Cancel Edit
            $('#btn-cancel-edit').off('click' + ns).on('click' + ns, function() {
                self.cancelEdit();
            });

            // Handle Test Relay
            $('.test-relay').off('click' + ns).on('click' + ns, function() {
                self.testRelay($(this));
            });

            // Handle Test Email Form
            $('#smtp-test-form').off('submit' + ns).on('submit' + ns, function(e) {
                e.preventDefault();
                self.sendTestEmail();
            });

            // Clear logs button
            $('#clear-logs-btn').off('click' + ns).on('click' + ns, function() {
                self.clearLogs();
            });

            // Sortable table (use document delegation to avoid duplicates)
            $(document).off('click' + ns, '.ap-sortable-table th.ap-sortable').on('click' + ns, '.ap-sortable-table th.ap-sortable', function() {
                self.sortTable($(this));
            });

            // Link to Configuration tab from logs notice
            $(document).off('click' + ns, '.smtp-go-to-config').on('click' + ns, '.smtp-go-to-config', function(e) {
                e.preventDefault();
                // Switch to config tab
                $('.blaminhor-essentials-tab[data-tab="config"]').trigger('click');
                // Scroll to top
                $('html, body').animate({ scrollTop: 0 }, 300);
            });

            // Import SMTP configuration
            $(document).off('click' + ns, '.ap-import-smtp-btn').on('click' + ns, '.ap-import-smtp-btn', function() {
                var $btn = $(this);
                var source = $btn.data('source');
                self.importSmtpConfig(source, $btn);
            });
        },

        showTutorial: function(providerKey) {
            var tutorial = this.tutorials[providerKey];
            var $tutorialDiv = $('#smtp-provider-tutorial');

            if (tutorial) {
                $('#smtp-tutorial-title').html(tutorial.title);
                var stepsHtml = '';
                if (tutorial.steps) {
                    tutorial.steps.forEach(function(step) {
                        stepsHtml += '<li>' + step + '</li>';
                    });
                }
                $('#smtp-tutorial-steps').html(stepsHtml);

                if (tutorial.note) {
                    $('#smtp-tutorial-note').html('<strong>' + this.strings.note + '</strong> ' + tutorial.note).show();
                } else {
                    $('#smtp-tutorial-note').hide();
                }

                $tutorialDiv.slideDown(200);
            } else {
                $tutorialDiv.slideUp(200);
            }
        },

        selectProvider: function(providerKey) {
            var provider = this.providers[providerKey];
            var tutorial = this.tutorials[providerKey];

            // Update dropdown selection
            $('#smtp-provider-select').val(providerKey);

            // Update hidden input
            $('#smtp-provider').val(providerKey);

            // Show tutorial
            this.showTutorial(providerKey);

            // Auto-fill from tutorial if available
            if (tutorial && tutorial.host) {
                $('#relay_name').val(provider ? provider.name : '');
                $('#smtp-host').val(tutorial.host);
                $('#smtp-port').val(tutorial.port);
                $('#smtp-encryption').val(tutorial.encryption);
            } else if (provider && provider.host) {
                $('#relay_name').val(provider.name);
                $('#smtp-host').val(provider.host);
                $('#smtp-port').val(provider.port);
                $('#smtp-encryption').val(provider.encryption);
            } else if (providerKey === 'other') {
                $('#relay_name').val('');
            }
        },

        clearProviderSelection: function() {
            $('#smtp-provider').val('other');
            $('#smtp-provider-tutorial').slideUp(200);
        },

        editRelay: function($btn) {
            var $item = $btn.closest('.smtp-relay-item');
            var relayData = JSON.parse($item.find('.relay-data').text());
            var index = $item.data('relay-id');

            // Switch form mode
            $('#action_add_relay').prop('disabled', true);
            $('#action_update_relay').prop('disabled', false).val('1');
            $('#relay_index').val(index);
            $('#form-title').text(this.strings.editRelay + ': ' + relayData.name);
            $('#btn-save-relay').text(this.strings.updateRelay);
            $('#btn-cancel-edit').show();

            // Populate fields
            $('#relay_name').val(relayData.name);
            $('#smtp-host').val(relayData.host);
            $('#smtp-port').val(relayData.port);
            $('#relay_username').val(relayData.username);
            $('#relay_password').val('').attr('placeholder', relayData.password ? this.strings.unchanged : '');
            $('#relay_from_email').val(relayData.from_email);
            $('#relay_from_name').val(relayData.from_name);
            $('#smtp-encryption').val(relayData.encryption);

            // Update provider dropdown and show tutorial
            $('#smtp-provider-select').val(relayData.provider);
            $('#smtp-provider').val(relayData.provider);
            this.showTutorial(relayData.provider);

            // Scroll to form
            $('html, body').animate({
                scrollTop: $('#add-relay-form').offset().top - 50
            }, 500);
        },

        cancelEdit: function() {
            // Reset form
            $('#add-relay-form')[0].reset();
            $('#relay_password').attr('placeholder', '');

            // Switch back to Add mode
            $('#action_add_relay').prop('disabled', false);
            $('#action_update_relay').prop('disabled', true);
            $('#relay_index').val('');
            $('#form-title').text(this.strings.addNewRelay);
            $('#btn-save-relay').text(this.strings.addRelay);
            $('#btn-cancel-edit').hide();

            // Reset UI
            $('#smtp-provider-select').val('');
            $('#smtp-provider').val('other');
            $('#smtp-provider-tutorial').slideUp(200);
        },

        testRelay: function($btn) {
            var self = this;
            var $icon = $btn.find('.dashicons');
            var originalIcon = $icon.attr('class');
            var index = $btn.closest('.smtp-relay-item').data('relay-id');

            $btn.prop('disabled', true);
            $icon.attr('class', 'dashicons dashicons-update spin');

            $.ajax({
                url: this.ajaxurl,
                type: 'POST',
                data: {
                    action: 'blaminhor_essentials_smtp_test_relay',
                    nonce: this.nonce,
                    relay_index: index
                },
                success: function(response) {
                    $btn.prop('disabled', false);
                    $icon.attr('class', originalIcon);

                    if (response.success) {
                        alert(self.strings.success);
                    } else {
                        alert(self.strings.failed + (response.data.message || 'Unknown error'));
                    }
                },
                error: function() {
                    $btn.prop('disabled', false);
                    $icon.attr('class', originalIcon);
                    alert(self.strings.ajaxError);
                }
            });
        },

        sendTestEmail: function() {
            // Prevent double submission
            if (this.isSendingTest) {
                return;
            }
            this.isSendingTest = true;

            var self = this;
            var $form = $('#smtp-test-form');
            var $btn = $form.find('button[type="submit"]');
            var $result = $('#smtp-test-result');

            $btn.prop('disabled', true).text(this.strings.sending);
            $result.html('');

            $.ajax({
                url: this.ajaxurl,
                type: 'POST',
                data: {
                    action: 'blaminhor_essentials_smtp_test',
                    nonce: this.nonce,
                    email: $('#test-email').val()
                },
                success: function(response) {
                    self.isSendingTest = false;
                    $btn.prop('disabled', false).text(self.strings.sendTestEmail);

                    if (response.success) {
                        $result.html('<div class="blaminhor-essentials-notice success">' + response.data.message + '</div>');
                        // If logging is enabled, switch to logs tab after delay
                        if (response.data.logging_enabled) {
                            setTimeout(function() {
                                var $logTab = $('[data-tab="logs"]');
                                if ($logTab.length) {
                                    $logTab.trigger('click');
                                }
                            }, 1500);
                        }
                    } else {
                        $result.html('<div class="blaminhor-essentials-notice error">' + response.data.message + '</div>');
                    }
                },
                error: function() {
                    self.isSendingTest = false;
                    $btn.prop('disabled', false).text(self.strings.sendTestEmail);
                    $result.html('<div class="blaminhor-essentials-notice error">' + self.strings.ajaxError + '</div>');
                }
            });
        },

        clearLogs: function() {
            var self = this;
            if (!confirm(this.strings.confirmClearLogs)) {
                return;
            }

            $.ajax({
                url: this.ajaxurl,
                type: 'POST',
                data: {
                    action: 'blaminhor_essentials_smtp_clear_logs',
                    nonce: this.nonce
                },
                success: function(response) {
                    if (response.success) {
                        location.reload();
                    } else {
                        alert(response.data.message);
                    }
                }
            });
        },

        sortTable: function($th) {
            var $table = $th.closest('table');
            var $tbody = $table.find('tbody');
            var colIndex = $th.index();
            var sortType = $th.data('sort') || 'string';
            var isAsc = !$th.hasClass('asc');

            // Remove sort classes from all headers
            $table.find('th.ap-sortable').removeClass('asc desc');

            // Add sort class to current header
            $th.addClass(isAsc ? 'asc' : 'desc');

            // Get rows and sort
            var rows = $tbody.find('tr').toArray();
            var dataAttrs = ['date', 'to', 'subject', 'relay', 'status'];

            rows.sort(function(a, b) {
                var aVal, bVal;
                var dataAttr = dataAttrs[colIndex];

                if (sortType === 'number') {
                    aVal = parseFloat($(a).data(dataAttr)) || 0;
                    bVal = parseFloat($(b).data(dataAttr)) || 0;
                } else {
                    if ($(a).data(dataAttr) !== undefined) {
                        aVal = String($(a).data(dataAttr)).toLowerCase();
                        bVal = String($(b).data(dataAttr)).toLowerCase();
                    } else {
                        aVal = $(a).find('td').eq(colIndex).text().toLowerCase();
                        bVal = $(b).find('td').eq(colIndex).text().toLowerCase();
                    }
                }

                if (aVal < bVal) return isAsc ? -1 : 1;
                if (aVal > bVal) return isAsc ? 1 : -1;
                return 0;
            });

            // Reorder rows
            $.each(rows, function(i, row) {
                $tbody.append(row);
            });
        },

        importSmtpConfig: function(source, $btn) {
            var self = this;
            var $card = $btn.closest('.ap-import-source-card');
            var $result = $card.find('.ap-import-smtp-result[data-source="' + source + '"]');

            $btn.prop('disabled', true);
            $result.hide().empty();

            $.ajax({
                url: this.ajaxurl,
                type: 'POST',
                data: {
                    action: 'blaminhor_essentials_smtp_import',
                    nonce: this.nonce,
                    source: source
                },
                success: function(response) {
                    $btn.prop('disabled', false);
                    if (response.success) {
                        $result.html('<div class="blaminhor-essentials-notice success">' + response.data.message + '</div>').show();
                        // Reload page after 2 seconds to show the new relay
                        setTimeout(function() {
                            location.reload();
                        }, 2000);
                    } else {
                        $result.html('<div class="blaminhor-essentials-notice error">' + (response.data.message || 'Import failed') + '</div>').show();
                    }
                },
                error: function() {
                    $btn.prop('disabled', false);
                    $result.html('<div class="blaminhor-essentials-notice error">' + self.strings.ajaxError + '</div>').show();
                }
            });
        }
    };

    /**
     * Post Types Order Module
     */
    window.BlaminhorEssentialsPostTypesOrder = {
        nonce: '',
        ajaxurl: '',
        strings: {},
        initialized: false,
        currentType: '',
        isTaxonomy: false,

        init: function(config) {
            if (this.initialized) return;
            this.initialized = true;

            this.nonce = config.nonce || '';
            this.ajaxurl = config.ajaxurl || ajaxurl;
            this.strings = config.strings || {};
            this.bindEvents();
        },

        bindEvents: function() {
            var self = this;
            var ns = '.blaminhorPTO';

            $('#ap-pto-type-select').off('change' + ns).on('change' + ns, function() {
                var type = $(this).val();
                var $selected = $(this).find(':selected');
                var isTax = $selected.data('taxonomy') === true || $selected.data('taxonomy') === 'true';

                if (type) {
                    self.loadItems(type, isTax);
                } else {
                    self.clearList();
                }
            });

            $('#ap-pto-save-order').off('click' + ns).on('click' + ns, function() {
                self.saveOrder();
            });

            $('#ap-pto-reset-order').off('click' + ns).on('click' + ns, function() {
                if (confirm(self.strings.confirmReset)) {
                    self.resetOrder();
                }
            });
        },

        loadItems: function(type, isTaxonomy) {
            var self = this;
            this.currentType = type;
            this.isTaxonomy = isTaxonomy;

            this.clearList();
            $('#ap-pto-loading').show();
            $('#ap-pto-save-order, #ap-pto-reset-order').hide();

            $.post(this.ajaxurl, {
                action: 'ap_pto_get_items',
                nonce: this.nonce,
                type: type,
                is_taxonomy: isTaxonomy ? 'true' : 'false'
            }, function(response) {
                $('#ap-pto-loading').hide();

                if (!response.success) {
                    return;
                }

                var data = response.data;

                if (data.limited) {
                    $('#ap-pto-limit-notice').show();
                } else {
                    $('#ap-pto-limit-notice').hide();
                }

                if (!data.items || data.items.length === 0) {
                    $('#ap-pto-empty').show();
                    return;
                }

                self.renderItems(data.items, isTaxonomy);
                self.initSortable();
                $('#ap-pto-save-order, #ap-pto-reset-order').show();
            }).fail(function() {
                $('#ap-pto-loading').hide();
            });
        },

        renderItems: function(items, isTaxonomy) {
            var $list = $('#ap-pto-sortable-list');
            $list.empty();

            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                var meta = '';
                var statusBadge = '';

                if (isTaxonomy) {
                    meta = '<span class="ap-pto-item-meta">' + item.count + ' ' + this.strings.items + '</span>';
                } else {
                    var statusClass = 'ap-pto-status-' + item.status;
                    var statusLabel = item.status;

                    if (item.status === 'publish') statusLabel = this.strings.publish;
                    else if (item.status === 'draft') statusLabel = this.strings.draft;
                    else if (item.status === 'pending') statusLabel = this.strings.pending;
                    else if (item.status === 'private') statusLabel = this.strings['private'];

                    statusBadge = '<span class="ap-pto-status-badge ' + statusClass + '">' + statusLabel + '</span>';
                    meta = '<span class="ap-pto-item-meta">' + item.date + '</span>';
                }

                var li = '<li data-id="' + item.id + '">' +
                    '<span class="ap-pto-handle dashicons dashicons-menu"></span>' +
                    '<span class="ap-pto-order-number">' + (i + 1) + '</span>' +
                    '<span class="ap-pto-item-title">' + $('<span>').text(item.title).html() + '</span>' +
                    statusBadge +
                    meta +
                    '</li>';

                $list.append(li);
            }

            $list.show();
        },

        initSortable: function() {
            var self = this;

            $('#ap-pto-sortable-list').sortable({
                handle: '.ap-pto-handle',
                placeholder: 'ap-pto-placeholder',
                axis: 'y',
                cursor: 'grabbing',
                tolerance: 'pointer',
                update: function() {
                    self.updateOrderNumbers();
                }
            });
        },

        updateOrderNumbers: function() {
            $('#ap-pto-sortable-list li').each(function(index) {
                $(this).find('.ap-pto-order-number').text(index + 1);
            });
        },

        saveOrder: function() {
            var self = this;
            var $btn = $('#ap-pto-save-order');
            var order = [];

            $('#ap-pto-sortable-list li').each(function() {
                order.push($(this).data('id'));
            });

            $btn.prop('disabled', true).find('.dashicons').removeClass('dashicons-saved').addClass('dashicons-update ap-spin');

            $.post(this.ajaxurl, {
                action: 'ap_pto_save_order',
                nonce: this.nonce,
                type: this.currentType,
                is_taxonomy: this.isTaxonomy ? 'true' : 'false',
                order: order
            }, function(response) {
                $btn.prop('disabled', false).find('.dashicons').removeClass('dashicons-update ap-spin').addClass('dashicons-saved');

                if (response.success) {
                    self.showNotice(self.strings.saved, 'success');
                } else {
                    self.showNotice(self.strings.ajaxError, 'error');
                }
            }).fail(function() {
                $btn.prop('disabled', false).find('.dashicons').removeClass('dashicons-update ap-spin').addClass('dashicons-saved');
                self.showNotice(self.strings.ajaxError, 'error');
            });
        },

        resetOrder: function() {
            var self = this;
            var $btn = $('#ap-pto-reset-order');

            $btn.prop('disabled', true).find('.dashicons').addClass('ap-spin');

            $.post(this.ajaxurl, {
                action: 'ap_pto_reset_order',
                nonce: this.nonce,
                type: this.currentType,
                is_taxonomy: this.isTaxonomy ? 'true' : 'false'
            }, function(response) {
                $btn.prop('disabled', false).find('.dashicons').removeClass('ap-spin');

                if (response.success) {
                    self.showNotice(self.strings.resetDone, 'success');
                    // Reload items to show default order.
                    self.loadItems(self.currentType, self.isTaxonomy);
                } else {
                    self.showNotice(self.strings.ajaxError, 'error');
                }
            }).fail(function() {
                $btn.prop('disabled', false).find('.dashicons').removeClass('ap-spin');
                self.showNotice(self.strings.ajaxError, 'error');
            });
        },

        clearList: function() {
            $('#ap-pto-sortable-list').empty().hide();
            $('#ap-pto-limit-notice').hide();
            $('#ap-pto-empty').hide();
            $('#ap-pto-save-order, #ap-pto-reset-order').hide();
        },

        showNotice: function(message, type) {
            if (!window.BlaminhorEssentials) return;
            var $n = BlaminhorEssentials.showNotice(message, type === 'success' ? 'success' : 'error', {
                container: '#ap-pto-sortable-list',
                position: 'before',
                style: 'plugin',
                timeout: 3000,
                icon: true,
                escape: false
            });
            if ($n && $n.length) $n.css('margin-bottom', '15px');
        }
    };

    /**
     * Post Types Order — Admin List Table Drag & Drop
     */
    window.BlaminhorEssentialsPTOListSort = {
        nonce: '',
        ajaxurl: '',
        type: '',
        isTaxonomy: false,
        strings: {},
        initialized: false,

        init: function(config) {
            if (this.initialized) return;
            this.initialized = true;

            this.nonce = config.nonce || '';
            this.ajaxurl = config.ajaxurl || ajaxurl;
            this.type = config.type || '';
            this.isTaxonomy = config.isTaxonomy || false;
            this.strings = config.strings || {};
            this.setup();
        },

        setup: function() {
            var self = this;
            var $list = $('#the-list');

            if (!$list.length) return;

            // Add drag handle inline next to the title link.
            $list.find('tr').each(function() {
                if (!$(this).find('.ap-pto-list-handle').length) {
                    $(this).find('.row-title').first().before(
                        '<span class="ap-pto-list-handle dashicons dashicons-menu"></span>'
                    );
                }
            });

            $list.sortable({
                items: 'tr',
                handle: '.ap-pto-list-handle',
                axis: 'y',
                cursor: 'grabbing',
                placeholder: 'ap-pto-list-placeholder',
                helper: function(e, tr) {
                    var $originals = tr.children();
                    var $helper = tr.clone();
                    $helper.children().each(function(index) {
                        $(this).width($originals.eq(index).outerWidth());
                    });
                    return $helper;
                },
                update: function() {
                    self.saveOrder();
                }
            });
        },

        saveOrder: function() {
            var self = this;
            var order = [];
            var prefix = this.isTaxonomy ? 'tag-' : 'post-';

            $('#the-list tr').each(function() {
                var id = $(this).attr('id');
                if (id) {
                    var itemId = id.replace(prefix, '');
                    if (itemId && !isNaN(itemId)) {
                        order.push(parseInt(itemId, 10));
                    }
                }
            });

            if (!order.length) return;

            $.post(this.ajaxurl, {
                action: 'ap_pto_save_order',
                nonce: this.nonce,
                type: this.type,
                is_taxonomy: this.isTaxonomy ? 'true' : 'false',
                order: order
            }).done(function(response) {
                if (response.success) {
                    var $notice = $('<div class="notice notice-success is-dismissible" style="margin:5px 0 15px;"><p>' + (self.strings.saved || 'Order saved.') + '</p></div>');
                    $('.wp-header-end').after($notice);
                    setTimeout(function() {
                        $notice.fadeOut(300, function() { $(this).remove(); });
                    }, 2000);
                }
            });
        }
    };

    /**
     * User Role Editor Module
     */
    window.BlaminhorEssentialsUserRoleEditor = {
        nonce: '',
        ajaxurl: '',
        strings: {},
        currentRole: null,
        initialized: false,

        init: function(config) {
            if (this.initialized) return;
            this.initialized = true;

            this.nonce = config.nonce || '';
            this.ajaxurl = config.ajaxurl || ajaxurl;
            this.strings = config.strings || {};
            this.capLabels = config.capLabels || {};
            this.labelDisplayMode = config.labelDisplayMode || 'both';
            this.bindEvents();
            this.loadUsers();
        },

        bindEvents: function() {
            var self = this;
            var ns = '.blaminhorURE';

            // Role selection
            $(document).off('click' + ns, '.ap-ure-role-item').on('click' + ns, '.ap-ure-role-item', function() {
                self.selectRole($(this).data('role'));
            });

            // Role search
            $('#ap-ure-role-search').off('input' + ns).on('input' + ns, function() {
                var search = $(this).val().toLowerCase();
                $('.ap-ure-role-item').each(function() {
                    var name = $(this).find('.ap-ure-role-name').text().toLowerCase();
                    $(this).toggle(name.indexOf(search) !== -1);
                });
            });

            // Add role button
            $('#ap-ure-add-role').off('click' + ns).on('click' + ns, function() {
                self.openRoleModal('create');
            });

            // Clone role button
            $('#ap-ure-clone-role').off('click' + ns).on('click' + ns, function() {
                if (self.currentRole) {
                    self.openRoleModal('clone', self.currentRole);
                }
            });

            // Delete role button
            $('#ap-ure-delete-role').off('click' + ns).on('click' + ns, function() {
                if (self.currentRole) {
                    self.deleteRole(self.currentRole);
                }
            });

            // Rename role button
            $('#ap-ure-rename-role').off('click' + ns).on('click' + ns, function() {
                if (self.currentRole) {
                    self.openRenameModal(self.currentRole);
                }
            });

            // Save capabilities button
            $('#ap-ure-save-caps').off('click' + ns).on('click' + ns, function() {
                self.saveCapabilities();
            });

            // Select/Deselect all
            $('.ap-ure-select-all').off('click' + ns).on('click' + ns, function() {
                $('#ap-ure-caps-container input[type="checkbox"]').prop('checked', true);
                self.updateGroupCounts();
            });

            $('.ap-ure-deselect-all').off('click' + ns).on('click' + ns, function() {
                $('#ap-ure-caps-container input[type="checkbox"]').prop('checked', false);
                self.updateGroupCounts();
            });

            // Capability search (searches both technical and friendly labels)
            $('#ap-ure-cap-search').off('input' + ns).on('input' + ns, function() {
                var search = $(this).val().toLowerCase().trim();

                if (!search) {
                    // Show all capabilities and groups
                    $('.ap-ure-cap-item').show();
                    $('.ap-ure-cap-group').show();
                    self.updateGroupCounts();
                    return;
                }

                // Filter capabilities
                $('.ap-ure-cap-item').each(function() {
                    var $item = $(this);
                    var technical = $item.find('input').attr('name').replace('caps[', '').replace(']', '').toLowerCase();
                    var friendly = $item.find('.ap-ure-cap-friendly').text().toLowerCase();
                    var techLabel = $item.find('.ap-ure-cap-technical').text().toLowerCase();

                    var matches = technical.indexOf(search) !== -1 ||
                                  friendly.indexOf(search) !== -1 ||
                                  techLabel.indexOf(search) !== -1;

                    $item.toggle(matches);
                });

                // Hide groups with no visible capabilities
                $('.ap-ure-cap-group').each(function() {
                    var $group = $(this);
                    var hasVisible = $group.find('.ap-ure-cap-item:visible').length > 0;
                    $group.toggle(hasVisible);

                    // Expand groups with matches
                    if (hasVisible) {
                        $group.removeClass('collapsed');
                        $group.find('.ap-ure-group-toggle')
                            .removeClass('dashicons-arrow-right-alt2')
                            .addClass('dashicons-arrow-down-alt2');
                    }
                });

                self.updateGroupCounts();
            });

            // Group toggle
            $(document).off('click' + ns, '.ap-ure-cap-group-header').on('click' + ns, '.ap-ure-cap-group-header', function() {
                var $group = $(this).closest('.ap-ure-cap-group');
                $group.toggleClass('collapsed');
                $(this).find('.ap-ure-group-toggle')
                    .toggleClass('dashicons-arrow-down-alt2 dashicons-arrow-right-alt2');
            });

            // Capability checkbox change
            $(document).off('change' + ns, '.ap-ure-cap-item input').on('change' + ns, '.ap-ure-cap-item input', function() {
                self.updateGroupCounts();
            });

            // Modal close
            $('.ap-modal-close, .ap-db-modal-overlay').off('click' + ns).on('click' + ns, function() {
                $(this).closest('.ap-db-modal').fadeOut(200, function() { $(this).addClass('ap-hidden'); });
            });

            // Role modal submit
            $('#ap-ure-modal-submit').off('click' + ns).on('click' + ns, function() {
                self.submitRoleModal();
            });

            // Rename modal submit
            $('#ap-ure-rename-submit').off('click' + ns).on('click' + ns, function() {
                self.submitRenameModal();
            });

            // Auto-generate slug from name
            $('#ap-ure-modal-name').off('input' + ns).on('input' + ns, function() {
                var slug = $(this).val().toLowerCase()
                    .replace(/[^a-z0-9]+/g, '_')
                    .replace(/^_+|_+$/g, '');
                $('#ap-ure-modal-slug').val(slug);
            });

            // Users tab
            $('#ap-ure-user-search').off('input' + ns).on('input' + ns, $.debounce(300, function() {
                self.loadUsers(1, $(this).val());
            }));

            $('#ap-ure-refresh-users').off('click' + ns).on('click' + ns, function() {
                self.loadUsers(1, $('#ap-ure-user-search').val());
            });

            // Edit user caps
            $(document).off('click' + ns, '.ap-ure-edit-user-caps').on('click' + ns, '.ap-ure-edit-user-caps', function() {
                self.openUserCapsModal($(this).data('user-id'));
            });

            // Save user caps
            $('#ap-ure-user-caps-save').off('click' + ns).on('click' + ns, function() {
                self.saveUserCaps();
            });

            // Users pagination
            $(document).off('click' + ns, '#ap-ure-users-pagination a').on('click' + ns, '#ap-ure-users-pagination a', function(e) {
                e.preventDefault();
                var page = $(this).data('page');
                self.loadUsers(page, $('#ap-ure-user-search').val());
            });

            // Export button
            $('#ap-ure-export-btn').off('click' + ns).on('click' + ns, function() {
                self.exportRoles();
            });

            // Import button
            $('#ap-ure-import-btn').off('click' + ns).on('click' + ns, function() {
                self.importRoles();
            });

            // Reset button
            $('#ap-ure-reset-btn').off('click' + ns).on('click' + ns, function() {
                self.resetToDefaults();
            });

            // Add custom capability
            $('#ap-ure-add-custom-cap').off('click' + ns).on('click' + ns, function() {
                self.addCustomCapability();
            });

            // Remove custom capability
            $(document).off('click' + ns, '.ap-ure-remove-custom-cap').on('click' + ns, '.ap-ure-remove-custom-cap', function() {
                self.removeCustomCapability($(this).data('cap'));
            });
        },

        selectRole: function(slug) {
            var self = this;
            this.currentRole = slug;

            // Update UI
            $('.ap-ure-role-item').removeClass('active');
            $('.ap-ure-role-item[data-role="' + slug + '"]').addClass('active');
            $('#ap-ure-clone-role').prop('disabled', false);
            $('#ap-ure-no-selection').addClass('ap-hidden');
            $('#ap-ure-role-editor').removeClass('ap-hidden');

            // Load role data
            $.post(this.ajaxurl, {
                action: 'ap_ure_get_role',
                nonce: this.nonce,
                role: slug
            }, function(response) {
                if (response.success) {
                    var data = response.data;
                    $('#ap-ure-current-role-name').text(data.name + ' (' + slug + ')');

                    // Show/hide delete button based on protection
                    if (data.is_protected) {
                        $('#ap-ure-delete-role').hide();
                        $('#ap-ure-rename-role').hide();
                    } else {
                        $('#ap-ure-delete-role').show();
                        $('#ap-ure-rename-role').show();
                    }

                    // Update checkboxes
                    $('#ap-ure-caps-container input[type="checkbox"]').each(function() {
                        var cap = $(this).attr('name').replace('caps[', '').replace(']', '');
                        $(this).prop('checked', data.capabilities[cap] === true);
                    });

                    self.updateGroupCounts();
                }
            });
        },

        updateGroupCounts: function() {
            $('.ap-ure-cap-group').each(function() {
                var $group = $(this);
                var total = $group.find('input[type="checkbox"]').length;
                var checked = $group.find('input[type="checkbox"]:checked').length;
                $group.find('.ap-ure-group-count').text(checked + '/' + total);
            });
        },

        openRoleModal: function(action, sourceRole) {
            $('#ap-ure-modal-action').val(action);
            $('#ap-ure-modal-source').val(sourceRole || '');
            $('#ap-ure-modal-name').val('');
            $('#ap-ure-modal-slug').val('');

            if (action === 'clone') {
                $('#ap-ure-modal-title').text(this.strings.cloneRole || 'Clone Role');
                $('#ap-ure-modal-submit').text(this.strings.clone || 'Clone');
            } else {
                $('#ap-ure-modal-title').text(this.strings.createRole || 'Create Role');
                $('#ap-ure-modal-submit').text(this.strings.create || 'Create');
            }

            $('#ap-ure-role-modal').removeClass('ap-hidden').fadeIn(200);
        },

        submitRoleModal: function() {
            var self = this;
            var action = $('#ap-ure-modal-action').val();
            var name = $('#ap-ure-modal-name').val().trim();
            var slug = $('#ap-ure-modal-slug').val().trim();

            if (!name) {
                alert(this.strings.enterRoleName || 'Please enter a role name.');
                return;
            }

            if (!slug) {
                alert(this.strings.enterRoleSlug || 'Please enter a role slug.');
                return;
            }

            var data = {
                nonce: this.nonce,
                name: name,
                slug: slug
            };

            if (action === 'clone') {
                data.action = 'ap_ure_clone_role';
                data.source = $('#ap-ure-modal-source').val();
            } else {
                data.action = 'ap_ure_create_role';
            }

            $.post(this.ajaxurl, data, function(response) {
                if (response.success) {
                    $('#ap-ure-role-modal').fadeOut(200, function() { $(this).addClass('ap-hidden'); });
                    location.reload();
                } else {
                    alert(response.data || self.strings.error);
                }
            });
        },

        deleteRole: function(slug) {
            var self = this;

            if (!confirm(this.strings.confirmDelete || 'Are you sure you want to delete this role?')) {
                return;
            }

            $.post(this.ajaxurl, {
                action: 'ap_ure_delete_role',
                nonce: this.nonce,
                role: slug
            }, function(response) {
                if (response.success) {
                    location.reload();
                } else {
                    alert(response.data || self.strings.error);
                }
            });
        },

        openRenameModal: function(slug) {
            var currentName = $('.ap-ure-role-item[data-role="' + slug + '"] .ap-ure-role-name').text().trim();
            // Remove lock icon text if present
            currentName = currentName.replace(/^\s*/, '');

            $('#ap-ure-rename-role-slug').val(slug);
            $('#ap-ure-rename-name').val(currentName);
            $('#ap-ure-rename-modal').removeClass('ap-hidden').fadeIn(200);
        },

        submitRenameModal: function() {
            var self = this;
            var slug = $('#ap-ure-rename-role-slug').val();
            var name = $('#ap-ure-rename-name').val().trim();

            if (!name) {
                alert(this.strings.enterRoleName || 'Please enter a role name.');
                return;
            }

            $.post(this.ajaxurl, {
                action: 'ap_ure_rename_role',
                nonce: this.nonce,
                role: slug,
                name: name
            }, function(response) {
                if (response.success) {
                    $('#ap-ure-rename-modal').fadeOut(200, function() { $(this).addClass('ap-hidden'); });
                    location.reload();
                } else {
                    alert(response.data || self.strings.error);
                }
            });
        },

        saveCapabilities: function() {
            var self = this;

            if (!this.currentRole) {
                alert(this.strings.noRoleSelected || 'Please select a role.');
                return;
            }

            var caps = {};
            $('#ap-ure-caps-container input[type="checkbox"]').each(function() {
                var cap = $(this).attr('name').replace('caps[', '').replace(']', '');
                caps[cap] = $(this).is(':checked') ? 1 : 0;
            });

            var $btn = $('#ap-ure-save-caps');
            var originalText = $btn.html();
            $btn.prop('disabled', true).html('<span class="dashicons dashicons-update spin"></span> ' + (this.strings.saving || 'Saving...'));

            $.post(this.ajaxurl, {
                action: 'ap_ure_update_caps',
                nonce: this.nonce,
                role: this.currentRole,
                caps: caps
            }, function(response) {
                $btn.prop('disabled', false).html(originalText);
                if (response.success) {
                    self.showNotice(self.strings.capsUpdated || 'Capabilities updated.', 'success');
                    self.showSaveNotice(self.strings.capsUpdated || 'Capabilities updated.', 'success');
                } else {
                    self.showSaveNotice(response.data || self.strings.error, 'error');
                }
            }).fail(function() {
                $btn.prop('disabled', false).html(originalText);
                self.showSaveNotice(self.strings.error || 'An error occurred.', 'error');
            });
        },

        showSaveNotice: function(message, type) {
            var $notice = $('#ap-ure-save-notice');
            $notice
                .removeClass('success error')
                .addClass(type)
                .text(message)
                .fadeIn(200);

            if (type === 'success') {
                setTimeout(function() { $notice.fadeOut(300); }, 4000);
            }
        },

        loadUsers: function(page, search) {
            var self = this;
            page = page || 1;
            search = search || '';

            var $tbody = $('#ap-ure-users-tbody');
            $tbody.html('<tr><td colspan="5" style="text-align:center;padding:20px;"><span class="dashicons dashicons-update spin"></span> ' + (this.strings.loading || 'Loading...') + '</td></tr>');

            $.post(this.ajaxurl, {
                action: 'ap_ure_get_users',
                nonce: this.nonce,
                page: page,
                search: search
            }, function(response) {
                if (response.success) {
                    self.renderUsers(response.data);
                } else {
                    $tbody.html('<tr><td colspan="5" style="text-align:center;">' + (self.strings.error || 'Error loading users.') + '</td></tr>');
                }
            });
        },

        renderUsers: function(data) {
            var self = this;
            var $tbody = $('#ap-ure-users-tbody');
            $tbody.empty();

            if (!data.users || data.users.length === 0) {
                $tbody.html('<tr><td colspan="5" style="text-align:center;">' + (this.strings.noUsers || 'No users found.') + '</td></tr>');
                return;
            }

            $.each(data.users, function(i, user) {
                var roles = user.roles.join(', ') || '-';
                var customCaps = user.custom_caps > 0 ? '<span class="ap-badge ap-badge-custom">' + user.custom_caps + '</span>' : '-';

                var row = '<tr>' +
                    '<td><strong>' + self_escHtml(user.display_name) + '</strong><br><small>' + self_escHtml(user.login) + '</small></td>' +
                    '<td>' + self_escHtml(user.email) + '</td>' +
                    '<td>' + self_escHtml(roles) + '</td>' +
                    '<td>' + customCaps + '</td>' +
                    '<td><button type="button" class="button button-small ap-ure-edit-user-caps" data-user-id="' + user.id + '">' + (self.strings.edit || 'Edit') + '</button></td>' +
                    '</tr>';
                $tbody.append(row);
            });

            // Pagination
            var $pagination = $('#ap-ure-users-pagination');
            $pagination.empty();

            if (data.pages > 1) {
                for (var i = 1; i <= data.pages; i++) {
                    var cls = i === data.current_page ? 'button button-primary' : 'button';
                    $pagination.append('<a href="#" class="' + cls + '" data-page="' + i + '">' + i + '</a> ');
                }
            }
        },

        openUserCapsModal: function(userId) {
            var self = this;

            $('#ap-ure-user-modal-id').val(userId);
            $('#ap-ure-user-caps-list').html('<p style="text-align:center;"><span class="dashicons dashicons-update spin"></span> ' + (this.strings.loading || 'Loading...') + '</p>');
            $('#ap-ure-user-caps-modal').removeClass('ap-hidden').fadeIn(200);

            $.post(this.ajaxurl, {
                action: 'ap_ure_get_user_caps',
                nonce: this.nonce,
                user_id: userId
            }, function(response) {
                if (response.success) {
                    self.renderUserCaps(response.data);
                } else {
                    $('#ap-ure-user-caps-list').html('<p>' + (self.strings.error || 'Error') + '</p>');
                }
            });
        },

        renderUserCaps: function(data) {
            var self = this;
            var mode = this.labelDisplayMode;
            var $info = $('#ap-ure-user-modal-info');
            var $list = $('#ap-ure-user-caps-list');
            var $search = $('#ap-ure-user-cap-search');

            $info.html('<strong>' + self_escHtml(data.user.display_name) + '</strong> - ' + data.user.roles.join(', '));
            $list.empty();
            $search.val('').show();

            // Group capabilities
            $.each(data.groups, function(groupId, group) {
                if (!group.caps || group.caps.length === 0) return;

                var $group = $('<div class="ap-ure-user-cap-group"><h4>' + self_escHtml(group.label) + '</h4></div>');
                var $caps = $('<div class="ap-ure-user-cap-list"></div>');

                $.each(group.caps, function(i, cap) {
                    var isRoleCap = data.role_caps && data.role_caps[cap];
                    var isUserCap = data.user_caps[cap];
                    var checked = (isRoleCap || isUserCap) ? 'checked' : '';
                    var disabled = isRoleCap ? 'disabled' : '';
                    var friendly = self.capLabels[cap] || '';
                    var labelHtml;

                    if (mode === 'technical' || !friendly) {
                        labelHtml = '<span class="ap-ure-cap-label"><span class="ap-ure-cap-technical">' + self_escHtml(cap) + '</span></span>';
                    } else if (mode === 'friendly') {
                        labelHtml = '<span class="ap-ure-cap-label"><span class="ap-ure-cap-friendly">' + self_escHtml(friendly) + '</span></span>';
                    } else {
                        labelHtml = '<span class="ap-ure-cap-label"><span class="ap-ure-cap-friendly">' + self_escHtml(friendly) + '</span><span class="ap-ure-cap-technical">' + self_escHtml(cap) + '</span></span>';
                    }

                    var cls = 'ap-ure-cap-item' + (isRoleCap ? ' ap-ure-cap-from-role' : '');
                    $caps.append('<label class="' + cls + '" data-label-mode="' + mode + '"><input type="checkbox" name="user_caps[' + cap + ']" value="1" ' + checked + ' ' + disabled + '> ' + labelHtml + '</label>');
                });

                $group.append($caps);
                $list.append($group);
            });

            // Bind search
            $search.off('input.userCaps').on('input.userCaps', function() {
                var search = $(this).val().toLowerCase().trim();
                if (!search) {
                    $list.find('.ap-ure-cap-item').show();
                    $list.find('.ap-ure-user-cap-group').show();
                    return;
                }
                $list.find('.ap-ure-cap-item').each(function() {
                    var $item = $(this);
                    var technical = $item.find('input').attr('name').replace('user_caps[', '').replace(']', '').toLowerCase();
                    var friendly = $item.find('.ap-ure-cap-friendly').text().toLowerCase();
                    $item.toggle(technical.indexOf(search) !== -1 || friendly.indexOf(search) !== -1);
                });
                $list.find('.ap-ure-user-cap-group').each(function() {
                    var $g = $(this);
                    $g.toggle($g.find('.ap-ure-cap-item:visible').length > 0);
                });
            });
        },

        saveUserCaps: function() {
            var self = this;
            var userId = $('#ap-ure-user-modal-id').val();
            var caps = {};

            $('#ap-ure-user-caps-list input[type="checkbox"]:checked:not(:disabled)').each(function() {
                var cap = $(this).attr('name').replace('user_caps[', '').replace(']', '');
                caps[cap] = 1;
            });

            var $btn = $('#ap-ure-user-caps-save');
            $btn.prop('disabled', true);

            $.post(this.ajaxurl, {
                action: 'ap_ure_update_user_caps',
                nonce: this.nonce,
                user_id: userId,
                caps: caps
            }, function(response) {
                $btn.prop('disabled', false);
                if (response.success) {
                    $('#ap-ure-user-caps-modal').fadeOut(200, function() { $(this).addClass('ap-hidden'); });
                    self.loadUsers();
                    self.showNotice(self.strings.userCapsUpdated || 'User capabilities updated.', 'success');
                } else {
                    alert(response.data || self.strings.error);
                }
            });
        },

        exportRoles: function() {
            var self = this;

            $.post(this.ajaxurl, {
                action: 'ap_ure_export',
                nonce: this.nonce
            }, function(response) {
                if (response.success) {
                    var dataStr = JSON.stringify(response.data.data, null, 2);
                    var blob = new Blob([dataStr], {type: 'application/json'});
                    var url = URL.createObjectURL(blob);
                    var a = document.createElement('a');
                    a.href = url;
                    a.download = response.data.filename;
                    document.body.appendChild(a);
                    a.click();
                    document.body.removeChild(a);
                    URL.revokeObjectURL(url);
                } else {
                    alert(response.data || self.strings.error);
                }
            });
        },

        importRoles: function() {
            var self = this;
            var $file = $('#ap-ure-import-file');
            var file = $file[0].files[0];

            if (!file) {
                alert(this.strings.selectFile || 'Please select a file.');
                return;
            }

            var reader = new FileReader();
            reader.onload = function(e) {
                var content = e.target.result;
                var mode = $('input[name="ap_ure_import_mode"]:checked').val();

                try {
                    JSON.parse(content); // Validate JSON
                } catch (err) {
                    alert(self.strings.invalidJson || 'Invalid JSON file.');
                    return;
                }

                $.post(self.ajaxurl, {
                    action: 'ap_ure_import',
                    nonce: self.nonce,
                    data: content,
                    mode: mode
                }, function(response) {
                    var $result = $('#ap-ure-import-result');
                    if (response.success) {
                        $result.html('<div class="notice notice-success"><p>' +
                            (self.strings.importSuccess || 'Import completed.') +
                            ' (' + response.data.imported + ' imported, ' +
                            response.data.updated + ' updated, ' +
                            response.data.skipped + ' skipped)</p></div>').show();
                        setTimeout(function() { location.reload(); }, 2000);
                    } else {
                        $result.html('<div class="notice notice-error"><p>' + self_escHtml(response.data) + '</p></div>').show();
                    }
                });
            };
            reader.readAsText(file);
        },

        resetToDefaults: function() {
            var self = this;

            if (!confirm(this.strings.confirmReset || 'Are you sure? This will remove all custom roles.')) {
                return;
            }

            $.post(this.ajaxurl, {
                action: 'ap_ure_reset',
                nonce: this.nonce
            }, function(response) {
                if (response.success) {
                    location.reload();
                } else {
                    alert(response.data || self.strings.error);
                }
            });
        },

        addCustomCapability: function() {
            var self = this;
            var cap = $('#ap-ure-new-custom-cap').val().trim().toLowerCase().replace(/[^a-z0-9_]/g, '_');

            if (!cap) {
                alert(this.strings.enterCapName || 'Please enter a capability name.');
                return;
            }

            $.post(this.ajaxurl, {
                action: 'ap_ure_add_custom_cap',
                nonce: this.nonce,
                cap: cap
            }, function(response) {
                if (response.success) {
                    location.reload();
                } else {
                    alert(response.data || self.strings.error);
                }
            });
        },

        removeCustomCapability: function(cap) {
            var self = this;

            $.post(this.ajaxurl, {
                action: 'ap_ure_remove_custom_cap',
                nonce: this.nonce,
                cap: cap
            }, function(response) {
                if (response.success) {
                    location.reload();
                } else {
                    alert(response.data || self.strings.error);
                }
            });
        },

        showNotice: function(message, type) {
            var $header = $('.blaminhor-essentials-wrap .blaminhor-essentials-header');
            if (!$header.length || !window.BlaminhorEssentials) return;
            var $n = BlaminhorEssentials.showNotice(message, type === 'success' ? 'success' : 'error', {
                container: $header,
                position: 'after',
                style: 'wp',
                timeout: 3000
            });
            if ($n && $n.length) $n.css('margin', '15px 0');
        }
    };

    // Debounce helper
    $.debounce = function(delay, callback) {
        var timer = null;
        return function() {
            var context = this, args = arguments;
            clearTimeout(timer);
            timer = setTimeout(function() {
                callback.apply(context, args);
            }, delay);
        };
    };

    // Color fields (favicon/maintenance): sync native picker <-> hex text input
    jQuery(function($) {
        var hexRe = /^#[0-9A-Fa-f]{6}$/;

        $(document).on('input change', '.blaminhor-essentials-color-swatch', function() {
            var $hex = $(this).siblings('.blaminhor-essentials-color-hex');
            if (!$hex.length) { return; }
            $hex.val(this.value).removeClass('blaminhor-essentials-color-hex-invalid');
        });

        $(document).on('input', '.blaminhor-essentials-color-hex', function() {
            var $field = $(this);
            var v = $field.val().trim();
            if (v && v.charAt(0) !== '#') { v = '#' + v; $field.val(v); }
            var $swatch = $field.siblings('.blaminhor-essentials-color-swatch');
            if (hexRe.test(v)) {
                $field.removeClass('blaminhor-essentials-color-hex-invalid');
                $swatch.val(v);
            } else {
                $field.addClass('blaminhor-essentials-color-hex-invalid');
            }
        });

        // On blur: if invalid, restore from swatch
        $(document).on('blur', '.blaminhor-essentials-color-hex', function() {
            var $field = $(this);
            if (!hexRe.test($field.val())) {
                $field.val($field.siblings('.blaminhor-essentials-color-swatch').val())
                      .removeClass('blaminhor-essentials-color-hex-invalid');
            }
        });
    });

    // Cookies module: initialize WordPress color picker (Iris) — HEX by default.
    jQuery(function($) {
        var $fields = $('.blaminhor-essentials-color-picker');
        if (!$fields.length || typeof $.fn.wpColorPicker !== 'function') {
            return;
        }
        $fields.wpColorPicker({
            palettes: false
        });
    });

    // Cookies: master checkbox per services category (3-state)
    jQuery(function($) {
        var $tables = $('.blaminhor-essentials-services-table');
        if (!$tables.length) {
            return;
        }

        var syncMaster = function($table) {
            var $rows  = $table.find('tbody input[type="checkbox"]');
            var total  = $rows.length;
            var on     = $rows.filter(':checked').length;
            var $mst   = $table.find('thead .blaminhor-essentials-services-toggle-all');
            $mst.prop('checked', total > 0 && on === total);
            $mst.prop('indeterminate', on > 0 && on < total);
        };

        $tables.each(function() { syncMaster($(this)); });

        $tables.on('change', '.blaminhor-essentials-services-toggle-all', function() {
            var $table = $(this).closest('table');
            $table.find('tbody input[type="checkbox"]').prop('checked', this.checked);
            syncMaster($table);
        });

        $tables.on('change', 'tbody input[type="checkbox"]', function() {
            syncMaster($(this).closest('table'));
        });
    });

})(jQuery);
