<?php
if (!defined('ABSPATH')) exit;

if ( ! defined( 'MYAIO_SAC_PATH' ) ) { define( 'MYAIO_SAC_PATH', plugin_dir_path( dirname( __FILE__ ) ) ); } // plugin root
if ( ! defined( 'MYAIO_SAC_URL' ) ) { define( 'MYAIO_SAC_URL', plugin_dir_url( dirname( __FILE__ ) ) ); } // plugin root

class MYAIO_SAC_Loader {

    private static function append_install_log($line) {
        $line = trim((string) $line);
        if ($line === '') return;

        $ts = '[' . gmdate('Y-m-d H:i:s') . ' UTC] ';
        $entry = $ts . $line;

        // Store in option (used by installer UI).
        $log = (array) get_option('myaio_install_log', []);
        $log[] = $entry;
        if (count($log) > 400) {
            $log = array_slice($log, -400);
        }
        update_option('myaio_install_log', $log);

        // Also write a real file so logs survive option resets.
        $log_dir = defined('MYAIO_STORAGE_PATH') ? MYAIO_STORAGE_PATH : '';
        if (!$log_dir) {
            $u = wp_upload_dir();
            $log_dir = trailingslashit($u['basedir']) . 'myaio/';
        }
        if ( ! file_exists( $log_dir ) ) {
            wp_mkdir_p( $log_dir );
        }
        $log_file = trailingslashit($log_dir) . 'install.log';
        // Best-effort append; suppress errors on locked FS.
        @file_put_contents($log_file, $entry . "\n", FILE_APPEND);
    }

    private static function reset_installer_state() {
        delete_option('myaio_setup_state');
        delete_option('myaio_setup_complete');
        delete_option('myaio_install_log');
        delete_option('myaio_primary_sitemap_url');
        update_option('myaio_install_state', 'pending');
        update_option('myaio_install_step', 'start');
        
        // Clear database backup state (stored separately by MYAIO_DB_Backup_Optimized)
        global $wpdb;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
                $wpdb->esc_like('myaio_db_backup_state_') . '%'
            )
        );
        
        // Clear background backup state
        delete_option('myaio_background_backup_run_id');
        delete_option('myaio_background_db_status');
        delete_option('myaio_background_db_progress');
        delete_option('myaio_background_db_result');
        delete_option('myaio_background_db_error');
        delete_option('myaio_background_files_status');
        delete_option('myaio_background_files_result');
        delete_option('myaio_background_files_error');
        
        // Clear S3 URLs
        delete_option('myaio_s3_url_db');
        delete_option('myaio_s3_url_files');
    }

    private static function remote_post_with_retries($endpoint, $args, $context = '') {
        $max = defined('MYAIO_RETRY_ATTEMPTS') ? (int) MYAIO_RETRY_ATTEMPTS : 4;
        if ($max < 1) $max = 1;

        for ($attempt = 1; $attempt <= $max; $attempt++) {
            self::append_install_log(($context ? $context . ': ' : '') . 'HTTP attempt ' . $attempt . '/' . $max . ' -> ' . $endpoint);

            $resp = wp_remote_post($endpoint, $args);

            if (is_wp_error($resp)) {
                self::append_install_log(($context ? $context . ': ' : '') . 'HTTP error: ' . $resp->get_error_message());
                if ($attempt < $max) {
                    usleep(500000);
                    continue;
                }
                return $resp;
            }

            $code = (int) wp_remote_retrieve_response_code($resp);
            if (($code === 0 || $code === 408 || $code === 429 || $code >= 500) && $attempt < $max) {
                $body = (string) wp_remote_retrieve_body($resp);
                $body = trim(preg_replace('/\s+/', ' ', $body));
                if ($body) {
                    if (strlen($body) > 200) $body = substr($body, 0, 200) . '…';
                    self::append_install_log(($context ? $context . ': ' : '') . 'HTTP ' . $code . ' retrying. Response: ' . $body);
                } else {
                    self::append_install_log(($context ? $context . ': ' : '') . 'HTTP ' . $code . ' retrying.');
                }
                usleep(500000);
                continue;
            }

            return $resp;
        }

        return new WP_Error('myaio_retry_exhausted', 'Retry attempts exhausted.');
    }

    /**
     * Determine the primary deployed sitemap URL.
     *
     * Prefers plugin-style sitemap files over WordPress core sitemap.
     *
     * @return string
     */
    private static function get_primary_sitemap_url() {
        $preferred_files = array(
            'sitemap_index.xml',
            'sitemap.xml',
            'post-sitemap.xml',
            'page-sitemap.xml',
            'category-sitemap.xml',
            'post_tag-sitemap.xml',
            'author-sitemap.xml',
            'news-sitemap.xml',
            'video-sitemap.xml',
            'image-sitemap.xml',
            'wp-sitemap.xml',
        );

        foreach ($preferred_files as $file) {
            $path = ABSPATH . $file;
            if (file_exists($path) && is_file($path)) {
                return home_url('/' . $file);
            }
        }

        return '';
    }

    /**
     * ✅ Redirect to installer page right after activation
     */
    public static function maybe_redirect_to_installer() {
        if (!is_admin()) return;
        if (!current_user_can('manage_options')) return;

        if (get_transient('_myaio_do_installer_redirect')) {
            delete_transient('_myaio_do_installer_redirect');
            wp_safe_redirect(admin_url('admin.php?page=myaio_installer'));
            exit;
        }
    }

    public static function init() {

        add_action('admin_init', [__CLASS__, 'maybe_redirect_to_installer'], 0);

        require_once MYAIO_SAC_PATH . 'includes/backups/class-myaio-db-backup-optimized.php';
        require_once MYAIO_SAC_PATH . 'includes/backups/class-myaio-files-backup.php';   
        require_once MYAIO_SAC_PATH . 'includes/snapshots/class-myaio-seo-snapshot.php';
        require_once MYAIO_SAC_PATH . 'includes/seo/class-myaio-seo-continuity.php';
        require_once MYAIO_SAC_PATH . 'includes/seo/class-myaio-seo-migrator.php';

        if ( class_exists('MYAIO_SEO_Continuity') && is_callable(['MYAIO_SEO_Continuity', 'init_runtime']) ) {
            MYAIO_SEO_Continuity::init_runtime();
        } else {
            self::append_install_log('SEO continuity class not found. Skipping MYAIO_SEO_Continuity::init_runtime().');
        }

        if ( class_exists('MYAIO_SEO_Migrator') && is_callable(['MYAIO_SEO_Migrator', 'boot']) ) {
            MYAIO_SEO_Migrator::boot();
        }

        self::load_modules();
        add_action('admin_menu', [__CLASS__, 'register_admin_menu']);
        add_action('admin_init', [__CLASS__, 'enforce_installer_gate'], 1);
        add_action('admin_init', [__CLASS__, 'handle_api_key_submission']);

        add_action('wp_ajax_myaio_run_setup_step', [__CLASS__, 'ajax_run_setup_step']);
        
        add_action('myaio_background_db_backup', [__CLASS__, 'background_db_backup']);
        add_action('myaio_background_files_backup', [__CLASS__, 'background_files_backup']);
        
        add_action('wp_ajax_myaio_check_backup_status', [__CLASS__, 'ajax_check_backup_status']);
        add_action('wp_ajax_myaio_list_backup_files', [__CLASS__, 'ajax_list_backup_files']);
        add_action('wp_ajax_myaio_trigger_backup_manually', [__CLASS__, 'ajax_trigger_backup_manually']);
        add_action('wp_ajax_myaio_upload_backup_to_s3', [__CLASS__, 'ajax_upload_backup_to_s3']);
        add_action('wp_ajax_myaio_toggle_s3_upload', [__CLASS__, 'ajax_toggle_s3_upload']);
        add_action('wp_ajax_myaio_check_s3_status', [__CLASS__, 'ajax_check_s3_status']);

        // if (get_option('myaio_sac_api_verified')) {
        //     add_action('wp_footer', 'myaio_sac_add_crawler_tracker');
        // }
    }

    public static function activate() {
        update_option('myaio_sac_api_verified', false);
        update_option('myaio_sac_api_key', '');

        update_option('myaio_install_state', 'pending');
        update_option('myaio_install_step', 'start');

        delete_option('myaio_setup_state');
        delete_option('myaio_setup_complete');
        delete_option('myaio_primary_sitemap_url');
        
        if (false === get_option('myaio_s3_upload_enabled')) {
            add_option('myaio_s3_upload_enabled', true);
        }

        set_transient('_myaio_do_installer_redirect', true, 300);
    }

    public static function deactivate() {
        $api_key = get_option('myaio_sac_api_key');
        $site_url = home_url();

        if (!empty($api_key)) {
            wp_remote_post('https://app.myaio.com/api/projects/status', [
                'body' => [
                    'website_id' => $api_key,
                    'url' => $site_url,
                    'status' => 'deactive',
                    'last_step'=>'4'
                ]
            ]);
        }

        delete_option('sac_api_key');
        delete_option('sac_api_verified');

        delete_option('myaio_website_id');

        delete_option('myaio_setup_state');
        delete_option('myaio_setup_complete');
        delete_option('myaio_install_state');
        delete_option('myaio_install_step');

        delete_transient('_myaio_do_installer_redirect');
        delete_transient('_sac_do_redirect');
    }

    public static function register_admin_menu() {
        $state = (string) get_option('myaio_install_state', 'pending');

        if ($state !== 'completed') {
            add_menu_page(
                'MYAIO Installer',
                'MYAIO Installer',
                'manage_options',
                'myaio_installer',
                [__CLASS__, 'installer_page'],
                'dashicons-hammer',
                3
            );

            add_submenu_page(null, 'MYAIO API Key', 'MYAIO API Key', 'manage_options', 'myaio_api_key', [__CLASS__, 'api_key_page']);
            return;
        }

        add_menu_page(
            'MYAIO SEO',
            'MYAIO SEO',
            'manage_options',
            'myaio_sac_dashboard',
            [__CLASS__, 'dashboard_page'],
            'dashicons-chart-area',
            3
        );

        add_submenu_page('myaio_sac_dashboard', 'API Key', 'API Key', 'manage_options', 'sac_settings', [__CLASS__, 'api_key_page']);

        add_submenu_page(null, 'MYAIO Installer', 'MYAIO Installer', 'manage_options', 'myaio_installer', [__CLASS__, 'installer_page']);
        add_submenu_page(null, 'MYAIO API Key', 'MYAIO API Key', 'manage_options', 'myaio_api_key', [__CLASS__, 'api_key_page']);
    }

    public static function installer_page() {
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions.');
        }

        if ((string) get_option('myaio_install_state', 'pending') === 'completed') {
            wp_safe_redirect(admin_url('admin.php?page=myaio_api_key'));
            exit;
        }

        $nonce = wp_create_nonce('myaio_setup');

        $setup_state = get_option('myaio_setup_state', []);
        $steps_order = ['dirs','seo_options','db','files','json','deploy','api','seo_gate','done'];
        $next_step = 'dirs';
        if (is_array($setup_state)) {
            foreach ($steps_order as $sid) {
                $done = false;
                if ($sid === 'dirs') {
                    $done = file_exists(MYAIO_STORAGE_PATH);
                } elseif ($sid === 'seo_options') {
                    $done = !empty($setup_state['seo_options']);
                } elseif ($sid === 'db') {
                    $db_status = get_option('myaio_background_db_status', 'pending');
                    $done = ($db_status === 'completed');
                } elseif ($sid === 'files') {
                    $files_status = get_option('myaio_background_files_status', 'pending');
                    $done = ($files_status === 'completed');
                } elseif ($sid === 'json') {
                    $done = !empty($setup_state['snapshot']);
                } elseif ($sid === 'deploy') {
                    $done = !empty($setup_state['deploy']);
                } elseif ($sid === 'api') {
                    $done = !empty($setup_state['api']);
                } elseif ($sid === 'seo_gate') {
                    $done = false;
                } elseif ($sid === 'done') {
                    $done = !empty(get_option('myaio_setup_complete'));
                }

                if (!$done) { $next_step = $sid; break; }
            }
        }

        $resume_from = $next_step;
        $log_lines = (array) get_option('myaio_install_log', []);
        ?>
        <div class="wrap">
            <h1>MYAIO Installer</h1>
            <p>This will take a quick backup and register your site, then you’ll enter your Activation Key.</p>

            <div id="myaio-setup-status" style="background:#fff;border:1px solid #ccd0d4;padding:12px;max-width:860px;">
                <strong>Status:</strong> <span id="myaio-setup-current">Starting…</span>
                <pre id="myaio-setup-log" style="margin-top:12px;white-space:pre-wrap;max-height:360px;overflow:auto;"><?php echo esc_html(implode("\n", $log_lines)); ?></pre>
                <div id="myaio-setup-actions" style="margin-top:12px; display:none;">
                    <button type="button" class="button button-primary" id="myaio-setup-retry">Retry</button>
                    <button type="button" class="button" id="myaio-setup-restart" style="margin-left:6px;">Restart</button>
                </div>
            </div>
        </div>

        <!-- <div class="wrap" style="margin-top:20px;">
            <div id="myaio-backup-info" style="background:#fff;border:1px solid #ccd0d4;padding:12px;max-width:860px;">
                <strong>Backup Files:</strong>
                <button type="button" class="button button-small" id="myaio-refresh-backups" style="margin-left:10px;">Refresh</button>
                <button type="button" class="button button-small" id="myaio-trigger-db-backup" style="margin-left:10px;">Trigger DB Backup</button>
                <button type="button" class="button button-small" id="myaio-trigger-files-backup" style="margin-left:10px;">Trigger Files Backup</button>
                <button type="button" class="button button-small <?php echo get_option('myaio_s3_upload_enabled', false) ? 'button-primary' : ''; ?>" id="myaio-toggle-s3" style="margin-left:10px;">
                    S3 Upload: <?php echo get_option('myaio_s3_upload_enabled', false) ? 'ON' : 'OFF'; ?>
                </button>
                <div id="myaio-backup-list" style="margin-top:12px;">
                    <p style="color:#666;">Click "Refresh" to see backup files...</p>
                </div>
            </div>
        </div> -->

        <script>
        (function(){
            const logEl = document.getElementById('myaio-setup-log');
            const curEl = document.getElementById('myaio-setup-current');
            const actionsEl = document.getElementById('myaio-setup-actions');
            const retryBtn = document.getElementById('myaio-setup-retry');
            const restartBtn = document.getElementById('myaio-setup-restart');
            const MAX_ATTEMPTS = <?php echo (int) (defined('MYAIO_RETRY_ATTEMPTS') ? MYAIO_RETRY_ATTEMPTS : 4); ?>;
            const STEP_TIMEOUT_MS = <?php echo (int) ((defined('MYAIO_REQUEST_TIMEOUT') ? MYAIO_REQUEST_TIMEOUT : 900) * 1000); ?>;
            const setupNonce = '<?php echo esc_js($nonce); ?>';
            const steps = [
                {id: 'dirs', label: 'Preparing storage…'},
                {id: 'seo_options', label: 'Backing up SEO plugin settings…'},
                {id: 'db', label: 'Starting database backup (async)…', isAsync: false},
                {id: 'files', label: 'Starting files backup (async)…', isAsync: false},
                {id: 'json', label: 'Building complete JSON…'},
                {id: 'deploy', label: 'Deploying rendering changes…'},
                {id: 'api', label: 'Sending backup data to API…'},
                {id: 'seo_gate', label: 'Checking SEO plugins…'},
                {id: 'done', label: 'Redirecting to Activation Key…'}
            ];

            const resumeStep = '<?php echo esc_js($resume_from); ?>';
            const startIndex = Math.max(0, steps.findIndex(s => s.id === resumeStep));

            function append(msg){
                logEl.textContent += msg + "\n";
                logEl.scrollTop = logEl.scrollHeight;
            }

            logEl.scrollTop = logEl.scrollHeight;

            let failedIndex = null;
            let isRunning = false;

            function showActions(show){
                actionsEl.style.display = show ? 'block' : 'none';
            }

            const activeMonitors = {};

            async function monitorBackupInBackground(stepId) {
                if (activeMonitors[stepId]) {
                    return;
                }

                activeMonitors[stepId] = true;

                const pollFd = new FormData();
                pollFd.append('action', 'myaio_check_backup_status');
                pollFd.append('_wpnonce', setupNonce);

                let lastMessage = '';
                let pollCount = 0;
                let maxPollsBeforeFallback = 2;
                let hasTriggeredFallback = false;

                const monitor = setInterval(async () => {
                    pollCount++;

                    try {
                        const res = await fetch(ajaxurl, {method: 'POST', body: pollFd});
                        const json = await res.json();

                        if (!json || !json.success) return;

                        const data = json.data;

                        if (stepId === 'db') {
                            const status = data.db.status;
                            const progress = data.db.progress || 0;
                            const message = data.db.message || '';

                            if (status === 'scheduled' && pollCount >= maxPollsBeforeFallback && !hasTriggeredFallback) {
                                hasTriggeredFallback = true;
                                append('⚠ WP-Cron not responding, triggering DB backup manually...');

                                const triggerFd = new FormData();
                                triggerFd.append('action', 'myaio_trigger_backup_manually');
                                triggerFd.append('backup_type', 'db');
                                triggerFd.append('_wpnonce', setupNonce);

                                await fetch(ajaxurl, {method: 'POST', body: triggerFd});
                                pollCount = 0;
                                return;
                            }

                            if (status === 'completed') {
                                append('✅ Database backup completed in background');
                                clearInterval(monitor);
                                delete activeMonitors[stepId];
                                if (refreshBtn) refreshBtn.click();
                                uploadBackupToS3('db');
                            } else if (status === 'failed') {
                                append('❌ Database backup failed: ' + (data.db.error || 'Unknown error'));
                                clearInterval(monitor);
                                delete activeMonitors[stepId];
                            } else if (status === 'in_progress') {
                                if (message && message !== lastMessage) {
                                    append('  → DB: ' + message + ' (' + progress + '%)');
                                    lastMessage = message;
                                }
                            }
                        } else if (stepId === 'files') {
                            const status = data.files.status;

                            if (status === 'scheduled' && pollCount >= maxPollsBeforeFallback && !hasTriggeredFallback) {
                                hasTriggeredFallback = true;
                                append('⚠ WP-Cron not responding, triggering files backup manually...');

                                const triggerFd = new FormData();
                                triggerFd.append('action', 'myaio_trigger_backup_manually');
                                triggerFd.append('backup_type', 'files');
                                triggerFd.append('_wpnonce', setupNonce);

                                await fetch(ajaxurl, {method: 'POST', body: triggerFd});
                                pollCount = 0;
                                return;
                            }

                            if (status === 'completed') {
                                append('✅ Files backup completed in background');
                                clearInterval(monitor);
                                delete activeMonitors[stepId];
                                if (refreshBtn) refreshBtn.click();
                                uploadBackupToS3('files');
                            } else if (status === 'failed') {
                                append('❌ Files backup failed: ' + (data.files.error || 'Unknown error'));
                                clearInterval(monitor);
                                delete activeMonitors[stepId];
                            }
                        }
                    } catch (e) {
                    }
                }, 3000);
            }

            async function uploadBackupToS3(backupType) {
                append('→ Checking S3 upload status...');

                const checkFd = new FormData();
                checkFd.append('action', 'myaio_check_s3_status');
                checkFd.append('_wpnonce', setupNonce);

                try {
                    const checkRes = await fetch(ajaxurl, {method: 'POST', body: checkFd});
                    const checkJson = await checkRes.json();

                    if (!checkJson || !checkJson.success || !checkJson.data.enabled) {
                        append('  → S3 upload disabled, keeping local backup');
                        return;
                    }
                } catch (e) {
                    append('  → Could not check S3 status, keeping local backup');
                    return;
                }

                append('→ Uploading ' + backupType + ' backup to S3...');

                const uploadFd = new FormData();
                uploadFd.append('action', 'myaio_upload_backup_to_s3');
                uploadFd.append('backup_type', backupType);
                uploadFd.append('_wpnonce', setupNonce);

                try {
                    const res = await fetch(ajaxurl, {method: 'POST', body: uploadFd});
                    const text = await res.text();

                    let json;
                    try {
                        json = JSON.parse(text);
                    } catch (e) {
                        append('❌ Server error');
                        return;
                    }

                    if (json && json.success) {
                        append('✅ ' + json.data.message);
                        if (refreshBtn) refreshBtn.click();
                    } else {
                        const msg = (json && json.data && json.data.message) ? json.data.message : 'Upload failed';
                        append('❌ S3 upload failed: ' + msg);
                    }
                } catch (e) {
                    append('❌ S3 upload error: ' + e.message);
                }
            }

            async function run(startIndex){
                if (isRunning) return;
                isRunning = true;
                showActions(false);
                failedIndex = null;

                for (let i = startIndex; i < steps.length; i++) {
                    const s = steps[i];
                    curEl.textContent = s.label;
                    append('▶ ' + s.label);

                    const fd = new FormData();
                    fd.append('action','myaio_run_setup_step');
                    fd.append('step', s.id);
                    fd.append('_wpnonce', setupNonce);

                    let json = null;
                    for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
                        try {
                            if (attempt > 1) {
                                append('↻ Network retry ' + attempt + '/' + MAX_ATTEMPTS + '…');
                            }
                            const controller = new AbortController();
                            const t = setTimeout(() => controller.abort(), STEP_TIMEOUT_MS);
                            const res = await fetch(ajaxurl, {method:'POST', body: fd, signal: controller.signal});
                            clearTimeout(t);
                            json = await res.json();
                            break;
                        } catch (e) {
                            json = null;
                            if (attempt === MAX_ATTEMPTS) {
                                json = { success: false, data: { message: 'Network/parse error. We tried ' + MAX_ATTEMPTS + ' times. Please reinstall the plugin after some time (setup was halted).' }};
                            }
                        }
                    }

                    if (!json || !json.success) {
                        const msg = (json && json.data && json.data.message) ? json.data.message : 'Setup failed.';
                        append('❌ ' + msg);
                        curEl.textContent = 'Error';
                        failedIndex = i;
                        showActions(true);
                        isRunning = false;
                        return;
                    }

                    if (json.data && json.data.message) append('✅ ' + json.data.message);

                    if (s.id === 'db' || s.id === 'files') {
                        monitorBackupInBackground(s.id);
                    }

                    if (json.data && json.data.continue) {
                        i--;
                        continue;
                    }
                }

                curEl.textContent = 'Redirecting…';
                window.location.href = '<?php echo esc_js(admin_url('admin.php?page=myaio_api_key')); ?>';
                isRunning = false;
            }

            retryBtn.addEventListener('click', function(){
                append('↻ Retrying…');
                run(failedIndex === null ? 0 : failedIndex);
            });

            restartBtn.addEventListener('click', function(){
                append('↺ Restarting from beginning…');
                const fd = new FormData();
                fd.append('action','myaio_run_setup_step');
                fd.append('step','reset');
                fd.append('_wpnonce', setupNonce);
                fetch(ajaxurl, {method:'POST', body: fd}).then(()=>{
                    logEl.textContent = '';
                    run(0);
                });
            });

            run(startIndex);

            const refreshBtn = document.getElementById('myaio-refresh-backups');
            const backupList = document.getElementById('myaio-backup-list');
            const triggerDbBtn = document.getElementById('myaio-trigger-db-backup');
            const triggerFilesBtn = document.getElementById('myaio-trigger-files-backup');
            const toggleS3Btn = document.getElementById('myaio-toggle-s3');

            setInterval(() => {
                if (isRunning || activeMonitors.db || activeMonitors.files) {
                    refreshBtn.click();
                }
            }, 5000);

            triggerDbBtn.addEventListener('click', function() {
                triggerDbBtn.disabled = true;
                triggerDbBtn.textContent = 'Triggering...';

                const fd = new FormData();
                fd.append('action', 'myaio_trigger_backup_manually');
                fd.append('backup_type', 'db');
                fd.append('_wpnonce', setupNonce);

                fetch(ajaxurl, {method: 'POST', body: fd})
                    .then(res => res.json())
                    .then(json => {
                        if (json && json.success) {
                            append('✓ ' + json.data.message);
                            setTimeout(() => refreshBtn.click(), 2000);
                        } else {
                            append('✗ Failed to trigger DB backup');
                        }
                    })
                    .finally(() => {
                        triggerDbBtn.disabled = false;
                        triggerDbBtn.textContent = 'Trigger DB Backup';
                    });
            });

            triggerFilesBtn.addEventListener('click', function() {
                triggerFilesBtn.disabled = true;
                triggerFilesBtn.textContent = 'Triggering...';

                const fd = new FormData();
                fd.append('action', 'myaio_trigger_backup_manually');
                fd.append('backup_type', 'files');
                fd.append('_wpnonce', setupNonce);

                fetch(ajaxurl, {method: 'POST', body: fd})
                    .then(res => res.json())
                    .then(json => {
                        if (json && json.success) {
                            append('✓ ' + json.data.message);
                            setTimeout(() => refreshBtn.click(), 2000);
                        } else {
                            append('✗ Failed to trigger files backup');
                        }
                    })
                    .finally(() => {
                        triggerFilesBtn.disabled = false;
                        triggerFilesBtn.textContent = 'Trigger Files Backup';
                    });
            });

            toggleS3Btn.addEventListener('click', function() {
                toggleS3Btn.disabled = true;
                const originalText = toggleS3Btn.textContent;
                toggleS3Btn.textContent = 'Toggling...';

                const fd = new FormData();
                fd.append('action', 'myaio_toggle_s3_upload');
                fd.append('_wpnonce', setupNonce);

                fetch(ajaxurl, {method: 'POST', body: fd})
                    .then(res => res.json())
                    .then(json => {
                        if (json && json.success) {
                            const enabled = json.data.enabled;
                            toggleS3Btn.textContent = 'S3 Upload: ' + (enabled ? 'ON' : 'OFF');
                            toggleS3Btn.className = 'button button-small' + (enabled ? ' button-primary' : '');
                            append('✓ ' + json.data.message);
                        } else {
                            append('✗ Failed to toggle S3 upload');
                            toggleS3Btn.textContent = originalText;
                        }
                    })
                    .catch(e => {
                        append('✗ Error toggling S3: ' + e.message);
                        toggleS3Btn.textContent = originalText;
                    })
                    .finally(() => {
                        toggleS3Btn.disabled = false;
                    });
            });

            refreshBtn.addEventListener('click', function() {
                refreshBtn.disabled = true;
                refreshBtn.textContent = 'Loading...';

                const fd = new FormData();
                fd.append('action', 'myaio_list_backup_files');
                fd.append('_wpnonce', setupNonce);

                fetch(ajaxurl, {method: 'POST', body: fd})
                    .then(res => res.json())
                    .then(json => {
                        if (json && json.success && json.data) {
                            const data = json.data;
                            let html = '<div style="font-size:12px;">';

                            html += '<p><strong>Storage Path:</strong> <code>' + data.storage_path + '</code></p>';

                            if (data.s3_urls && (data.s3_urls.db || data.s3_urls.files)) {
                                html += '<h4 style="margin:10px 0 5px 0;color:#2271b1;">☁️ S3 Uploaded Backups:</h4>';
                                html += '<div style="background:#f0f6fc;padding:10px;border-left:3px solid #2271b1;margin-bottom:15px;">';

                                if (data.s3_urls.db) {
                                    html += '<p style="margin:5px 0;"><strong>Database:</strong><br>';
                                    html += '<a href="' + data.s3_urls.db + '" target="_blank" rel="noopener noreferrer" style="word-break:break-all;color:#2271b1;">' + data.s3_urls.db + '</a></p>';
                                }

                                if (data.s3_urls.files) {
                                    html += '<p style="margin:5px 0;"><strong>Files:</strong><br>';
                                    html += '<a href="' + data.s3_urls.files + '" target="_blank" rel="noopener noreferrer" style="word-break:break-all;color:#2271b1;">' + data.s3_urls.files + '</a></p>';
                                }

                                html += '</div>';
                            }

                            html += '<h4 style="margin:10px 0 5px 0;">Database Backups (' + data.db_files.length + '):</h4>';
                            if (data.db_files.length > 0) {
                                html += '<ul style="margin:0;padding-left:20px;">';
                                data.db_files.forEach(f => {
                                    html += '<li><strong>' + f.name + '</strong> - ' + f.size_mb + ' MB (modified: ' + f.modified + ')</li>';
                                });
                                html += '</ul>';
                            } else {
                                html += '<p style="color:#999;margin:5px 0;">No database backup files found.</p>';
                            }

                            html += '<h4 style="margin:10px 0 5px 0;">Files Backups (' + data.files_files.length + '):</h4>';
                            if (data.files_files.length > 0) {
                                html += '<ul style="margin:0;padding-left:20px;">';
                                data.files_files.forEach(f => {
                                    html += '<li><strong>' + f.name + '</strong> - ' + f.size_mb + ' MB (modified: ' + f.modified + ')</li>';
                                });
                                html += '</ul>';
                            } else {
                                html += '<p style="color:#999;margin:5px 0;">No files backup found.</p>';
                            }

                            html += '<h4 style="margin:10px 0 5px 0;">SEO Options Backups (' + data.seo_files.length + '):</h4>';
                            if (data.seo_files.length > 0) {
                                html += '<ul style="margin:0;padding-left:20px;">';
                                data.seo_files.forEach(f => {
                                    html += '<li><strong>' + f.name + '</strong> - ' + f.size_kb + ' KB (modified: ' + f.modified + ')</li>';
                                });
                                html += '</ul>';
                            } else {
                                html += '<p style="color:#999;margin:5px 0;">No SEO options backup found.</p>';
                            }

                            html += '</div>';
                            backupList.innerHTML = html;
                        } else {
                            backupList.innerHTML = '<p style="color:red;">Failed to load backup files.</p>';
                        }
                    })
                    .catch(e => {
                        backupList.innerHTML = '<p style="color:red;">Error loading backup files.</p>';
                    })
                    .finally(() => {
                        refreshBtn.disabled = false;
                        refreshBtn.textContent = 'Refresh';
                    });
            });

            setInterval(() => {
                if (isRunning) {
                    refreshBtn.click();
                }
            }, 10000);
        })();
        </script>
        <?php
    }

    public static function enforce_installer_gate() {
        if (!current_user_can('manage_options')) return;

        $state = (string) get_option('myaio_install_state', 'pending');
        if ($state === 'completed') return;

        if (defined('DOING_AJAX') && DOING_AJAX) return;

        global $pagenow;
        $allowed_pagenows = ['plugins.php', 'plugin-install.php', 'update.php', 'themes.php', 'theme-install.php'];
        if (in_array($pagenow, $allowed_pagenows, true)) return;

        $page = '';
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- page parameter used for admin routing only.
        if ( isset( $_GET['page'] ) ) {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- page parameter used for admin routing only.
            $page = sanitize_key( wp_unslash( $_GET['page'] ) );
        }
        $allowed_pages = ['myaio_installer'];
        if (in_array($page, $allowed_pages, true)) return;

        wp_safe_redirect(admin_url('admin.php?page=myaio_installer'));
        exit;
    }

    /**
     * Backup SEO plugin options to a JSON file (independent of database backup)
     * Also backs up physical sitemap files generated by SEO plugins
     *
     * @param string $dest_dir Destination directory for backup
     * @param string $run_id Unique run identifier
     * @return array Backup info with filename, path, plugins
     */
    private static function backup_seo_plugin_options($dest_dir, $run_id) {
        if (!function_exists('is_plugin_active')) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }

        $dest_dir = (string) $dest_dir;
        $run_id = (string) $run_id;

        if (!is_dir($dest_dir) && !wp_mkdir_p($dest_dir)) {
            throw new Exception('Cannot create SEO options backup directory.');
        }

        $sitemap_backup_dir = MYAIO_BACKUP_SITEMAPS_PATH;
        if (!is_dir($sitemap_backup_dir) && !wp_mkdir_p($sitemap_backup_dir)) {
            throw new Exception('Cannot create sitemap backup directory.');
        }

        $old_sitemaps = glob( trailingslashit( $sitemap_backup_dir ) . '*' );
		if ( is_array( $old_sitemaps ) ) {
			foreach ( $old_sitemaps as $old_sitemap ) {
				if ( is_file( $old_sitemap ) ) {
					wp_delete_file( $old_sitemap );
				}
			}
		}
        self::append_install_log('  ✓ Cleaned up old sitemap backups');

        $pattern = trailingslashit($dest_dir) . 'seo_options_*.json';
        $old_files = glob($pattern);
        if (is_array($old_files)) {
            foreach ($old_files as $old_file) {
                if (is_file($old_file)) {
                    wp_delete_file($old_file);
                }
            }
        }

        $old_sitemaps = glob(trailingslashit($sitemap_backup_dir) . '*');
        if (is_array($old_sitemaps)) {
            foreach ($old_sitemaps as $old_sitemap) {
                if (is_file($old_sitemap)) {
                    wp_delete_file($old_sitemap);
                }
            }
        }

        $known_plugins = array(
            'wordpress-seo/wp-seo.php' => 'yoast',
            'seo-by-rank-math/rank-math.php' => 'rankmath',
            'all-in-one-seo-pack/all_in_one_seo_pack.php' => 'aioseo',
            'autodescription/autodescription.php' => 'seoframework',
        );

        $backup_data = array(
            'site' => home_url('/'),
            'created_at' => gmdate('c'),
            'wp_version' => get_bloginfo('version'),
            'plugins' => array(),
            'sitemap_files' => array(),
        );

        foreach ($known_plugins as $plugin_path => $slug) {
            if (is_plugin_active($plugin_path)) {
                $options = self::collect_seo_plugin_options($slug);
                if (!empty($options)) {
                    $backup_data['plugins'][$slug] = array(
                        'plugin_path' => $plugin_path,
                        'options' => $options,
                    );
                }
            }
        }

        self::append_install_log('  → Scanning for sitemap files in WordPress root...');

        $sitemap_files = array(
            'sitemap.xml',
            'sitemap_index.xml',
            'wp-sitemap.xml',
            'post-sitemap.xml',
            'page-sitemap.xml',
            'category-sitemap.xml',
            'post_tag-sitemap.xml',
            'author-sitemap.xml',
            'sitemap-index.xml',
            'post-sitemap1.xml',
            'page-sitemap1.xml',
            'category-sitemap1.xml',
            'general-sitemap.xml',
            'addl-sitemap.xml',
            'news-sitemap.xml',
            'video-sitemap.xml',
            'image-sitemap.xml',
        );

        $backed_up_files = array();
        $checked_count = 0;

        foreach ($sitemap_files as $sitemap_file) {
            $source_path = ABSPATH . $sitemap_file;
            $checked_count++;

            if (file_exists($source_path) && is_file($source_path)) {
                $backup_filename = $sitemap_file . '.backup';
                $backup_path = $sitemap_backup_dir . $backup_filename;

                if (@copy($source_path, $backup_path)) {
                    $content = @file_get_contents($backup_path);
                    if ($content !== false) {
                        $cleaned_content = preg_replace('/<\?xml-stylesheet\s+[^?]*\?>\s*/i', '', $content);
                        $cleaned_content = str_ireplace('rankmath.com', 'myaio.com', $cleaned_content);
                        $cleaned_content = str_replace('Rank Math', 'MYAIO', $cleaned_content);
                        @file_put_contents($backup_path, $cleaned_content);
                    }

                    $backed_up_files[] = array(
                        'original' => $sitemap_file,
                        'backup' => $backup_filename,
                        'size' => filesize($backup_path),
                        'type' => 'physical',
                    );
                    self::append_install_log('  ✓ Backed up physical sitemap: ' . $sitemap_file);
                }
            }
        }

        $xml_files = glob(ABSPATH . '*sitemap*.xml');
        if (is_array($xml_files)) {
            foreach ($xml_files as $xml_file) {
                $filename = basename($xml_file);
                $already_backed_up = false;
                foreach ($backed_up_files as $backed_up) {
                    if ($backed_up['original'] === $filename) {
                        $already_backed_up = true;
                        break;
                    }
                }

                if (!$already_backed_up && is_file($xml_file)) {
                    $backup_filename = $filename . '.backup';
                    $backup_path = $sitemap_backup_dir . $backup_filename;

                    if (@copy($xml_file, $backup_path)) {
                        $content = @file_get_contents($backup_path);
                        if ($content !== false) {
                            $cleaned_content = preg_replace('/<\?xml-stylesheet\s+[^?]*\?>\s*/i', '', $content);
                            $cleaned_content = str_ireplace('rankmath.com', 'myaio.com', $cleaned_content);
                            $cleaned_content = str_replace('Rank Math', 'MYAIO', $cleaned_content);
                            @file_put_contents($backup_path, $cleaned_content);
                        }

                        $backed_up_files[] = array(
                            'original' => $filename,
                            'backup' => $backup_filename,
                            'size' => filesize($backup_path),
                            'type' => 'physical',
                        );
                        self::append_install_log('  ✓ Backed up additional sitemap: ' . $filename);
                    }
                }
            }
        }

        self::append_install_log('  → Checking for virtual/dynamic sitemaps...');

        $virtual_sitemap_urls = array(
            'sitemap_index.xml',
            'post-sitemap.xml',
            'page-sitemap.xml',
            'category-sitemap.xml',
            'post_tag-sitemap.xml',
            'author-sitemap.xml',
        );

        foreach ($virtual_sitemap_urls as $sitemap_file) {
            $already_backed_up = false;
            foreach ($backed_up_files as $backed_up) {
                if ($backed_up['original'] === $sitemap_file) {
                    $already_backed_up = true;
                    break;
                }
            }

            if ($already_backed_up) {
                continue;
            }

            $sitemap_url = home_url('/' . $sitemap_file);
            $response = wp_remote_get($sitemap_url, array(
                'timeout' => 15,
                'sslverify' => false,
            ));

            if (!is_wp_error($response)) {
                $status_code = wp_remote_retrieve_response_code($response);
                $content = wp_remote_retrieve_body($response);

                if ($status_code === 200 && !empty($content) && strpos($content, '<?xml') !== false) {
                    $cleaned_content = preg_replace('/<\?xml-stylesheet\s+[^?]*\?>\s*/i', '', $content);
                    $cleaned_content = str_ireplace('rankmath.com', 'myaio.com', $cleaned_content);
                    $cleaned_content = str_replace('Rank Math', 'MYAIO', $cleaned_content);

                    $backup_filename = $sitemap_file . '.backup';
                    $backup_path = $sitemap_backup_dir . $backup_filename;

                    if (@file_put_contents($backup_path, $cleaned_content)) {
                        $backed_up_files[] = array(
                            'original' => $sitemap_file,
                            'backup' => $backup_filename,
                            'size' => filesize($backup_path),
                            'type' => 'virtual',
                            'url' => $sitemap_url,
                        );
                        self::append_install_log('  ✓ Backed up virtual sitemap: ' . $sitemap_file . ' (from ' . $sitemap_url . ')');
                    }
                }
            }
        }

        self::append_install_log('  ℹ Checked ' . $checked_count . ' known sitemap locations');
        if (empty($backed_up_files)) {
            self::append_install_log('  ⚠ No sitemap files found (they may not be generated yet)');
        } else {
            $physical_count = 0;
            $virtual_count = 0;
            foreach ($backed_up_files as $file) {
                if ($file['type'] === 'physical') {
                    $physical_count++;
                } else {
                    $virtual_count++;
                }
            }
            self::append_install_log('  ✓ Backed up ' . count($backed_up_files) . ' sitemap(s): ' . $physical_count . ' physical, ' . $virtual_count . ' virtual');
        }

        if (!empty($backed_up_files)) {
            self::append_install_log('  → Deploying sitemap files to WordPress root...');
            $deployed_count = 0;

            foreach ($backed_up_files as $file) {
                if (empty($file['backup']) || empty($file['original'])) {
                    continue;
                }

                $backup_path = $sitemap_backup_dir . $file['backup'];
                $deploy_path = ABSPATH . $file['original'];

                if (file_exists($backup_path) && is_file($backup_path) && @copy($backup_path, $deploy_path)) {
                    $content = @file_get_contents($deploy_path);
                    if ($content !== false) {
                        $cleaned_content = preg_replace('/<\?xml-stylesheet\s+[^?]*\?>\s*/i', '', $content);
                        $cleaned_content = str_ireplace('rankmath.com', 'myaio.com', $cleaned_content);
                        $cleaned_content = str_replace('Rank Math', 'MYAIO', $cleaned_content);
                        @file_put_contents($deploy_path, $cleaned_content);
                    }

                    $deployed_count++;
                    self::append_install_log('  ✓ Deployed: ' . $file['original'] . ' to WordPress root');
                }
            }

            if ($deployed_count > 0) {
                $primary_sitemap_url = self::get_primary_sitemap_url();
                if (!empty($primary_sitemap_url)) {
                    update_option('myaio_primary_sitemap_url', esc_url_raw($primary_sitemap_url));
                    self::append_install_log('  ✓ Primary sitemap set: ' . $primary_sitemap_url);
                }

                self::append_install_log('  ✓ Deployed ' . $deployed_count . ' sitemap file(s) to WordPress root');

                flush_rewrite_rules(false);

                if (is_plugin_active('seo-by-rank-math/rank-math.php')) {
                    $rm_sitemap_options = get_option('rank-math-options-sitemap', array());
                    if (is_array($rm_sitemap_options)) {
                        $rm_sitemap_options['sitemap_enable'] = 'off';
                        update_option('rank-math-options-sitemap', $rm_sitemap_options);
                    }
                }

                if (is_plugin_active('wordpress-seo/wp-seo.php')) {
                    $yoast_xml = get_option('wpseo_xml', array());
                    if (is_array($yoast_xml)) {
                        $yoast_xml['disable_sitemap'] = true;
                        update_option('wpseo_xml', $yoast_xml);
                    }
                }
            }
        }

        $backup_data['sitemap_files'] = $backed_up_files;

        $ts = time();
        $safe_id = preg_replace('/[^a-zA-Z0-9_\-]/', '', $run_id);
        $filename = 'seo_options_' . $safe_id . '_' . $ts . '.json';
        $path = trailingslashit($dest_dir) . $filename;

        $json = wp_json_encode($backup_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);

        if (false === @file_put_contents($path, $json)) {
            throw new Exception('Failed to write SEO options backup file.');
        }

        return array(
            'filename' => $filename,
            'path' => $path,
            'plugins' => array_keys($backup_data['plugins']),
            'sitemap_files' => count($backed_up_files),
            'bytes' => file_exists($path) ? filesize($path) : 0,
        );
    }

    /**
     * Collect options for a specific SEO plugin
     *
     * @param string $slug Plugin slug (yoast, rankmath, aioseo, seoframework)
     * @return array|null Plugin options or null
     */
    private static function collect_seo_plugin_options($slug) {
        switch ((string) $slug) {
            case 'yoast':
                return array(
                    'wpseo_titles' => get_option('wpseo_titles', null),
                    'wpseo_social' => get_option('wpseo_social', null),
                    'wpseo_xml' => get_option('wpseo_xml', null),
                    'wpseo' => get_option('wpseo', null),
                    'wpseo_taxonomy_meta' => get_option('wpseo_taxonomy_meta', null),
                );
            case 'rankmath':
                return array(
                    'rank_math_options_titles' => get_option('rank_math_options_titles', null),
                    'rank_math_options_general' => get_option('rank_math_options_general', null),
                    'rank_math_options_sitemap' => get_option('rank_math_options_sitemap', null),
                    'rank-math-options-general' => get_option('rank-math-options-general', null),
                    'rank_math_redirections' => get_option('rank_math_redirections', null),
                );
            case 'aioseo':
                return array(
                    'aioseo_options' => get_option('aioseo_options', null),
                    'aioseo_options_advanced' => get_option('aioseo_options_advanced', null),
                );
            case 'seoframework':
                return array(
                    'the_seo_framework_settings' => get_option('the_seo_framework_settings', null),
                );
            default:
                return null;
        }
    }

    /**
     * Restore sitemap files from backup
     *
     * @param string $backup_dir Backup directory path
     * @param string $run_id Run identifier (optional, uses latest if not provided)
     * @return array Restore results
     */
    public static function restore_seo_sitemaps($backup_dir, $run_id = null) {
        $sitemap_backup_dir = MYAIO_BACKUP_SITEMAPS_PATH;

        if (!is_dir($sitemap_backup_dir)) {
            return array(
                'success' => false,
                'message' => 'Sitemap backup directory not found',
                'restored' => 0,
            );
        }

        $restored = 0;
        $errors = array();

        $backup_files = glob(trailingslashit($sitemap_backup_dir) . '*.backup');

        if (!is_array($backup_files) || empty($backup_files)) {
            return array(
                'success' => false,
                'message' => 'No sitemap backups found',
                'restored' => 0,
            );
        }

        foreach ($backup_files as $backup_file) {
            $original_filename = str_replace('.backup', '', basename($backup_file));
            $restore_path = ABSPATH . $original_filename;

            if (@copy($backup_file, $restore_path)) {
                $restored++;
            } else {
                $errors[] = $original_filename;
            }
        }

        if ($restored > 0) {
            $primary_sitemap_url = self::get_primary_sitemap_url();
            if (!empty($primary_sitemap_url)) {
                update_option('myaio_primary_sitemap_url', esc_url_raw($primary_sitemap_url));
            }
        }

        return array(
            'success' => $restored > 0,
            'message' => sprintf('Restored %d sitemap file(s)', $restored),
            'restored' => $restored,
            'errors' => $errors,
        );
    }

    public static function ajax_run_setup_step() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Insufficient permissions.'], 403);
        }
        check_ajax_referer('myaio_setup');

        $step = isset($_POST['step']) ? sanitize_key(wp_unslash($_POST['step'])) : '';

        self::append_install_log('========================================');
        self::append_install_log('STEP START: ' . strtoupper($step));
        self::append_install_log('========================================');

        $state = get_option('myaio_setup_state', [
            'run_id' => wp_generate_uuid4(),
            'db' => null,
            'files' => null,
            'snapshot' => null,
            'deploy' => null,
            'api' => null,
        ]);

        try {
            switch ($step) {
                case 'reset':
                    self::append_install_log('→ Resetting installer state...');
                    self::reset_installer_state();
                    self::append_install_log('✓ Installer state reset complete');
                    self::append_install_log('========================================');
                    wp_send_json_success(['message' => 'Installer reset.']);

                case 'dirs':
                    self::append_install_log('→ Creating storage directories...');
                    if (function_exists('myaio_ensure_storage_dirs')) {
                        myaio_ensure_storage_dirs();
                        self::append_install_log('  ✓ Storage directories created');
                    }
                    update_option('myaio_install_step', 'dirs');
                    self::append_install_log('✓ Storage ready at ' . MYAIO_STORAGE_PATH);
                    self::append_install_log('========================================');
                    wp_send_json_success(['message' => 'Storage ready (' . MYAIO_STORAGE_PATH . ').']);

                case 'seo_options':
                    self::append_install_log('→ Starting SEO plugin options backup...');
                    self::append_install_log('  → Detecting active SEO plugins...');

                    $seo_backup = self::backup_seo_plugin_options(MYAIO_BACKUP_DB_PATH, $state['run_id']);
                    $state['seo_options'] = $seo_backup;
                    update_option('myaio_setup_state', $state);

                    if (!empty($seo_backup['plugins'])) {
                        $plugin_names = implode(', ', $seo_backup['plugins']);
                        self::append_install_log('  ✓ Backed up options for: ' . $plugin_names);
                    } else {
                        self::append_install_log('  ℹ No SEO plugins detected');
                    }

                    if (!empty($seo_backup['sitemap_files'])) {
                        self::append_install_log('  ✓ Backed up ' . $seo_backup['sitemap_files'] . ' sitemap file(s)');
                    }

                    update_option('myaio_install_step', 'seo_options');
                    self::append_install_log('✓ SEO plugin options backup complete: ' . $seo_backup['filename']);
                    self::append_install_log('========================================');

                    $message = 'SEO plugin options backed up';
                    if (!empty($seo_backup['sitemap_files'])) {
                        $message .= ' (' . $seo_backup['sitemap_files'] . ' sitemap files)';
                    }
                    wp_send_json_success(['message' => $message]);

                case 'db':
                    self::append_install_log('→ Starting database backup (async mode)...');

                    $db_status = get_option('myaio_background_db_status', 'pending');

                    self::append_install_log('  → Current DB status: ' . $db_status);

                    if ($db_status === 'pending') {
                        update_option('myaio_background_backup_run_id', $state['run_id']);
                        update_option('myaio_background_db_status', 'scheduled');
                        update_option('myaio_background_db_progress', 0);
                        delete_option('myaio_background_db_error');
                        delete_option('myaio_background_db_result');
                        delete_option('myaio_s3_url_db');

                        self::append_install_log('  → Set status to: scheduled');
                        self::append_install_log('  → Run ID: ' . $state['run_id']);

                        wp_schedule_single_event(time(), 'myaio_background_db_backup');

                        self::append_install_log('  → Attempting to trigger WP-Cron...');

                        spawn_cron();

                        $cron_url = site_url('wp-cron.php?doing_wp_cron');
                        wp_remote_post($cron_url, [
                            'timeout' => 0.01,
                            'blocking' => false,
                            'sslverify' => apply_filters('myaio_https_local_ssl_verify', false)
                        ]);

                        self::append_install_log('  ✓ Database backup scheduled as background job');
                        self::append_install_log('  → WP-Cron triggered via multiple methods');
                        self::append_install_log('  → Installer will continue while backup runs in background');
                        self::append_install_log('========================================');

                        update_option('myaio_install_step', 'db');
                        wp_send_json_success(['message' => 'Database backup started in background']);
                    } elseif ($db_status === 'completed') {
                        $result = get_option('myaio_background_db_result', []);

                        $file_exists = false;
                        if (!empty($result['path']) && file_exists($result['path'])) {
                            $file_exists = true;
                        }

                        self::append_install_log('  → DB backup marked as completed');
                        self::append_install_log('  → File exists: ' . ($file_exists ? 'Yes' : 'No'));

                        if (!$file_exists) {
                            self::append_install_log('  ⚠ No backup file found, resetting status...');
                            update_option('myaio_background_db_status', 'pending');
                            wp_send_json_success(['message' => 'Retrying database backup', 'continue' => true]);
                        } else {
                            $state['db'] = $result;
                            update_option('myaio_setup_state', $state);

                            $size_mb = !empty($result['bytes']) ? round($result['bytes'] / 1024 / 1024, 2) : 0;
                            self::append_install_log('  ✓ Database backup already completed (' . $size_mb . ' MB)');
                            self::append_install_log('========================================');

                            wp_send_json_success(['message' => 'Database backup completed']);
                        }
                    } elseif ($db_status === 'failed') {
                        $error = get_option('myaio_background_db_error', 'Unknown error');
                        self::append_install_log('  ✗ Database backup failed: ' . $error);
                        self::append_install_log('========================================');

                        wp_send_json_error(['message' => 'Database backup failed: ' . $error]);
                    } else {
                        $progress = (int) get_option('myaio_background_db_progress', 0);
                        self::append_install_log('  ⟳ Database backup in progress (' . $progress . '%)');

                        update_option('myaio_install_step', 'db');
                        wp_send_json_success(['message' => 'Database backup running in background (' . $progress . '%)']);
                    }
                    break;

                case 'files':
                    self::append_install_log('→ Starting files backup (async mode)...');

                    $files_status = get_option('myaio_background_files_status', 'pending');

                    self::append_install_log('  → Current files status: ' . $files_status);

                    if ($files_status === 'pending') {
                        update_option('myaio_background_backup_run_id', $state['run_id']);
                        update_option('myaio_background_files_status', 'scheduled');
                        delete_option('myaio_background_files_error');
                        delete_option('myaio_background_files_result');
                        delete_option('myaio_s3_url_files');

                        self::append_install_log('  → Set status to: scheduled');
                        self::append_install_log('  → Run ID: ' . $state['run_id']);

                        wp_schedule_single_event(time(), 'myaio_background_files_backup');

                        self::append_install_log('  → Attempting to trigger WP-Cron...');

                        spawn_cron();

                        $cron_url = site_url('wp-cron.php?doing_wp_cron');
                        wp_remote_post($cron_url, [
                            'timeout' => 0.01,
                            'blocking' => false,
                            'sslverify' => apply_filters('myaio_https_local_ssl_verify', false)
                        ]);

                        self::append_install_log('  ✓ Files backup scheduled as background job');
                        self::append_install_log('  → WP-Cron triggered via multiple methods');
                        self::append_install_log('  → Installer will continue while backup runs in background');
                        self::append_install_log('========================================');

                        update_option('myaio_install_step', 'files');
                        wp_send_json_success(['message' => 'Files backup started in background']);
                    } elseif ($files_status === 'completed') {
                        $result = get_option('myaio_background_files_result', []);

                        $file_exists = false;
                        if (!empty($result['path']) && file_exists($result['path'])) {
                            $file_exists = true;
                        }

                        self::append_install_log('  → Files backup marked as completed');
                        self::append_install_log('  → File exists: ' . ($file_exists ? 'Yes' : 'No'));

                        if (!$file_exists) {
                            self::append_install_log('  ⚠ No backup file found, resetting status...');
                            update_option('myaio_background_files_status', 'pending');
                            wp_send_json_success(['message' => 'Retrying files backup', 'continue' => true]);
                        } else {
                            $state['files'] = $result;
                            update_option('myaio_setup_state', $state);

                            $size_mb = !empty($result['bytes']) ? round($result['bytes'] / 1024 / 1024, 2) : 0;
                            self::append_install_log('  ✓ Files backup already completed (' . $size_mb . ' MB)');
                            self::append_install_log('========================================');

                            wp_send_json_success(['message' => 'Files backup completed']);
                        }
                    } elseif ($files_status === 'failed') {
                        $error = get_option('myaio_background_files_error', 'Unknown error');
                        self::append_install_log('  ✗ Files backup failed: ' . $error);
                        self::append_install_log('========================================');

                        wp_send_json_error(['message' => 'Files backup failed: ' . $error]);
                    } else {
                        self::append_install_log('  ⟳ Files backup in progress');

                        update_option('myaio_install_step', 'files');
                        wp_send_json_success(['message' => 'Files backup running in background']);
                    }
                    break;

                case 'json':
                    self::append_install_log('→ Starting SEO snapshot generation...');
                    if (empty($state['snapshot'])) {
                        self::append_install_log('  → Building complete SEO + URL inventory...');
                        $run = MYAIO_SEO_Snapshot::run(MYAIO_SNAPSHOTS_PATH, $state['run_id']);
                        $state['snapshot'] = $run;
                        update_option('myaio_setup_state', $state);
                        self::append_install_log('  ✓ Snapshot files generated');
                    } else {
                        self::append_install_log('  ℹ Snapshot already exists (skipping)');
                    }
                    $url_count = !empty($state['snapshot']['url_count']) ? (int) $state['snapshot']['url_count'] : 0;
                    update_option('myaio_install_step', 'json');
                    self::append_install_log('✓ JSON snapshot complete (' . $url_count . ' URLs)');
                    self::append_install_log('========================================');
                    wp_send_json_success(['message' => 'JSON snapshot created (' . $url_count . ' URLs).']);

                case 'deploy':
                    self::append_install_log('→ Starting SEO deployment process...');

                    self::append_install_log('  → Ensuring SEO continuity tables exist...');
                    if ( class_exists('MYAIO_SEO_Continuity') && is_callable(['MYAIO_SEO_Continuity', 'ensure_tables']) ) {
                        MYAIO_SEO_Continuity::ensure_tables();
                        self::append_install_log('  ✓ SEO continuity tables ready');
                    } else {
                        self::append_install_log('  ⚠ SEO continuity class not found (skipping table creation)');
                    }

                    self::append_install_log('  → Migrating SEO data from existing plugins...');
                    $report = MYAIO_SEO_Migrator::migrate_all();
                    self::append_install_log('  ✓ SEO migration complete');
                    self::append_install_log('    - Posts scanned: ' . (int)($report['counts']['posts_scanned'] ?? 0));
                    self::append_install_log('    - Meta fields migrated: ' . (int)($report['counts']['meta_migrated'] ?? 0));
                    self::append_install_log('    - Redirects migrated: ' . (int)($report['counts']['redirects_migrated'] ?? 0));

                    do_action('myaio_deploy_rendering_changes', $state);

                    if (function_exists('flush_rewrite_rules')) {
                        flush_rewrite_rules(false);
                    }

                    $primary_sitemap_url = self::get_primary_sitemap_url();
                    if (!empty($primary_sitemap_url)) {
                        update_option('myaio_primary_sitemap_url', esc_url_raw($primary_sitemap_url));
                    }

                    $state['deploy'] = ['ok' => true, 'ts' => time(), 'report' => $report];
                    update_option('myaio_setup_state', $state);
                    update_option('myaio_seo_migration_report', $report);
                    update_option('myaio_install_step', 'deploy');
                    $msg = 'Deploy step completed. Migrated ' . (int)($report['counts']['meta_migrated'] ?? 0) . ' SEO fields across ' . (int)($report['counts']['posts_scanned'] ?? 0) . ' posts. Redirects migrated: ' . (int)($report['counts']['redirects_migrated'] ?? 0) . '.';
                    self::append_install_log($msg);
                    wp_send_json_success(['message' => $msg]);

                case 'api':
                    $site_url = home_url();

                    $website_id = get_option('myaio_website_id');
                    if (empty($website_id)) {
                        $website_id = wp_generate_uuid4();
                        update_option('myaio_website_id', $website_id);
                    }

                    $pages_count = (int) wp_count_posts('page')->publish;
                    $posts_count = (int) wp_count_posts('post')->publish;
                    $users_count = (int) count_users()['total_users'];
                    $active_plugins = array_values((array) get_option('active_plugins', []));
                    $theme = wp_get_theme()->get_stylesheet();

                    $detected_seo = [];
                    if (!empty($state['snapshot']['snapshot_file']) && file_exists($state['snapshot']['snapshot_file'])) {
                        $snap = json_decode((string) file_get_contents($state['snapshot']['snapshot_file']), true);
                        if (is_array($snap) && !empty($snap['seo_plugins']) && is_array($snap['seo_plugins'])) {
                            $detected_seo = array_keys(array_filter($snap['seo_plugins']));
                        }
                    }
                    $url_count = !empty($state['snapshot']['url_count']) ? (int) $state['snapshot']['url_count'] : 0;

                    $json_contents = wp_json_encode([
                        'pages' => $pages_count,
                        'posts' => $posts_count,
                        'users' => $users_count,
                        'url_count' => $url_count,
                    ]);

                    $json_extracts = wp_json_encode([
                        'plugins' => $active_plugins,
                        'seo_plugins' => $detected_seo,
                        'theme' => $theme,
                        'snapshot' => [
                            'seo_snapshot' => !empty($state['snapshot']['snapshot_file']) ? basename($state['snapshot']['snapshot_file']) : null,
                            'urls' => !empty($state['snapshot']['urls_file']) ? basename($state['snapshot']['urls_file']) : null,
                            'url_meta' => !empty($state['snapshot']['meta_file']) ? basename($state['snapshot']['meta_file']) : null,
                        ],
                    ]);

                    $seo_options_name = !empty($state['seo_options']['filename']) ? $state['seo_options']['filename'] : '';
                    $db_name = !empty($state['db']['name']) ? $state['db']['name'] : '';
                    $files_name = !empty($state['files']['name']) ? $state['files']['name'] : '';

                    $db_bkp_path = $db_name ? ('/backups/db/' . $db_name) : ($seo_options_name ? ('/backups/db/' . $seo_options_name) : '');
                    $file_bkp_path = $files_name ? ('/backups/files/' . $files_name) : ($seo_options_name ? ('/backups/db/sitemaps/') : '');

                    $endpoint = 'https://app.myaio.com/api/website/backup';
                    $payload = [
                        'website_id' => $website_id,
                        'status' => 'active',
                        'website_url' => $site_url,
                        'db_bkp_path' => $db_bkp_path,
                        'file_bkp_path' => $file_bkp_path,
                        'json_contents' => $json_contents,
                        'json_extracts' => $json_extracts,
                    ];

                    $resp = self::remote_post_with_retries($endpoint, [
                        'timeout' => (defined('MYAIO_REQUEST_TIMEOUT') ? (int) MYAIO_REQUEST_TIMEOUT : 900),
                        'headers' => [
                            'Content-Type' => 'application/json; charset=utf-8',
                            'Accept' => 'application/json',
                        ],
                        'body' => wp_json_encode($payload),
                    ], 'API registration');

                    if (is_wp_error($resp)) {
                        update_option('myaio_install_state', 'halted');
                        self::append_install_log('API registration halted after retries: ' . $resp->get_error_message());
                        $max = defined('MYAIO_RETRY_ATTEMPTS') ? (int) MYAIO_RETRY_ATTEMPTS : 4;
                        throw new Exception('Network error while registering the backup. We tried ' . $max . ' times. Please reinstall the plugin after some time (setup was halted).');
                    }

                    $code = (int) wp_remote_retrieve_response_code($resp);
                    if ($code >= 300) {
                        $body = (string) wp_remote_retrieve_body($resp);
                        $body = trim(preg_replace('/\s+/', ' ', $body));
                        if (strlen($body) > 500) {
                            $body = substr($body, 0, 500) . '…';
                        }
                        $detail = $body ? (' Response: ' . $body) : '';

                        if ($code === 0 || $code === 408 || $code === 429 || $code >= 500) {
                            update_option('myaio_install_state', 'halted');
                            $max = defined('MYAIO_RETRY_ATTEMPTS') ? (int) MYAIO_RETRY_ATTEMPTS : 4;
                            self::append_install_log('API registration halted after retries. HTTP ' . $code . '.' . $detail);
                            throw new Exception('Network error while registering the backup. We tried ' . $max . ' times. Please reinstall the plugin after some time (setup was halted).');
                        }

                        throw new Exception('API registration failed. HTTP ' . $code . '.' . $detail);
                    }

                    $body_arr = json_decode((string) wp_remote_retrieve_body($resp), true);
                    if (is_array($body_arr) && !empty($body_arr['data']['id'])) {
                        update_option('myaio_website_id', (string) $body_arr['data']['id']);
                        $website_id = (string) $body_arr['data']['id'];
                    }

                    $state['api'] = ['ok' => true, 'website_id' => $website_id];
                    update_option('myaio_setup_state', $state);
                    update_option('myaio_install_step', 'api');
                    self::append_install_log('API backup registration ok. website_id=' . $website_id);
                    wp_send_json_success(['message' => 'Site registered.']);

                case 'seo_gate':
                    if (!function_exists('is_plugin_active')) {
                        require_once ABSPATH . 'wp-admin/includes/plugin.php';
                    }

                    $seo_plugins = [
                        'wordpress-seo/wp-seo.php' => 'Yoast SEO',
                        'all-in-one-seo-pack/all_in_one_seo_pack.php' => 'All in One SEO',
                        'seo-by-rank-math/rank-math.php' => 'rankmath',
                        'the-seo-framework/the-seo-framework.php' => 'The SEO Framework',
                        'seopress/seopress.php' => 'SEOPress',
                        'aioseo/aioseo.php' => 'AIOSEO',
                    ];

                    $active = [];
                    foreach ($seo_plugins as $plugin_file => $label) {
                        if (is_plugin_active($plugin_file)) {
                            $active[] = $label;
                        }
                    }

                    if (!empty($active)) {
                        $rep = get_option('myaio_seo_migration_report', []);
                        $meta = (int)($rep['counts']['meta_migrated'] ?? 0);
                        $posts = (int)($rep['counts']['posts_scanned'] ?? 0);
                        $reds = (int)($rep['counts']['redirects_migrated'] ?? 0);
                        $providers = !empty($rep['detected']) && is_array($rep['detected']) ? implode(', ', $rep['detected']) : 'unknown';

                        if ($meta < 5 && $reds < 1) {
                            $msg = "We detected active SEO plugins (" . implode(', ', $active) . ") but could not find enough SEO settings/redirects to migrate automatically.

To avoid ranking loss, please KEEP your current SEO plugin installed and contact MYAIO support.

(Detection summary: migrated {$meta} SEO fields across {$posts} posts; redirects migrated: {$reds}; detected sources: {$providers}.)";
                            wp_send_json_error(['message' => $msg], 409);
                        }

                        $msg = "MYAIO deployed SEO continuity so you can safely uninstall the SEO plugins without losing rankings.

Detected active SEO plugins: " . implode(', ', $active) . "

Migration summary: migrated {$meta} SEO fields across {$posts} posts; redirects migrated: {$reds}; detected sources: {$providers}.

Now uninstall the SEO plugin(s) listed above, then click Retry.";
                        wp_send_json_error(['message' => $msg], 422);
                    }
                    update_option('myaio_install_step', 'seo_gate');
                    self::append_install_log('No conflicting SEO plugins detected.');
                    wp_send_json_success(['message' => 'No conflicting SEO plugins detected.']);

                case 'done':
                    update_option('myaio_setup_complete', 1);
                    update_option('myaio_install_state', 'completed');
                    update_option('myaio_install_step', 'done');
                    delete_transient('_myaio_do_installer_redirect');
                    self::append_install_log('Setup complete.');
                    wp_send_json_success(['message' => 'Setup complete.']);

                default:
                    wp_send_json_error(['message' => 'Unknown step.'], 400);
            }
        } catch (Exception $e) {
            wp_send_json_error(['message' => $e->getMessage()], 500);
        }
    }

    public static function dashboard_page() {
        echo '<div class="wrap">';
        echo '<h1>MYAIO Dashboard</h1>';
        echo '<div style="margin-top:20px;">';
        echo '<img src="' . esc_url(MYAIO_SAC_URL . 'assets/images/aio-dashboard.png') . '" alt="MYAIO Dashboard" style="width:100%;max-width:100%;height:auto;display:block;">';
        echo '</div></div>';
    }

    public static function api_key_page() {
        $api_key = get_option( 'myaio_sac_api_key', '' );
        $api_verified = (bool) get_option( 'myaio_sac_api_verified', false );

        $error_flag   = (bool) get_transient( 'myaio_api_key_error' );
        $block_list   = (array) get_transient( 'myaio_seo_block_plugins' );

        if ( $error_flag ) {
            delete_transient( 'myaio_api_key_error' );
        }
        ?>
        <div class="wrap">
            <h1><?php echo esc_html__( 'API Key Validation', 'myaio-your-ai-powered-co-founder' ); ?></h1>
            <form method="post">
                <?php wp_nonce_field( 'myaio_save_api_key', 'myaio_save_api_key_nonce' ); ?>

                <?php if ( $error_flag ) : ?>
                    <div style="color:red;"><?php echo esc_html__( '❌ Invalid API Key or URL', 'myaio-your-ai-powered-co-founder' ); ?></div>
                <?php endif; ?>

                <table class="form-table">
                    <tr>
                        <th scope="row"><label for="myaio_sac_api_key"><?php echo esc_html__( 'API Key', 'myaio-your-ai-powered-co-founder' ); ?></label></th>
                        <td>
                            <input name="myaio_sac_api_key" type="text" id="myaio_sac_api_key" value="<?php echo esc_attr( $api_key ); ?>" class="regular-text" required>
                            <?php if ( $api_verified && ! empty( $api_key ) ) : ?>
                                <span style="color: green; font-weight: bold; margin-left: 10px;"><?php echo esc_html__( '✔ Activated', 'myaio-your-ai-powered-co-founder' ); ?></span>
                            <?php endif; ?>
                        </td>
                    </tr>
                </table>
                <p class="submit">
                    <input type="submit" name="myaio_sac_save_key" id="submit" class="button button-primary" value="<?php echo esc_attr__( 'Validate & Save', 'myaio-your-ai-powered-co-founder' ); ?>">
                </p>
            </form>
        </div>
        <?php
    }

    public static function handle_api_key_submission() {
        if ( isset( $_POST['myaio_sac_save_key'] ) ) {

            if ( ! isset( $_POST['myaio_save_api_key_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['myaio_save_api_key_nonce'] ) ), 'myaio_save_api_key' ) ) {
                set_transient( 'myaio_api_key_error', 1, 30 );
                wp_safe_redirect( admin_url( 'admin.php?page=myaio_api_key' ) );
                exit;
            }

            $key = '';

            if ( isset( $_POST['myaio_sac_save_key'] ) ) {
                if (
                    ! isset( $_POST['myaio_save_api_key_nonce'] ) ||
                    ! wp_verify_nonce(
                        sanitize_text_field( wp_unslash( $_POST['myaio_save_api_key_nonce'] ) ),
                        'myaio_save_api_key'
                    )
                ) {
                    return;
                }

                if ( isset( $_POST['myaio_sac_api_key'] ) ) {
                    $key = sanitize_text_field( wp_unslash( $_POST['myaio_sac_api_key'] ) );
                }
            }
            $site_url = home_url();

            $response = wp_remote_post(
                'https://app.myaio.com/api/projects/status',
                [
                    'timeout' => 20,
                    'body'    => [
                        'website_id' => $key,
                        'url'        => $site_url,
                        'status'     => 'active',
                        'last_step'  => '4',
                    ],
                ]
            );

            if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
                set_transient( 'myaio_api_key_error', 1, 30 );
                wp_safe_redirect( admin_url( 'admin.php?page=myaio_api_key' ) );
                exit;
            }

            $body = json_decode( wp_remote_retrieve_body( $response ), true );
            if ( ! empty( $body['status'] ) && $body['status'] === 'active' ) {
                update_option( 'myaio_sac_api_key', $key );
                update_option( 'myaio_sac_api_verified', true );
                wp_safe_redirect( admin_url( 'admin.php?page=myaio_sac_dashboard' ) );
                exit;
            }

            set_transient( 'myaio_api_key_error', 1, 30 );
            wp_safe_redirect( admin_url( 'admin.php?page=myaio_api_key' ) );
            exit;
        }

        if ( get_transient( '_sac_do_redirect' ) ) {
            delete_transient( '_sac_do_redirect' );
            wp_safe_redirect( admin_url( 'admin.php?page=myaio_api_key' ) );
            exit;
        }
    }

    private static function load_modules() {
        if (!get_option('myaio_sac_api_verified')) return;

        $files = [
            'modules/seo-audit.php',
            'modules/keyword-research.php',
            'modules/backlink-analysis.php',
            'modules/site-map.php',
            'modules/schema-markup.php',
            'modules/robots-manager.php',
            'modules/breadcrumb-schema.php',
            'modules/slow-images.php',
            'modules/seo-summary.php',
            'modules/sac-google-realtime.php',
            'modules/crawler-logger.php',
            'modules/broken-links.php',
            'modules/robots-meta.php',
            'modules/canonical.php',
            'modules/open-graph-data.php',
            'modules/twitter-card-data.php',
            'modules/internal-link-opportunities.php',
            'modules/create-blog-post.php',
            'modules/set-open-graph.php',
            'modules/set-twitter-card.php',
            'modules/insert-gtm-script.php',
            'modules/keyword-frequency.php',
            'modules/seo-fix.php',
            'modules/bin-crunch.php',
            'modules/title-slug-checker.php',
            'modules/menu-structure-check.php',
            'modules/homepage-animation-check.php',
            'modules/ssl-audit-endpoint.php',
            'modules/heading-audit-endpoint.php',
            'modules/create-custom-html-page.php',
            'modules/page-explorer.php',
            'modules/logo-api.php',
            'modules/folder-structure.php',
            'modules/social-links-api.php',
            'modules/amp-check.php',
            'modules/knowledge-graph-check.php',
            'modules/external-links-api.php',
            'modules/crawler-logs.php',
            'modules/add-footer-links.php',
            'modules/page-depth-api.php',
            'modules/site-audit-checks.php',
            'modules/url-slug-api.php',
            'modules/header-footer-api.php',
            'modules/knowledge-graph-signals-api.php',
            'modules/homepage-image-alt-audit-api.php',
            'modules/crawl.php',
            'modules/page-explorer-matrix.php',
            'modules/backlink-explorer.php',
            'includes/myaio-manual-change-tracker.php',
            'modules/redirects.php',
            'modules/schema-scan.php',
            'modules/site-explorer.php',
            'modules/site-foundation-manager.php',
            'modules/redirects-api.php',
            'modules/schema-field-extractor.php',
        ];

        foreach ($files as $rel) {
            $full = MYAIO_SAC_PATH . $rel;
            if (file_exists($full)) {
                require_once $full;
            } else {
                if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { self::append_install_log( 'Missing module: ' . $rel ); }
            }
        }
    }

    /**
     * Background job: Database backup
     */
    public static function background_db_backup() {
        self::append_install_log('→ background_db_backup() function called');

        $run_id = get_option('myaio_background_backup_run_id', '');
        if (empty($run_id)) {
            self::append_install_log('✗ Background DB backup: No run_id found');
            update_option('myaio_background_db_status', 'failed');
            update_option('myaio_background_db_error', 'No run_id found');
            return;
        }

        self::append_install_log('→ Background DB backup started (run_id: ' . $run_id . ')');
        self::append_install_log('  → Backup path: ' . MYAIO_BACKUP_DB_PATH);
        self::append_install_log('  → Directory exists: ' . (is_dir(MYAIO_BACKUP_DB_PATH) ? 'Yes' : 'No'));
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable
        self::append_install_log('  → Directory writable: ' . (is_writable(MYAIO_BACKUP_DB_PATH) ? 'Yes' : 'No'));

        if (!class_exists('MYAIO_DB_Backup_Optimized')) {
            self::append_install_log('✗ MYAIO_DB_Backup_Optimized class not found!');
            self::append_install_log('  → Attempting to load class...');

            $class_file = MYAIO_SAC_PATH . 'includes/backups/class-myaio-db-backup-optimized.php';
            if (file_exists($class_file)) {
                require_once $class_file;
                self::append_install_log('  ✓ Class file loaded');
            } else {
                self::append_install_log('  ✗ Class file not found: ' . $class_file);
                update_option('myaio_background_db_status', 'failed');
                update_option('myaio_background_db_error', 'Backup class file not found');
                return;
            }
        }

        try {
            update_option('myaio_background_db_status', 'in_progress');
            update_option('myaio_background_db_progress', 0);

            self::append_install_log('  → Calling MYAIO_DB_Backup_Optimized::run_quick()...');

            $result = MYAIO_DB_Backup_Optimized::run_quick(
                MYAIO_BACKUP_DB_PATH,
                $run_id
            );

            self::append_install_log('  → Backup method returned: ' . wp_json_encode($result));

            if ($result['complete']) {
                update_option('myaio_background_db_progress', 95);
                self::append_install_log('  → Zipping SQL file...');

                $zip_result = MYAIO_DB_Backup_Optimized::zip_backup($result['path']);

                if ($zip_result['success']) {
                    $result['path'] = $zip_result['zip_path'];
                    $result['name'] = $zip_result['zip_name'];
                    $result['bytes'] = file_exists($zip_result['zip_path']) ? filesize($zip_result['zip_path']) : 0;

                    $size_mb = round($result['bytes'] / 1024 / 1024, 2);
                    self::append_install_log('  ✓ SQL file zipped: ' . $result['name'] . ' (' . $size_mb . ' MB)');
                } else {
                    self::append_install_log('  ⚠ Failed to zip SQL file: ' . $zip_result['message']);
                    self::append_install_log('  → Continuing with unzipped SQL file');
                }

                update_option('myaio_background_db_status', 'completed');
                update_option('myaio_background_db_result', $result);
                update_option('myaio_background_db_progress', 100);

                $size_mb = round($result['bytes'] / 1024 / 1024, 2);
                self::append_install_log('✓ Background DB backup completed: ' . $result['name'] . ' (' . $size_mb . ' MB)');
                self::append_install_log('  → File path: ' . $result['path']);
                self::append_install_log('  → File exists: ' . (file_exists($result['path']) ? 'Yes' : 'No'));
            } else {
                update_option('myaio_background_db_progress', $result['progress']);
                update_option('myaio_background_db_result', $result);
                wp_schedule_single_event(time() + 5, 'myaio_background_db_backup');

                $msg = !empty($result['message']) ? $result['message'] : ($result['progress'] . '%');
                self::append_install_log('⟳ Background DB backup progress: ' . $msg);
            }
        } catch (Exception $e) {
            self::append_install_log('✗ Quick mode failed: ' . $e->getMessage());
            self::append_install_log('  → Error file: ' . $e->getFile() . ':' . $e->getLine());

            try {
                self::append_install_log('→ Switching to safe backup mode...');
                $result = MYAIO_DB_Backup_Optimized::run_safe(
                    MYAIO_BACKUP_DB_PATH,
                    $run_id
                );

                if ($result['complete']) {
                    update_option('myaio_background_db_progress', 95);
                    self::append_install_log('  → Zipping SQL file...');

                    $zip_result = MYAIO_DB_Backup_Optimized::zip_backup($result['path']);

                    if ($zip_result['success']) {
                        $result['path'] = $zip_result['zip_path'];
                        $result['name'] = $zip_result['zip_name'];
                        $result['bytes'] = file_exists($zip_result['zip_path']) ? filesize($zip_result['zip_path']) : 0;

                        self::append_install_log('  ✓ SQL file zipped (safe mode)');
                    } else {
                        self::append_install_log('  ⚠ Failed to zip SQL file: ' . $zip_result['message']);
                    }

                    update_option('myaio_background_db_status', 'completed');
                    update_option('myaio_background_db_result', $result);
                    update_option('myaio_background_db_progress', 100);
                    self::append_install_log('✓ Background DB backup completed (safe mode)');
                } else {
                    update_option('myaio_background_db_progress', $result['progress']);
                    update_option('myaio_background_db_result', $result);
                    wp_schedule_single_event(time() + 5, 'myaio_background_db_backup');

                    $msg = !empty($result['message']) ? $result['message'] : ($result['progress'] . '%');
                    self::append_install_log('⟳ Background DB backup progress (safe mode): ' . $msg);
                }
            } catch (Exception $e2) {
                update_option('myaio_background_db_status', 'failed');
                update_option('myaio_background_db_error', $e2->getMessage());
                self::append_install_log('✗ Background DB backup failed: ' . $e2->getMessage());
                self::append_install_log('  → Error file: ' . $e2->getFile() . ':' . $e2->getLine());
            }
        }
    }

    /**
     * Background job: Files backup
     */
    public static function background_files_backup() {
        self::append_install_log('→ background_files_backup() function called');

        $run_id = get_option('myaio_background_backup_run_id', '');
        if (empty($run_id)) {
            self::append_install_log('✗ Background files backup: No run_id found');
            update_option('myaio_background_files_status', 'failed');
            update_option('myaio_background_files_error', 'No run_id found');
            return;
        }

        self::append_install_log('→ Background files backup started (run_id: ' . $run_id . ')');

        if (!class_exists('MYAIO_Files_Backup')) {
            self::append_install_log('✗ MYAIO_Files_Backup class not found!');
            self::append_install_log('  → Attempting to load class...');

            $class_file = MYAIO_SAC_PATH . 'includes/backups/class-myaio-files-backup.php';
            if (file_exists($class_file)) {
                require_once $class_file;
                self::append_install_log('  ✓ Class file loaded');
            } else {
                self::append_install_log('  ✗ Class file not found: ' . $class_file);
                update_option('myaio_background_files_status', 'failed');
                update_option('myaio_background_files_error', 'Backup class file not found');
                return;
            }
        }

        try {
            update_option('myaio_background_files_status', 'in_progress');

            $result = MYAIO_Files_Backup::run(MYAIO_BACKUP_FILES_PATH, $run_id);

            update_option('myaio_background_files_status', 'completed');
            update_option('myaio_background_files_result', $result);

            $size_mb = round($result['bytes'] / 1024 / 1024, 2);
            self::append_install_log('✓ Background files backup completed: ' . $result['name'] . ' (' . $size_mb . ' MB)');
        } catch (Exception $e) {
            update_option('myaio_background_files_status', 'failed');
            update_option('myaio_background_files_error', $e->getMessage());
            self::append_install_log('✗ Background files backup failed: ' . $e->getMessage());
        }
    }

    /**
     * AJAX endpoint: Upload backup to S3 and delete local file
     */
    public static function ajax_upload_backup_to_s3() {
        try {
            if (!current_user_can('manage_options')) {
                wp_send_json_error(['message' => 'Insufficient permissions.'], 403);
                return;
            }

            check_ajax_referer('myaio_setup');

            $backup_type = isset($_POST['backup_type']) ? sanitize_key(wp_unslash($_POST['backup_type'])) : '';
            if (!in_array($backup_type, ['db', 'files'], true)) {
                wp_send_json_error(['message' => 'Invalid backup type.']);
                return;
            }

            self::append_install_log('→ Starting S3 upload for ' . $backup_type . ' backup...');

            $result_option = 'myaio_background_' . $backup_type . '_result';
            $result = get_option($result_option, []);

            if (empty($result['path']) || !file_exists($result['path'])) {
                throw new Exception('Backup file not found');
            }

            $file_path = $result['path'];
            $file_name = basename($file_path);
            $file_size = filesize($file_path);

            self::append_install_log('  → File: ' . $file_name . ' (' . round($file_size / 1024 / 1024, 2) . ' MB)');

            $api_base_url = get_option('myaio_api_base_url', 'https://app.myaio.com/api');
            $upload_url = trailingslashit($api_base_url) . 'upload-file';

            self::append_install_log('  → Upload URL: ' . $upload_url);

            if (!function_exists('curl_init')) {
                throw new Exception('cURL is not available on this server');
            }

            if (!class_exists('CURLFile')) {
                throw new Exception('CURLFile class not available');
            }

            $website_id = get_option('myaio_website_id', '');

            $post_fields = [
                'backup_type' => $backup_type,
                'website_id' => $website_id,
                'file' => new CURLFile($file_path, 'application/octet-stream', $file_name),
            ];

            self::append_install_log('  → Uploading via cURL...');

            $ssl_verify = (bool) apply_filters( 'myaio_s3_upload_sslverify', true );
			// phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_init
            $ch = curl_init();
			// phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
            curl_setopt( $ch, CURLOPT_URL, $upload_url );
			// phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
            curl_setopt( $ch, CURLOPT_POST, true );
			// phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
            curl_setopt( $ch, CURLOPT_POSTFIELDS, $post_fields );
			// phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
            curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
			// phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
            curl_setopt( $ch, CURLOPT_TIMEOUT, 300 );
			// phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
            curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, $ssl_verify ? 1 : 0 );
			// phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt
            curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
			// phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_exec
            $response_body = curl_exec( $ch );
			// phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_getinfo
            $status_code = (int) curl_getinfo( $ch, CURLINFO_HTTP_CODE );
			// phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_error
            $curl_error = (string) curl_error( $ch );
			// phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_close
            curl_close( $ch );

            if ($curl_error) {
                throw new Exception('cURL error: ' . $curl_error);
            }

            self::append_install_log('  → Response code: ' . $status_code);

            if ($status_code !== 200 && $status_code !== 201) {
                $error_preview = is_string($response_body) ? substr($response_body, 0, 200) : '';
                throw new Exception('Upload failed with status ' . $status_code . ': ' . $error_preview);
            }

            $response_data = json_decode($response_body, true);
            $s3_url = '';

            if (json_last_error() !== JSON_ERROR_NONE) {
                if ($status_code === 200 || $status_code === 201) {
                    self::append_install_log('  → Upload successful (non-JSON response)');
                } else {
                    throw new Exception('Invalid JSON response');
                }
            } else {
                self::append_install_log('  → Upload successful');

                if (isset($response_data['url']) && !empty($response_data['url'])) {
                    $s3_url = $response_data['url'];
                } elseif (isset($response_data['file_url']) && !empty($response_data['file_url'])) {
                    $s3_url = $response_data['file_url'];
                } elseif (isset($response_data['data']['url']) && !empty($response_data['data']['url'])) {
                    $s3_url = $response_data['data']['url'];
                } elseif (isset($response_data['data']['file_url']) && !empty($response_data['data']['file_url'])) {
                    $s3_url = $response_data['data']['file_url'];
                }

                if ($s3_url) {
                    self::append_install_log('  → S3 URL: ' . $s3_url);
                    update_option('myaio_s3_url_' . $backup_type, $s3_url);
                }
            }

            self::append_install_log('  → Deleting local backup file...');

            if (wp_delete_file($file_path)) {
                self::append_install_log('  ✓ Local file deleted');
            } else {
                self::append_install_log('  ⚠ Failed to delete local file');
            }

            $message = ucfirst($backup_type) . ' backup uploaded to S3 and local file deleted';
            self::append_install_log('✓ ' . $message);

            wp_send_json_success(['message' => $message, 's3_url' => $s3_url]);

        } catch (Exception $e) {
            $error_msg = 'S3 upload failed: ' . $e->getMessage();
            self::append_install_log('✗ ' . $error_msg);
            wp_send_json_error(['message' => $error_msg]);
        } catch (Throwable $e) {
            $error_msg = 'Critical error: ' . $e->getMessage();
            self::append_install_log('✗ ' . $error_msg);
            wp_send_json_error(['message' => $error_msg]);
        }
    }

    /**
     * AJAX endpoint: Toggle S3 upload enabled/disabled
     */
    public static function ajax_toggle_s3_upload() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Insufficient permissions.'], 403);
            return;
        }

        check_ajax_referer('myaio_setup');

        $current = (bool) get_option('myaio_s3_upload_enabled', false);
        $new_value = !$current;

        update_option('myaio_s3_upload_enabled', $new_value);

        $status = $new_value ? 'enabled' : 'disabled';
        self::append_install_log('S3 upload ' . $status);

        wp_send_json_success([
            'enabled' => $new_value,
            'message' => 'S3 upload ' . $status
        ]);
    }

    /**
     * AJAX endpoint: Check S3 upload status
     */
    public static function ajax_check_s3_status() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Insufficient permissions.'], 403);
            return;
        }

        check_ajax_referer('myaio_setup');

        $enabled = (bool) get_option('myaio_s3_upload_enabled', false);

        wp_send_json_success([
            'enabled' => $enabled
        ]);
    }

    /**
     * AJAX endpoint: Manually trigger background backup
     */
    public static function ajax_trigger_backup_manually() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Insufficient permissions.'], 403);
        }
        check_ajax_referer('myaio_setup');

        $type = isset($_POST['backup_type']) ? sanitize_key(wp_unslash($_POST['backup_type'])) : 'db';

        self::append_install_log('→ Manual backup trigger requested: ' . $type);

        if ($type === 'db') {
            $next_run = wp_next_scheduled('myaio_background_db_backup');
			// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
            self::append_install_log('  → Next scheduled cron: ' . ($next_run ? date('Y-m-d H:i:s', $next_run) : 'Not scheduled'));

            self::append_install_log('  → Triggering background_db_backup() directly...');
            self::background_db_backup();

            wp_send_json_success(['message' => 'DB backup triggered manually. Check logs.']);
        } elseif ($type === 'files') {
            $next_run = wp_next_scheduled('myaio_background_files_backup');
			// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
            self::append_install_log('  → Next scheduled cron: ' . ($next_run ? date('Y-m-d H:i:s', $next_run) : 'Not scheduled'));

            self::append_install_log('  → Triggering background_files_backup() directly...');
            self::background_files_backup();

            wp_send_json_success(['message' => 'Files backup triggered manually. Check logs.']);
        } else {
            wp_send_json_error(['message' => 'Invalid backup type.']);
        }
    }

    /**
     * AJAX endpoint: List all backup files
     */
    public static function ajax_list_backup_files() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Insufficient permissions.'], 403);
        }
        check_ajax_referer('myaio_setup');

        $backup_info = [
            'storage_path' => MYAIO_STORAGE_PATH,
            'db_path' => MYAIO_BACKUP_DB_PATH,
            'files_path' => MYAIO_BACKUP_FILES_PATH,
            'sitemaps_path' => MYAIO_BACKUP_SITEMAPS_PATH,
            'db_exists' => is_dir(MYAIO_BACKUP_DB_PATH),
            'files_exists' => is_dir(MYAIO_BACKUP_FILES_PATH),
            'db_files' => [],
            'files_files' => [],
            'seo_files' => [],
        ];

        $db_s3_url = get_option('myaio_s3_url_db', '');
        $files_s3_url = get_option('myaio_s3_url_files', '');

        $s3_urls = [];
        if (!empty($db_s3_url)) {
            $s3_urls['db'] = esc_url_raw($db_s3_url);
        }
        if (!empty($files_s3_url)) {
            $s3_urls['files'] = esc_url_raw($files_s3_url);
        }

        if (!empty($s3_urls)) {
            $backup_info['s3_urls'] = $s3_urls;
        }

        if (is_dir(MYAIO_BACKUP_DB_PATH)) {
            $db_files = glob(MYAIO_BACKUP_DB_PATH . '*');
            if (is_array($db_files)) {
                foreach ($db_files as $file) {
                    if (is_file($file)) {
                        $backup_info['db_files'][] = [
                            'name' => basename($file),
                            'size' => filesize($file),
                            'size_mb' => round(filesize($file) / 1024 / 1024, 2),
							// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
                            'modified' => date('Y-m-d H:i:s', filemtime($file)),
                        ];
                    }
                }
            }
        }

        if (is_dir(MYAIO_BACKUP_FILES_PATH)) {
            $files_files = glob(MYAIO_BACKUP_FILES_PATH . '*');
            if (is_array($files_files)) {
                foreach ($files_files as $file) {
                    if (is_file($file)) {
                        $backup_info['files_files'][] = [
                            'name' => basename($file),
                            'size' => filesize($file),
                            'size_mb' => round(filesize($file) / 1024 / 1024, 2),
							// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
                            'modified' => date('Y-m-d H:i:s', filemtime($file)),
                        ];
                    }
                }
            }
        }

        if (is_dir(MYAIO_BACKUP_DB_PATH)) {
            $seo_files = glob(MYAIO_BACKUP_DB_PATH . 'seo_options_*.json');
            if (is_array($seo_files)) {
                foreach ($seo_files as $file) {
                    if (is_file($file)) {
                        $backup_info['seo_files'][] = [
                            'name' => basename($file),
                            'size' => filesize($file),
                            'size_kb' => round(filesize($file) / 1024, 2),
							// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
                            'modified' => date('Y-m-d H:i:s', filemtime($file)),
                        ];
                    }
                }
            }
        }

        wp_send_json_success($backup_info);
    }

    /**
     * AJAX endpoint: Check background backup status
     */
    public static function ajax_check_backup_status() {
        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => 'Insufficient permissions.'], 403);
        }
        check_ajax_referer('myaio_setup');

        $db_status = get_option('myaio_background_db_status', 'pending');
        $db_progress = (int) get_option('myaio_background_db_progress', 0);
        $db_error = get_option('myaio_background_db_error', '');
        $db_result = get_option('myaio_background_db_result', []);
        $db_message = !empty($db_result['message']) ? $db_result['message'] : '';

        $files_status = get_option('myaio_background_files_status', 'pending');
        $files_error = get_option('myaio_background_files_error', '');

        wp_send_json_success([
            'db' => [
                'status' => $db_status,
                'progress' => $db_progress,
                'error' => $db_error,
                'message' => $db_message,
            ],
            'files' => [
                'status' => $files_status,
                'error' => $files_error,
            ],
        ]);
    }
}

// ✅ Global: Crawler Tracker Output (footer image)
// function myaio_sac_add_crawler_tracker() {
//     $beacon_url = rest_url('myaio/v1/crawler-beacon');
//     printf(
//         '<img src="%s" style="display:none;width:1px;height:1px;" alt="crawler-beacon">',
//         esc_url($beacon_url)
//     );
// }

// ✅ Optional placeholders if endpoints are API-only
function myaio_sac_audit_page() {
    echo wp_kses_post( '<div class="wrap"><h2>SEO Audit</h2><p>This feature is available via API.</p></div>' );
}
function myaio_sac_keywords_page() {
    echo wp_kses_post( '<div class="wrap"><h2>Keyword Research</h2><p>This feature is available via API.</p></div>' );
}
function myaio_sac_backlinks_page() {
    echo wp_kses_post( '<div class="wrap"><h2>Backlink Analysis</h2><p>This feature is available via API.</p></div>' );
}

// Inject into <head>
add_action('wp_head', function () {
    $script = get_option('sac_gtm_header_script');
    if (!empty($script)) {
        echo "\n<!-- GTM Header Script -->\n" . wp_kses_post($script) . "\n";
    }
});

// Inject into footer
add_action('wp_footer', function () {
    $script = get_option('sac_gtm_footer_script');
    if (!empty($script)) {
        echo "\n<!-- GTM Footer Script -->\n" . wp_kses_post($script) . "\n";
    }
});

add_filter('template_include', function ($template) {
    if (is_page()) {
        global $post;
        $html = get_post_meta($post->ID, '_custom_raw_html', true);
        if (!empty($html)) {
            status_header(200);
            echo wp_kses_post($html);
            exit;
        }
    }
    return $template;
}, 10, 1);

/**
 * Force robots.txt to output the actual MYAIO sitemap URL.
 */
add_filter('robots_txt', function ($output, $public) {
    $sitemap_url = get_option('myaio_primary_sitemap_url', '');

    if (empty($sitemap_url)) {
        $preferred_files = array(
            'sitemap_index.xml',
            'sitemap.xml',
            'post-sitemap.xml',
            'page-sitemap.xml',
            'category-sitemap.xml',
            'post_tag-sitemap.xml',
            'author-sitemap.xml',
            'news-sitemap.xml',
            'video-sitemap.xml',
            'image-sitemap.xml',
            'wp-sitemap.xml',
        );

        foreach ($preferred_files as $file) {
            $path = ABSPATH . $file;
            if (file_exists($path) && is_file($path)) {
                $sitemap_url = home_url('/' . $file);
                update_option('myaio_primary_sitemap_url', esc_url_raw($sitemap_url));
                break;
            }
        }
    }

    if (!empty($sitemap_url)) {
        $output = preg_replace('/^\s*Sitemap:\s+.*$/mi', '', (string) $output);
        $output = trim($output);

        if ($output !== '') {
            $output .= "\n\n";
        }

        $output .= 'Sitemap: ' . esc_url_raw($sitemap_url) . "\n";
    }

    return $output;
}, 999, 2);