<?php

/**
 * Main Plugin Class
 *
 * @package ProRank\SEO
 */

declare(strict_types=1);

namespace ProRank\SEO;

use ProRank\SEO\Core\ServiceContainer;
use ProRank\SEO\Core\Config\DomainConfig;
use ProRank\SEO\Core\SettingsManager;
use ProRank\SEO\Core\LicenseManager;
use ProRank\SEO\Core\NotificationManager;
use ProRank\SEO\Core\ModuleManager;
use ProRank\SEO\Core\ModuleLoader;
use ProRank\SEO\Core\AdminMenus;
use ProRank\SEO\Core\PresetManager;
use ProRank\SEO\Core\SettingsSnapshot;
use ProRank\SEO\Core\Compatibility\PerformanceCompatibilityRegistry;
use ProRank\SEO\Core\Diagnostics\GoogleTagConflictDetector;
use ProRank\SEO\Core\Buffer;

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// Import REST API Controllers
use ProRank\SEO\Core\RestApi\DashboardController;
use ProRank\SEO\Core\RestApi\ModulesController;
use ProRank\SEO\Core\RestApi\UserPreferencesController;
use ProRank\SEO\Core\RestApi\NotificationsFeedController;
use ProRank\SEO\Core\Notifications\NotificationsFeed;
use ProRank\SEO\Core\RestApi\SettingsController;
use ProRank\SEO\Core\RestApi\AdvancedSettingsController;
use ProRank\SEO\Core\RestApi\DataController;
use ProRank\SEO\Core\RestApi\FeedbackController;
use ProRank\SEO\Core\RestApi\MetaController;
use ProRank\SEO\Core\RestApi\SchemaAggregationController;
use ProRank\SEO\Core\RestApi\AskController;
use ProRank\SEO\Core\RestApi\McpController;
use ProRank\SEO\Core\RestApi\OnboardingController;
use ProRank\SEO\Core\RestApi\SitemapsController;
use ProRank\SEO\Core\RestApi\HtmlSitemapController;
use ProRank\SEO\Core\RestApi\ReadabilityController;
use ProRank\SEO\Core\RestApi\HeadlineAnalyzerController;
use ProRank\SEO\Core\RestApi\LinkingController;
use ProRank\SEO\Core\RestApi\RedirectsController;
use ProRank\SEO\Core\RestApi\RedirectSettingsController;
use ProRank\SEO\Core\RestApi\RobotsIndexingController;
use ProRank\SEO\Core\RestApi\HeadCleanupController;
use ProRank\SEO\Core\RestApi\UrlCleanupController;
use ProRank\SEO\Core\RestApi\ImportExportController;
use ProRank\SEO\Core\RestApi\EnhancedAuditController;
use ProRank\SEO\Core\RestApi\CommandPaletteController;
use ProRank\SEO\Core\RestApi\PresetsController;
use ProRank\SEO\Core\RestApi\CacheController;
use ProRank\SEO\Core\RestApi\SettingsSnapshotController;
use ProRank\SEO\Core\RestApi\ImageOptimizationController;
use ProRank\SEO\Core\RestApi\ImageOptimizationSettingsController;
use ProRank\SEO\Core\RestApi\ImagesController;
use ProRank\SEO\Core\RestApi\FontOptimizationController;
use ProRank\SEO\Core\RestApi\DatabaseOptimizationController;
use ProRank\SEO\Core\RestApi\SystemController;
use ProRank\SEO\Core\RestApi\CssOptimizationController;
use ProRank\SEO\Core\RestApi\CwvDataController;
use ProRank\SEO\Core\RestApi\PerformanceSettingsController;
use ProRank\SEO\Core\RestApi\WizardStateController;
use ProRank\SEO\Core\RestApi\CdnController;
use ProRank\SEO\Core\RestApi\CdnSetupController;
use ProRank\SEO\Core\RestApi\VersionController;
use ProRank\SEO\Frontend\HeadOutput;
use ProRank\SEO\Frontend\SiteBasicsOutput;
use ProRank\SEO\Frontend\IndexNowVirtualFile;
use ProRank\SEO\Frontend\SpeculationRules;
use ProRank\SEO\Admin\BulkEditIntegration;
use ProRank\SEO\Services\Cdn\CdnManager;
use ProRank\SEO\Frontend\ThirdPartyDefer;
use ProRank\SEO\Frontend\LazyMedia;
use ProRank\SEO\Frontend\RumBeacon;

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}


/**
 * Main Plugin Class
 */


class Plugin {
    
    /**
     * Plugin version
     *
     * @var string
     */
    const VERSION = '1.1.1';
    
    /**
     * Plugin instance
     *
     * @var Plugin|null
     */
    private static ?Plugin $instance = null;
    
    /**
     * Service container
     *
     * @var ServiceContainer
     */
    private ServiceContainer $services;

    /**
     * Handles whose inline `after` payload references jQuery and therefore
     * require execution-contract repair later in the request.
     *
     * @var array<int, string>
     */
    private array $inline_script_contract_handles = [];

    /**
     * Request-local cache for stable builder critical CSS detection.
     *
     * @var bool|null
     */
    private ?bool $builder_critical_css_ready = null;

    /**
     * Request-local cache for stronger base CSS ownership detection.
     *
     * This gate is intentionally stricter than the general builder-critical
     * CSS check. It is only used before moving the main theme/builder base
     * stylesheets off the blocking path.
     *
     * @var bool|null
     */
    private ?bool $builder_base_css_ownership_ready = null;

    /**
     * Whether jquery-core should be suppressed in the head and re-emitted
     * before footer scripts for the current request.
     *
     * @var bool
     */
    private bool $relocate_jquery_core_to_footer = false;
    
    /**
     * Get plugin instance
     *
     * @return Plugin
     */
    public static function get_instance(): Plugin {
        static $creating = false;
        
        if ( null === self::$instance ) {
            if ($creating) {
                // Prevent recursive instantiation
                prorank_log('[ProRank SEO] ERROR: Recursive Plugin instantiation detected!');
                throw new \RuntimeException('Recursive Plugin instantiation detected');
            }
            
            $creating = true;
            self::$instance = new self();
            $creating = false;
        }
        
        return self::$instance;
    }

    /**
     * Handle content changes to trigger cache/CDN purge
     */
    public function handle_content_change( $post_id, $post, $update ): void {
        if ( self::is_elementor_builder_save_request() ) {
            return;
        }

        if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
            return;
        }
        $this->purge_urls(
            array_merge(
                $this->collect_urls_for_post( $post_id ),
                $this->collect_global_urls()
            )
        );
    }

    /**
     * Handle post deletion
     */
    public function handle_content_delete( $post_id ): void {
        $post_id = (int) $post_id;
        if ( $post_id <= 0 ) {
            return;
        }
        $this->purge_urls(
            array_merge(
                $this->collect_urls_for_post( $post_id ),
                $this->collect_global_urls()
            )
        );
    }

    /**
     * Handle status transitions (e.g., draft -> publish)
     */
    public function handle_post_status_transition( $new_status, $old_status, $post ): void {
        if ( self::is_elementor_builder_save_request() ) {
            return;
        }

        if ( $new_status === $old_status ) {
            return;
        }
        $this->purge_urls(
            array_merge(
                $this->collect_urls_for_post( $post->ID ),
                $this->collect_global_urls()
            )
        );
    }

    /**
     * Handle menu updates
     */
    public function handle_menu_change( $nav_menu_id ): void {
        $this->purge_urls( $this->collect_global_urls() );
    }

    /**
     * Detect Elementor builder save AJAX requests.
     *
     * Elementor persists page changes through admin-ajax `save_builder`. ProRank
     * should not run heavy save_post maintenance hooks inside that request.
     *
     * @return bool
     */
    public static function is_elementor_builder_save_request(): bool {
        if ( ! wp_doing_ajax() ) {
            return false;
        }

        $ajax_action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( (string) $_REQUEST['action'] ) ) : '';
        if ( $ajax_action !== 'elementor_ajax' ) {
            return false;
        }

        $actions = $_REQUEST['actions'] ?? '';
        if ( is_array( $actions ) ) {
            $actions = wp_json_encode( $actions );
        }

        if ( ! is_string( $actions ) || $actions === '' ) {
            return false;
        }

        return str_contains( wp_unslash( $actions ), 'save_builder' );
    }

    /**
     * Collect URLs related to a post for purging
     */
    private function collect_urls_for_post( int $post_id ): array {
        $urls = [];
        $permalink = get_permalink( $post_id );
        if ( $permalink ) {
            $urls[] = $permalink;
        }
        return array_filter( array_unique( $urls ) );
    }

    /**
     * Collect sitewide URLs that should be purged on structural changes
     */
    private function collect_global_urls(): array {
        $urls = [];
        if ( function_exists( 'home_url' ) ) {
            $urls[] = home_url( '/' );
            $urls[] = home_url( '/feed/' );
            $urls[] = home_url( '/sitemap.xml' );
            $urls[] = home_url( '/sitemap_index.xml' );
            $urls[] = home_url( '/robots.txt' );
        }

        // get_feed_link() dereferences $wp_rewrite->get_feed_permastruct(),
        // which fatals when rewrite globals aren't bootstrapped yet — e.g.
        // when code runs wp_insert_post() early on plugins_loaded, before
        // WP has populated the $wp_rewrite singleton. Only call it when the
        // global is a real WP_Rewrite instance; otherwise rely on the
        // home_url('/feed/') entry already appended above.
        if (
            function_exists( 'get_feed_link' )
            && isset( $GLOBALS['wp_rewrite'] )
            && $GLOBALS['wp_rewrite'] instanceof \WP_Rewrite
        ) {
            $feed_link = get_feed_link();
            if ( $feed_link ) {
                $urls[] = $feed_link;
            }
        }

        return array_filter( array_unique( $urls ) );
    }

    /**
     * Purge URLs from CDN if configured
     */
    private function purge_urls( array $urls ): void {
        if ( empty( $urls ) ) {
            return;
        }
        $urls = array_unique( array_filter( $urls ) );
        $this->purge_supported_host_caches( $urls );
        try {
            $cdn = new CdnManager();
            $result = $cdn->purge_urls( $urls );
            if ( is_wp_error( $result ) ) {
                prorank_log( 'ProRank CDN purge failed: ' . $result->get_error_message() );
            } else {
                do_action( 'prorank_cdn_cache_cleared', get_option( 'prorank_cdn_provider', 'none' ), $urls );
                do_action( 'prorank_audit_log_event', 'cdn_purge', [ 'urls' => $urls, 'provider' => get_option( 'prorank_cdn_provider', 'none' ) ] );
            }
        } catch ( \Throwable $e ) {
            prorank_log( 'ProRank CDN purge exception: ' . $e->getMessage() );
        }
    }

    /**
     * Best-effort LiteSpeed Cache purge when LSCache is active.
     *
     * @param array $urls URLs to purge.
     */
    private function purge_litespeed_cache( array $urls ): void {
        if ( empty( $urls ) ) {
            return;
        }
        if ( ! defined( 'LSCWP_V' ) && ! class_exists( 'LiteSpeed_Cache_API' ) ) {
            return;
        }

        if ( class_exists( 'LiteSpeed_Cache_API' ) ) {
            if ( method_exists( 'LiteSpeed_Cache_API', 'purge_url' ) ) {
                foreach ( $urls as $url ) {
                    \LiteSpeed_Cache_API::purge_url( $url );
                }
                return;
            }
            if ( method_exists( 'LiteSpeed_Cache_API', 'purge' ) ) {
                foreach ( $urls as $url ) {
                    \LiteSpeed_Cache_API::purge( $url );
                }
                return;
            }
            if ( method_exists( 'LiteSpeed_Cache_API', 'purge_all' ) ) {
                \LiteSpeed_Cache_API::purge_all();
                return;
            }
        }

        if ( has_action( 'litespeed_purge_all' ) ) {
            do_action( 'litespeed_purge_all' );
            return;
        }
        if ( has_action( 'litespeed_purge_url' ) ) {
            foreach ( $urls as $url ) {
                do_action( 'litespeed_purge_url', $url );
            }
            return;
        }
        if ( has_action( 'litespeed_purge' ) ) {
            foreach ( $urls as $url ) {
                do_action( 'litespeed_purge', $url );
            }
        }
    }

    /**
     * Purge supported host/proxy caches for the provided URLs.
     *
     * @param array $urls URLs to purge.
     * @return void
     */
    private function purge_supported_host_caches( array $urls ): void {
        if ( empty( $urls ) ) {
            return;
        }

        $urls = array_unique( array_filter( $urls ) );
        $this->purge_litespeed_cache( $urls );
        $this->purge_siteground_cache( $urls );
    }

    /**
     * Best-effort SiteGround Optimizer proxy cache purge.
     *
     * SiteGround can continue serving stale HTML even after ProRank has disabled
     * its own runtime mutations. Purging it here prevents old broken HTML from
     * lingering on plain URLs after a performance rollback or settings change.
     *
     * @param array $urls URLs to purge when a URL-level API is available.
     * @return void
     */
    private function purge_siteground_cache( array $urls ): void {
        if ( empty( $urls ) ) {
            return;
        }

        $class = '\SiteGround_Optimizer\Supercacher\Supercacher';

        if ( ! class_exists( $class ) ) {
            $bootstrap = defined( 'WP_PLUGIN_DIR' )
                ? trailingslashit( WP_PLUGIN_DIR ) . 'sg-cachepress/sg-cachepress.php'
                : '';

            if ( ! $bootstrap || ! file_exists( $bootstrap ) ) {
                return;
            }

            try {
                require_once $bootstrap;
            } catch ( \Throwable $e ) {
                prorank_log( '[ProRank SEO] Failed to load SiteGround Optimizer for cache purge: ' . $e->getMessage() );
                return;
            }
        }

        if ( ! class_exists( $class ) ) {
            return;
        }

        try {
            if ( method_exists( $class, 'purge_cache_request' ) ) {
                $method = new \ReflectionMethod( $class, 'purge_cache_request' );
                $parameter_count = $method->getNumberOfParameters();
                if ( $parameter_count === 0 ) {
                    $class::purge_cache_request();
                    return;
                }
                if ( $parameter_count === 1 ) {
                    foreach ( $urls as $url ) {
                        $class::purge_cache_request( $url );
                    }
                    return;
                }
            }

            if ( method_exists( $class, 'purge_cache' ) ) {
                $class::purge_cache();
            }
        } catch ( \Throwable $e ) {
            prorank_log( '[ProRank SEO] SiteGround cache purge failed: ' . $e->getMessage() );
        }
    }
    
    /**
     * Constructor
     */
    private function __construct() {
        // Initialize domain configuration first
        DomainConfig::init();
        
        $this->services = new ServiceContainer();
        
        $this->init_services();
        $this->init_hooks();
        
        // Security validation will be done after plugins_loaded to prevent circular dependency
    }
    
    /**
     * Initialize services
     */
    private function init_services(): void {
        // Initialize core services in dependency order
        $this->services->register('settings', fn() => new SettingsManager());
        $this->services->register('notifications', fn() => new NotificationManager());
        $this->services->register('license', fn() => new LicenseManager());
        $this->services->register('modules', fn() => new ModuleManager());
        $this->services->register('menus', fn() => new \ProRank\SEO\Core\MenuRegistry());
        $this->services->register('presets', function() {
            return new PresetManager($this->settings());
        });
        $this->services->register('snapshots', function() {
            return new SettingsSnapshot($this->settings());
        });
        
        // Module loading will be done after plugin is fully initialized
    }
    
    /**
     * Initialize Action Scheduler
     */
    private function init_action_scheduler(): void {
        // Load Action Scheduler if not already loaded
        $action_scheduler_path = PRORANK_PLUGIN_DIR . 'vendor/woocommerce/action-scheduler/action-scheduler.php';
        if ( file_exists( $action_scheduler_path ) && ! class_exists( 'ActionScheduler' ) ) {
            require_once $action_scheduler_path;
        }
    }
    
    /**
     * Initialize hooks
     */
    private function init_hooks(): void {
        // Initialize Action Scheduler
        $this->init_action_scheduler();

        // Apply performance conflict safe mode before module bootstrap.
        add_action( 'plugins_loaded', [ $this, 'maybe_apply_performance_conflict_safe_mode' ], 4 );
        add_action( 'plugins_loaded', [ $this, 'apply_performance_engine_safeguards' ], 4 );
        
        // Load modules very early
        add_action( 'plugins_loaded', [ $this, 'load_modules' ], 5 );
        // Ensure background image optimization actions always have a callback
        add_action( 'plugins_loaded', [ $this, 'register_image_optimization_actions' ], 20 );
        // Ensure local Site Audit cron actions always have callbacks.
        add_action( 'plugins_loaded', [ $this, 'register_site_audit_actions' ], 20 );
        
        // Core initialization
        add_action( 'init', [ $this, 'init' ] );
        add_action( 'admin_init', [ $this, 'admin_init' ] );
        add_action( 'admin_post_prorank_download_backup', [ $this, 'handle_backup_download' ] );
        
        // Load text domain
        
        // Admin menu - using MenuHandler
        $menu_handler = new \ProRank\SEO\Admin\MenuHandler($this);
        add_action( 'admin_menu', [ $menu_handler, 'init' ] );
        
        // Initialize Meta Box
        $meta_box = new \ProRank\SEO\Admin\MetaBox($this);
        $meta_box->init();
        
        // Initialize Editor Sidebar
        $editor_sidebar = new \ProRank\SEO\Admin\EditorSidebar($this);
        $editor_sidebar->init();
        
        // Initialize Admin Bar Menu
        $admin_bar_menu = new \ProRank\SEO\Admin\AdminBarMenu();
        $admin_bar_menu->init();
        
        // Initialize Menu Styles
        $menu_styles = new \ProRank\SEO\Admin\MenuStyles();
        $menu_styles->init();

        // Admin scripts (enqueue main React app + import map)
        new \ProRank\SEO\Admin\AdminScripts($this);
        
        // Admin assets
        add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] );
        add_action( 'admin_notices', [ $this, 'render_google_tag_conflict_notice' ] );
        add_action( 'admin_post_prorank_ignore_google_tag_conflict_notice', [ $this, 'handle_ignore_google_tag_conflict_notice' ] );
        
        // Frontend assets (if needed)
        add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_frontend_assets' ] );

        // REST API initialization
        add_action( 'rest_api_init', [ $this, 'init_rest_api' ] );
        add_filter( 'auto_update_plugin', [ $this, 'filter_plugin_auto_update' ], 10, 2 );

        // Initialize Frontend Head Output
        // Safety guard: when performance group is OFF, ensure no stale runtime
        // mutations remain active from prior settings/cache artifacts.
        add_action( 'init', [ $this, 'reconcile_performance_active_modules_from_settings' ], 0 );
        add_action( 'init', [ $this, 'enforce_performance_runtime_safety' ], 1 );
        add_action( 'init', [ $this, 'init_frontend_head_output' ] );
        add_action( 'init', [ $this, 'init_indexnow_virtual_file' ] );
        add_action( 'init', [ $this, 'init_resource_hints_fallback' ] );
        add_action( 'init', [ $this, 'init_speculation_rules' ] );
        add_action( 'init', [ $this, 'init_third_party_defer' ] );
        add_action( 'init', [ $this, 'init_trustindex_loader_bridge' ] );
        add_action( 'init', [ $this, 'init_lazy_media' ] );
        add_action( 'init', [ $this, 'init_css_async_fallback' ] );
        add_action( 'init', [ $this, 'init_js_defer_fallback' ] );
        add_action( 'init', [ $this, 'init_cookieyes_asset_bridge' ] );
        add_action( 'init', [ $this, 'init_elementor_visibility_bridge' ] );
        add_action( 'init', [ $this, 'init_inline_script_contract_guard' ] );
        add_action( 'init', [ $this, 'init_rum_beacon' ] );
        add_action( 'prorank_seo_module_toggled', [ $this, 'handle_module_toggle_sync' ], 10, 2 );
        add_action( 'prorank_seo_module_group_toggled', [ $this, 'handle_module_group_toggle_sync' ], 10, 2 );
        add_action( 'prorank_performance_cache_clear', [ $this, 'refresh_performance_runtime' ] );
        add_action( 'prorank_clear_cache', [ $this, 'purge_global_host_cache' ] );
        
        // Cache/CDN purges on content changes
        add_action( 'save_post', [ $this, 'handle_content_change' ], 10, 3 );
        add_action( 'deleted_post', [ $this, 'handle_content_delete' ], 10, 1 );
        add_action( 'transition_post_status', [ $this, 'handle_post_status_transition' ], 10, 3 );
        add_action( 'wp_update_nav_menu', [ $this, 'handle_menu_change' ], 10, 1 );
        add_action( 'wp_enqueue_scripts', [ $this, 'patch_known_inline_after_jquery_contracts' ], 9998 );
        add_action( 'wp_enqueue_scripts', [ $this, 'move_safe_jquery_chain_to_footer' ], 9999 );
        add_action( 'wp_print_scripts', [ $this, 'prepare_safe_jquery_core_footer_relocation' ], 0 );
        add_action( 'wp_print_footer_scripts', [ $this, 'print_safe_relocated_jquery_core' ], 0 );

    }
    
    /**
     * Load modules early
     */
    public function load_modules(): void {
        static $modules_loaded = false;
        if (!$modules_loaded) {
            // Security validation checkpoint - module loading
            if (class_exists('\ProRank\SEO\Core\SecurityValidator')) {
                \ProRank\SEO\Core\SecurityValidator::validate('cp_module');
            }
            
            $module_loader = new ModuleLoader($this->modules());
            $module_loader->load();
            $modules_loaded = true;
            
            // Hook for Premium plugin to register external modules
            do_action( 'prorank_seo_modules_loaded', $this->modules() );
            
            // Initialize modules immediately
            $this->modules()->init_active_modules();
        }
    }

    /**
     * Register image optimization background actions even when modules are not initialized.
     *
     * @return void
     */
    public function register_image_optimization_actions(): void {
        if ( ! has_action( 'prorank_bulk_optimization_continue' ) ) {
            add_action( 'prorank_bulk_optimization_continue', [ $this, 'handle_bulk_optimization_continue' ], 10, 2 );
        }

        if ( ! has_action( 'prorank_bulk_optimization_batch' ) ) {
            add_action( 'prorank_bulk_optimization_batch', [ $this, 'handle_bulk_optimization_batch' ], 10, 2 );
        }

        if ( ! has_action( 'prorank_optimize_image' ) ) {
            add_action( 'prorank_optimize_image', [ $this, 'handle_optimize_image_action' ], 10, 1 );
        }
    }

    /**
     * Register Site Audit cron action callbacks, even when module bootstrap is delayed.
     *
     * @return void
     */
    public function register_site_audit_actions(): void {
        if ( ! has_action( 'prorank_run_site_crawler' ) ) {
            add_action( 'prorank_run_site_crawler', [ $this, 'handle_site_audit_run_crawler' ], 10, 1 );
        }

        if ( ! has_action( 'prorank_run_immediate_crawler' ) ) {
            add_action( 'prorank_run_immediate_crawler', [ $this, 'handle_site_audit_run_crawler' ], 10, 1 );
        }

        if ( ! has_action( 'prorank_schedule_url_checks' ) ) {
            add_action( 'prorank_schedule_url_checks', [ $this, 'handle_site_audit_schedule_checks' ], 10, 1 );
        }

        if ( ! has_action( 'prorank_check_audit_url' ) ) {
            add_action( 'prorank_check_audit_url', [ $this, 'handle_site_audit_check_url' ], 10, 3 );
        }
    }

    /**
     * Handle scheduled site crawl execution.
     *
     * @param string $audit_id Audit identifier.
     * @return void
     */
    public function handle_site_audit_run_crawler( $audit_id = '' ): void {
        if ( ! class_exists( \ProRank\SEO\Core\Audits\SiteAuditEngine::class ) ) {
            return;
        }

        $audit_id = (string) $audit_id;
        if ( $audit_id === '' ) {
            return;
        }

        $engine = new \ProRank\SEO\Core\Audits\SiteAuditEngine();
        $engine->run_crawler( $audit_id );
    }

    /**
     * Handle scheduled URL batch scheduling for Site Audit.
     *
     * @param string $audit_id Audit identifier.
     * @return void
     */
    public function handle_site_audit_schedule_checks( $audit_id = '' ): void {
        if ( ! class_exists( \ProRank\SEO\Core\Audits\SiteAuditEngine::class ) ) {
            return;
        }

        $audit_id = (string) $audit_id;
        if ( $audit_id === '' ) {
            return;
        }

        $engine = new \ProRank\SEO\Core\Audits\SiteAuditEngine();
        $engine->schedule_url_checks( $audit_id );
    }

    /**
     * Handle scheduled single URL check for Site Audit.
     *
     * @param string $audit_id Audit identifier.
     * @param int    $url_id   URL row ID.
     * @param string $url      URL to check.
     * @return void
     */
    public function handle_site_audit_check_url( $audit_id = '', $url_id = 0, $url = '' ): void {
        if ( ! class_exists( \ProRank\SEO\Core\Audits\SiteAuditEngine::class ) ) {
            return;
        }

        $audit_id = (string) $audit_id;
        $url_id   = (int) $url_id;
        $url      = (string) $url;

        if ( $audit_id === '' || $url_id <= 0 || $url === '' ) {
            return;
        }

        $engine = new \ProRank\SEO\Core\Audits\SiteAuditEngine();
        $engine->check_url( $audit_id, $url_id, $url );
    }

    /**
     * Handle bulk optimization continuation when Action Scheduler fires.
     *
     * @param string $job_id Job ID.
     * @param int    $next_offset Next offset.
     * @return void
     */
    public function handle_bulk_optimization_continue( $job_id = '', $next_offset = 0 ): void {
        if ( ! class_exists( ImageOptimizationController::class ) ) {
            return;
        }

        $controller = new ImageOptimizationController();
        $controller->handle_bulk_optimization_continue( (string) $job_id, (int) $next_offset );
    }

    /**
     * Handle bulk optimization batch when WP-Cron fallback fires.
     *
     * @param string $job_id Job ID.
     * @param int    $offset Offset for the next batch.
     * @return void
     */
    public function handle_bulk_optimization_batch( $job_id = '', $offset = 0 ): void {
        if ( ! class_exists( ImageOptimizationController::class ) ) {
            return;
        }

        $controller = new ImageOptimizationController();
        $controller->handle_bulk_optimization_continue( (string) $job_id, (int) $offset );
    }

    /**
     * Handle single image optimization when Action Scheduler fires.
     *
     * @param mixed $attachment_id Attachment ID or args array.
     * @return void
     */
    public function handle_optimize_image_action( $attachment_id = 0 ): void {
        if ( is_array( $attachment_id ) ) {
            $attachment_id = $attachment_id['attachment_id'] ?? 0;
        }

        $attachment_id = (int) $attachment_id;
        if ( $attachment_id <= 0 ) {
            return;
        }

        $module = $this->modules()->get_module( 'image_optimization' );
        if ( is_object( $module ) && method_exists( $module, 'process_single_image_optimization' ) ) {
            $module->process_single_image_optimization( $attachment_id );
            return;
        }

        if ( class_exists( \ProRank\SEO\Modules\Performance\ImageOptimizationModule::class ) ) {
            $fallback = new \ProRank\SEO\Modules\Performance\ImageOptimizationModule();
            $fallback->process_single_image_optimization( $attachment_id );
        }
    }
    
    /**
     * General initialization
     */
    public function init(): void {
        // Security validation checkpoint - initialization
        // Do this first now that the plugin is fully initialized
        if (class_exists('\ProRank\SEO\Core\SecurityValidator')) {
            \ProRank\SEO\Core\SecurityValidator::validate('cp_init');
        }

        // Keep performance child-module flags aligned before ModuleManager
        // decides what to boot on this request.
        $this->reconcile_performance_active_modules_from_settings();
        
        // Ensure modules are loaded (in case plugins_loaded didn't fire)
        $this->load_modules();

        // Hook for Premium plugin to initialize
        do_action( 'prorank_seo_init', $this );

        // Load Elementor integration if Elementor is active
        $this->load_elementor_integration();

        // Load Divi Builder integration if Divi is active
        $this->load_divi_integration();

        // Load Beaver Builder integration if BB is active
        $this->load_beaver_builder_integration();

        // Check for competitor plugins
        $this->check_competitors();

        // Audit system now handled by external prorank.io service
        // No local tables needed

    }

    /**
     * Respect the saved auto-update setting during WordPress update checks.
     */
    public function filter_plugin_auto_update( $update, $item ): bool {
        $plugin_basename = defined( 'PRORANK_PLUGIN_BASENAME' ) ? PRORANK_PLUGIN_BASENAME : plugin_basename( PRORANK_PLUGIN_FILE );
        $plugin_slug     = dirname( $plugin_basename );
        $item_plugin     = is_object( $item ) ? (string) ( $item->plugin ?? '' ) : '';
        $item_slug       = is_object( $item ) ? (string) ( $item->slug ?? '' ) : '';

        if ( $item_plugin === $plugin_basename || $item_slug === $plugin_slug || $item_slug === 'prorank-seo' ) {
            return (bool) get_option( 'prorank_auto_update', false );
        }

        return (bool) $update;
    }
    
    /**
     * Admin initialization
     */
    public function admin_init(): void {
        // Security validation checkpoint - admin access
        if (class_exists('\ProRank\SEO\Core\SecurityValidator')) {
            \ProRank\SEO\Core\SecurityValidator::validate('cp_admin', ['user_id' => get_current_user_id()]);
        }
        
        
        // Register settings
        // $this->settings()->register_settings();
        
        // Initialize media enhancements
        new Admin\MediaEnhancements($this);

        // Initialize media library optimization features
        new Admin\MediaLibraryOptimization($this);

        // Initialize bulk edit integration
        new BulkEditIntegration();

        // Initialize user profile E-E-A-T fields
        $user_profile_fields = new Admin\UserProfileFields();
        $user_profile_fields->init();

        // Initialize dashboard widget
        $dashboard_widget = new Admin\DashboardWidget();
        $dashboard_widget->init();

        // Check for plugin updates
        $this->check_updates();
        
        // Initialize onboarding
        $this->init_onboarding();
        
        // Initialize audit settings page
        if (class_exists('\ProRank\SEO\Admin\Pages\AuditSettings')) {
            $audit_settings = new \ProRank\SEO\Admin\Pages\AuditSettings();
            $audit_settings->init();
        }
        
    }
    
    /**
     * Get menu icon
     *
     * Returns the ProRank favicon as a data URI for WordPress admin menu.
     *
     * @return string
     */
    private function get_menu_icon(): string {
        // ProRank favicon - orange P on dark background
        $svg = '<svg version="1.1" viewBox="0 0 1909 2048" width="2106" height="2259" xmlns="http://www.w3.org/2000/svg"><path transform="translate(959,1)" d="m0 0h10l6 3 5 8 12 32 8 19 9 24 7 20 16 40 10 26 3 10 13 34 4 9 12 31 7 20 10 25 9 23 7 18 6 18 8 21 7 16 11 29 3 10 14 36 5 12 11 28 4 13 12 36 3 16 1 13v13l-2 16-2 4-2 1-14 1-424 11h-14l-6 4-6 7-2 6-1 30-1 149v195l-3 8-7 6-35 12-28 11-27 10-37 13-16 6-12 5-30 11-11-2-11-9-16-13-11-8-9-8-11-9-5-4-5-5-14-12-10-8-12-9-13-11-11-9-5-4-5-5-13-11-14-11-9-7-13-11-14-11-8-8-14-11-9-8-6-5-5-3-13-11-11-9-10-9-13-11-11-9-8-6-10-8-13-11-11-9-12-11-10-8-11-9-11-8-15-13-10-8-12-11-22-18-11-8-15-13-10-8-12-11-11-9-4-5-1-2v-12l4-6 8-4 27-1 36-1 11-1 24-1 47-1 42-2 47-1 35-2 47-1 26-1 9-1 73-2 42-2h19l44-2 19-1 47-1 25-1 10-1 35-1h12l12-1 10-5 4-7 7-20 9-24 5-11 10-27 6-18 11-29 4-8 13-35 7-20 11-28 10-26 4-10 4-13 12-31 8-20 8-21 6-18 10-26 4-9 11-29 5-15 13-34 5-12 11-29 7-20 9-24 4-9 12-31 4-13 4-8z" fill="#0B121F"/><path transform="translate(728,646)" d="m0 0h324l24 2 24 4 20 5 20 7 16 7 19 10 15 10 14 11 13 12 11 12 10 13 10 15 11 21 7 18 7 25 4 25 1 11v62l-3 24-4 19-9 27-10 21-11 18-10 13-11 13-14 14-11 9-14 10-15 9-17 9-23 9-26 7-23 4-22 2h-185v275h-142z" fill="#FE6900"/><path transform="translate(1156,637)" d="m0 0h26l567 19 143 5 9 3 5 5 3 9-3 8-8 8-11 9-8 6-11 9-13 11-9 7-5 5-11 10-11 9-8 6-11 9-13 11-10 8-5 5-10 9-30 24-13 11-11 9-7 7-11 9-13 11-13 10-26 22-11 10-11 9-12 10-8 6-13 11-11 9-4 2-2 4-13 11-11 9-8 6-11 9-13 11-11 9-12 11-10 8-13 10-14 11-14 12-10 9-16 13-14 11-8 6-11 9-16 13-10 9-14 11-11 9-5 3-16 13-14 11-10 9-14 11-24 18-25 20-13 11-16 12-11 8-13 10-17 13-5 5-16 12-14 11-7 4-15 12-15 11-5 5-19 14-11 8-10 6-7 1-4-2h-12l-12 1-25 1-50 1-16 1h-17l-1-4v-31l1-24 1-59v-142l-3-1v-3h3l1 3 22 1 9 1 29 1h15l60 3h29l28-3 23-4 29-9 25-12 19-10 12-7 15-11 12-10 16-16 9-11 11-13 10-18 6-13 4-13 8-28 3-13 4-30 1-25v-39l-3-29-7-28-7-19-4-10-8-15-8-12-7-8-8-10-11-12-7-7v-2l-4-2-13-11-15-10-24-13-9-5-5-6-1-3v-11l3-6z" fill="#0B121F"/><path transform="translate(1369,1159)" d="m0 0 9 1 6 4 4 6 4 15 10 40 5 22 6 24 5 17 6 25 3 14 8 33 3 9 8 32 4 20 7 27 5 18 6 25 4 18 7 27 9 35 4 18 8 33 4 13 7 29 6 26 5 20 5 18 6 24 6 27 7 25 9 36v10l-3 6-6 5-9 2-8-3-16-8-19-10-18-11-29-16-12-6-21-12-21-13-24-14-12-6-22-13-15-10-27-16-16-9-21-13-13-9-24-15-14-8-23-15-16-11-17-11-11-7-9-5-20-13-10-7-23-15-14-9-9-5-17-11-12-8-10-7-25-16-14-8-18-12-5-6-3-9v-8l4-9 4-5 13-10 8-7 17-13 9-7 5-3 26-20 13-11 17-13 11-8 16-12 13-10 7-5 5-5 14-11 13-10 13-9 14-11 13-10 9-8 12-9 13-10 9-6 14-11 13-10 9-8 28-21 11-8 18-14 5-4h2l2-4 12-9 18-14 6-3z" fill="#0B121F"/><path transform="translate(714,1135)" d="m0 0h3l1 41v73l1 138-1 24h142l55 1h71l21 1-5 5-11 8-11 9-14 10-8 6-7 4-13 10-15 11h-2l-1 3-19 14-14 10-17 12-18 13-5 5-18 13-11 8-9 6-19 14-9 7-13 10-14 10-9 6-17 12-16 12-10 8-17 12-15 10-28 20h-2l-1 3-19 13-14 10-7 4-17 12-14 10-15 11-18 12-7 4-24 16-15 11-20 13-15 9-17 11-12 8-10 7-18 11-8 5-8 4-11 7-5-1-4-7-1-5v-13l7-30 4-13 7-29 3-15 7-28 4-14 6-25 3-15 7-28 4-15 6-24 3-15 7-28 4-15 6-24 3-15 7-28 4-15 6-24 5-23 5-20 4-15 6-24 3-15 7-28 4-15 6-24 3-15 7-28 5-18 2-5 25-10 48-17 10-3 37-13 15-6 37-13z" fill="#0B121F"/><path transform="translate(915,789)" d="m0 0h44l53 1 22 2 12 3 19 9 10 8 12 12 8 14 5 14 2 14v28l-3 19-7 16-7 12-4 2-2 4-10 9-13 8-11 5-12 3-8 1-31 1-36 1h-25l-29-1-12-1-1-23v-45l1-115z" fill="#0B121F"/><path transform="translate(870,776)" d="m0 0h170l22 4 15 5 15 8 13 10 11 11 10 16 6 15 4 20v41l-4 19-6 15-7 12-9 10-5 5-8 7-15 9-19 7-20 4-19 1h-154zm45 13-23 1-1 115v45l1 23 12 1 29 1h25l47-1 20-1 16-3 13-5 12-7 11-9 4-4v-2l4-2 8-14 6-14 3-19v-28l-2-14-5-14-8-14-15-15-11-7-18-8-16-3-24-1-44-1z" fill="#fff"/></svg>';
        return 'data:image/svg+xml;base64,' . base64_encode($svg);
    }
    
    /**
     * Render admin page
     */
    public function render_admin_page(): void {
        echo '<div class="wrap"><div id="prorank-admin-root" class="prorank-admin-container"></div></div>';
    }
    
    /**
     * Render settings page
     */
    public function render_settings_page(): void {
        echo '<div class="wrap"><div id="prorank-admin-root" class="prorank-admin-container"></div></div>';
    }
    
    /**
     * Enqueue admin assets
     *
     * @param string $hook_suffix Current admin page hook suffix
     */
    public function enqueue_admin_assets( string $hook_suffix ): void {
        // Only load main admin assets on our admin pages
        if ( ! $this->is_prorank_admin_page( $hook_suffix ) ) {
            return;
        }

        if ( $this->current_prorank_page_uses_media_library() ) {
            wp_enqueue_media();
        }
        
        // Get asset file for admin bundle (fallback if asset file doesn't exist - Vite builds)
        $asset_file_path = PRORANK_PLUGIN_DIR . 'build/admin.asset.php';
        if (file_exists($asset_file_path)) {
            $asset_file = include $asset_file_path;
            $dependencies = $asset_file['dependencies'] ?? [];
            $version = $asset_file['version'] ?? self::VERSION;
        } else {
            // Fallback for Vite builds - no asset.php file
            $dependencies = ['wp-element', 'wp-components', 'wp-api-fetch', 'wp-i18n'];
            $version = self::VERSION;
        }

        // Admin script loading is handled by AdminScripts.php which properly
        // sets up ES modules with import maps. This prevents conflicts.
        // PropTypes and Recharts are bundled by Vite, no separate vendor files needed.

        // Only add localized data if AdminScripts has enqueued the main script
        if (wp_script_is('prorank-admin-js', 'enqueued')) {
            wp_localize_script(
                'prorank-admin-js',
                'prorankSeoAdmin',
                $this->get_admin_localized_data()
            );
        }
        
        // Enqueue admin styles only for legacy builds. The Vite admin bundle
        // imports its own shared CSS, so loading build/main.css here would
        // duplicate the same stylesheet on every routed admin page.
        $main_css = PRORANK_PLUGIN_DIR . 'build/main.css';
        $admin_css = PRORANK_PLUGIN_DIR . 'build/admin.css';
        $enqueued_main = false;

        if (!file_exists($main_css) && file_exists($admin_css)) {
            wp_enqueue_style(
                'prorank-seo-admin-main',
                PRORANK_PLUGIN_URL . 'build/admin.css',
                ['wp-components'],
                filemtime($admin_css)
            );
            $enqueued_main = true;
        } elseif (!file_exists($main_css)) {
            // Fallback for older builds.
            $legacy_css = PRORANK_PLUGIN_DIR . 'admin/assets/js/App.css';
            if (file_exists($legacy_css)) {
                wp_enqueue_style(
                    'prorank-seo-admin-main',
                    PRORANK_PLUGIN_URL . 'admin/assets/js/App.css',
                    ['wp-components'],
                    filemtime($legacy_css)
                );
                $enqueued_main = true;
            }
        }

        $site_audit_style_dependency = $enqueued_main ? 'prorank-seo-admin-main' : 'wp-components';

        $this->maybe_enqueue_site_audit_route_css($site_audit_style_dependency);
        
        // Enqueue admin menu fix styles
        $menu_fix_css = PRORANK_PLUGIN_DIR . 'admin/assets/css/admin-menu-fix.css';
        if (file_exists($menu_fix_css)) {
            wp_enqueue_style(
                'prorank-admin-menu-fix',
                PRORANK_PLUGIN_URL . 'admin/assets/css/admin-menu-fix.css',
                ['prorank-seo-admin'],
                PRORANK_VERSION
            );
        }
    }

    /**
     * Enqueue the lazy Site Audit route CSS chunk with explicit cache-busting.
     *
     * Vite emits the Site Audit view styles into a lazy CSS chunk (for example
     * `index3.css`). Enqueuing it through WordPress gives the file a real
     * `?ver=` value, which prevents stale admin CSS from surviving deploys.
     *
     * @param string $dependency_handle Base admin stylesheet handle.
     * @return void
     */
    private function maybe_enqueue_site_audit_route_css(string $dependency_handle): void {
        $current_page = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : '';
        $current_tab  = isset($_GET['tab']) ? sanitize_key(wp_unslash($_GET['tab'])) : '';

        if ('prorank-site-audit' !== $current_page && 'site-audit' !== $current_tab) {
            return;
        }

        $this->enqueue_vite_manifest_css_entry(
            'src/admin/pages/SiteAudit/index.jsx',
            'prorank-seo-site-audit',
            [$dependency_handle]
        );
    }

    /**
     * Enqueue CSS files listed for a Vite manifest entry.
     *
     * @param string $entry_source   Manifest entry source path.
     * @param string $handle_prefix  Prefix for generated style handles.
     * @param array  $dependencies   Base dependencies for the first chunk.
     * @return void
     */
    private function enqueue_vite_manifest_css_entry(string $entry_source, string $handle_prefix, array $dependencies = []): void {
        $manifest = $this->get_vite_manifest();

        if (!isset($manifest[$entry_source]['css']) || !is_array($manifest[$entry_source]['css'])) {
            return;
        }

        $chunk_dependencies = $dependencies;

        foreach ($manifest[$entry_source]['css'] as $css_file) {
            $css_file = ltrim((string) $css_file, '/');
            $css_path = PRORANK_PLUGIN_DIR . 'build/' . $css_file;

            if (!file_exists($css_path)) {
                continue;
            }

            $handle = sanitize_key($handle_prefix . '-' . pathinfo($css_file, PATHINFO_FILENAME));

            wp_enqueue_style(
                $handle,
                PRORANK_PLUGIN_URL . 'build/' . $css_file,
                $chunk_dependencies,
                filemtime($css_path)
            );

            $chunk_dependencies[] = $handle;
        }
    }

    /**
     * Get the Vite manifest as an array.
     *
     * @return array<string, mixed>
     */
    private function get_vite_manifest(): array {
        static $manifest = null;

        if (null !== $manifest) {
            return $manifest;
        }

        $manifest_path = PRORANK_PLUGIN_DIR . 'build/manifest.json';

        if (!file_exists($manifest_path)) {
            $manifest = [];
            return $manifest;
        }

        $raw = file_get_contents($manifest_path);
        $decoded = is_string($raw) ? json_decode($raw, true) : null;
        $manifest = is_array($decoded) ? $decoded : [];

        return $manifest;
    }

    /**
     * Determine whether the current ProRank admin page needs WordPress media assets.
     *
     * @return bool
     */
    private function current_prorank_page_uses_media_library(): bool {
        $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
        $tab  = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : '';

        if ( 'prorank-on-page-seo' === $page ) {
            return true;
        }

        return 'prorank-seo' === $page && 'quick-setup' === $tab;
    }
    
    /**
     * Get build information
     *
     * @return array
     */
    private function get_build_info(): array {
        $build_file = PRORANK_PLUGIN_DIR . 'build-info.json';
        $default_info = [
            'fingerprint' => '',
            'timestamp' => 0,
            'commit' => '',
        ];
        
        if (file_exists($build_file)) {
            $build_data = json_decode(file_get_contents($build_file), true);
            if ($build_data) {
                return array_merge($default_info, $build_data);
            }
        }
        
        return $default_info;
    }
    
    /**
     * Get admin localized data
     *
     * @return array
     */
    /**
     * Check if premium plugin is active
     *
     * @return bool
     */
    public function is_premium_active(): bool {
        if (defined('PRORANK_PREMIUM_VERSION')) {
            return true;
        }

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

        if (!function_exists('is_plugin_active')) {
            return false;
        }

        return is_plugin_active('prorank-seo-premium/prorank-seo.php')
            || is_plugin_active('prorank-seo-premium/prorank-seo-premium.php');
    }

    /**
     * Get modules with availability status
     *
     * Returns all free-tier modules with their availability status.
     *
     * @return array
     */
    /**
     * Load the static module catalog shipped with the plugin.
     *
     * Free build uses this catalog for free-module metadata only.
     *
     * @return array<string, array<string, mixed>>
     */
    private function load_module_catalog(): array {
        $catalog_file = PRORANK_PLUGIN_DIR . 'config/module-catalog.json';
        if (!file_exists($catalog_file)) {
            return [];
        }

        $raw = file_get_contents($catalog_file);
        if ($raw === false) {
            return [];
        }

        $decoded = json_decode($raw, true);
        if (!is_array($decoded) || !isset($decoded['modules']) || !is_array($decoded['modules'])) {
            return [];
        }

        return $decoded['modules'];
    }

    /**
     * Get modules with availability status.
     *
     * Returns only free-tier modules for the free plugin UI.
     *
     * @return array<string, array<string, mixed>>
     */
    private function get_filtered_modules(): array {
        $loaded_modules = $this->modules()->get_modules();
        $catalog_modules = $this->load_module_catalog();

        $result = [];

        $add_module = function (string $slug, array $meta) use (&$result, $loaded_modules): void {
            $tier = (string) ($meta['tier'] ?? 'free');
            if ($tier === 'core') {
                $tier = 'pro';
            }
            if ($tier !== 'free') {
                return;
            }

            $name = (string) ($meta['name'] ?? $slug);
            $description = (string) ($meta['description'] ?? '');
            $parent_slug = $meta['parent_slug'] ?? null;

            $enabled = (bool) ($meta['enabled'] ?? false);

            if (isset($loaded_modules[$slug]) && is_object($loaded_modules[$slug])) {
                $module = $loaded_modules[$slug];

                if (method_exists($module, 'get_feature_tier')) {
                    $tier = (string) $module->get_feature_tier();
                    if ($tier === 'core') {
                        $tier = 'pro';
                    }
                    if ($tier !== 'free') {
                        return;
                    }
                }

                if (method_exists($module, 'get_name')) {
                    $name = (string) $module->get_name();
                }

                if (method_exists($module, 'get_description')) {
                    $description = (string) $module->get_description();
                }

                if (method_exists($module, 'get_parent_slug')) {
                    $parent_slug = $module->get_parent_slug();
                }

                if (method_exists($module, 'is_enabled')) {
                    $enabled = (bool) $module->is_enabled();
                } else {
                    $enabled = true;
                }
            }

            $result[$slug] = [
                'id' => $slug,
                'name' => $name,
                'description' => $description,
                'tier' => $tier,
                'parent_slug' => $parent_slug,
                'enabled' => $enabled,
                'available' => true,
                'loaded' => isset($loaded_modules[$slug]),
            ];
        };

        // Prefer static catalog metadata for free modules.
        if (!empty($catalog_modules)) {
            foreach ($catalog_modules as $slug => $meta) {
                if (!is_array($meta)) {
                    $meta = [];
                }
                $add_module((string) $slug, $meta);
            }
        }

        // Ensure any runtime-registered modules not present in the catalog still show.
        foreach ($loaded_modules as $slug => $module) {
            if (!isset($result[$slug])) {
                $add_module((string) $slug, []);
            }
        }

        return $result;
    }



    private function get_admin_localized_data(): array {
        $current_user = wp_get_current_user();
        $server_software = '';
        if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
            $server_software = sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) );
        }
        $nginx_server = $server_software !== '' && stripos( $server_software, 'nginx' ) !== false;
        $litespeed_server = $server_software !== '' && stripos( $server_software, 'litespeed' ) !== false;
        $litespeed_cache = defined( 'LSCWP_V' ) || class_exists( 'LiteSpeed_Cache_API' );
        
        // Get build information
        $build_info = $this->get_build_info();
        
        return [
            'apiUrl' => home_url( '/wp-json/prorank-seo/v1' ),
            'nonce' => wp_create_nonce( 'wp_rest' ),
            'adminUrl' => admin_url(),
            'siteUrl' => home_url(),
            'pluginUrl' => PRORANK_PLUGIN_URL,
            'version' => self::VERSION,
            'buildFingerprint' => $build_info['fingerprint'],
            'buildTimestamp' => $build_info['timestamp'],
            'buildCommit' => $build_info['commit'],
            'isMultisite' => is_multisite(),
            'currentUser' => [
                'id' => $current_user->ID,
                'name' => $current_user->display_name,
                'email' => $current_user->user_email,
                'avatar' => get_avatar_url( $current_user->ID ),
                'capabilities' => $current_user->allcaps,
            ],
            'settings' => $this->settings()->get_all(),
            'license' => [
                'status' => 'inactive',
                'tier' => 'free',
            ],
            'isPremiumActive' => false,
            'upgrade_url' => 'https://prorank.io/pricing',
            'modules' => $this->get_filtered_modules(),
            'notifications' => $this->notifications()->get_notifications(),
            'competitors' => $this->get_competitor_plugins(),
            'urls' => DomainConfig::getAllUrls(),
            'server' => [
                'nginx' => [
                    'server' => $nginx_server,
                ],
                'litespeed' => [
                    'server' => $litespeed_server,
                    'cache_plugin' => $litespeed_cache,
                    'supported' => $litespeed_server || $litespeed_cache,
                ],
            ],
        ];
    }
    
    /**
     * Check if current page is a ProRank admin page
     *
     * @param string $hook_suffix Hook suffix
     * @return bool
     */
    private function is_prorank_admin_page( string $hook_suffix ): bool {
        // Check if it's the top-level page or any submenu page
        if ( strpos( $hook_suffix, 'prorank-seo' ) !== false ) {
            return true;
        }
        
        // Also check the specific hook formats WordPress uses
        $prorank_pages = [
            'toplevel_page_prorank-seo',
            'prorank-seo_page_prorank-on-page-seo',
            'prorank-seo_page_prorank-performance',
            'prorank-seo_page_prorank-technical-seo',
            'prorank-seo_page_prorank-site-audit',
            'prorank-seo_page_prorank-support',
        ];
        
        if ( in_array( $hook_suffix, $prorank_pages, true ) ) {
            return true;
        }
        
        return false;
    }
    
    /**
     * Enqueue frontend assets
     */
    public function enqueue_frontend_assets(): void {
        // Frontend assets will be enqueued by individual modules as needed
    }
    
    /**
     * Check for plugin updates
     */
    private function check_updates(): void {
        // Update check logic here
    }
    
    /**
     * Initialize onboarding
     */
    private function init_onboarding(): void {
        // Check if onboarding is needed
        $onboarding_completed = get_option( 'prorank_onboarding_completed', false );
        
        if ( ! $onboarding_completed && current_user_can( 'manage_options' ) ) {
            // Show onboarding notice
            // $this->admin_notices->add(
            //     'onboarding',
            //     sprintf(
/* translators: %1$: value */
            //         __( 'Welcome to ProRank SEO! <a href="%1$s">Click here to complete the setup wizard</a>.', 'prorank-seo' ),
            //         admin_url( 'admin.php?page=prorank-seo&onboarding=1' )
            //     ),
            //     'info',
            //     false
            // );
        }
    }
    
    /**
     * Get service by name
     *
     * @param string $name Service name
     * @return mixed
     */
    public function get( string $name ) {
        return $this->services->get( $name );
    }
    
    /**
     * Get settings manager
     *
     * @return SettingsManager
     */
    public function settings(): SettingsManager {
        return $this->services->get('settings');
    }
    
    /**
     * Get license manager
     *
     * @return LicenseManager
     */
    public function license(): LicenseManager {
        return $this->services->get('license');
    }
    
    /**
     * Get notification manager
     *
     * @return NotificationManager
     */
    public function notifications(): NotificationManager {
        return $this->services->get('notifications');
    }
    
    /**
     * Get module manager
     *
     * @return ModuleManager
     */
    public function modules(): ModuleManager {
        return $this->services->get('modules');
    }
    
    /**
     * Get menu registry
     *
     * @return \ProRank\SEO\Core\MenuRegistry
     */
    public function menus(): \ProRank\SEO\Core\MenuRegistry {
        return $this->services->get('menus');
    }
    
    /**
     * Check if plugin is initialized
     *
     * @return bool
     */
    public function is_initialized(): bool {
        return !empty($this->services);
    }

    /**
     * Load Elementor integration if Elementor is active
     */
    private function load_elementor_integration(): void {
        // Only load if Elementor is active
        if (!defined('ELEMENTOR_VERSION')) {
            return;
        }

        // Load the integration class
        $integration_file = PRORANK_PLUGIN_DIR . 'includes/Integrations/Elementor/ElementorIntegration.php';
        if (file_exists($integration_file)) {
            require_once $integration_file;
            \ProRank\SEO\Integrations\Elementor\ElementorIntegration::get_instance();
        }
    }

    private function load_divi_integration(): void {
        // Only load if Divi Builder is active (theme or plugin)
        if (!defined('ET_BUILDER_VERSION')) {
            return;
        }

        $integration_file = PRORANK_PLUGIN_DIR . 'includes/Integrations/Divi/DiviIntegration.php';
        if (file_exists($integration_file)) {
            require_once $integration_file;
            \ProRank\SEO\Integrations\Divi\DiviIntegration::get_instance();
        }
    }

    private function load_beaver_builder_integration(): void {
        if (!defined('FL_BUILDER_VERSION') && !class_exists('FLBuilderModel')) {
            return;
        }

        $integration_file = PRORANK_PLUGIN_DIR . 'includes/Integrations/BeaverBuilder/BeaverBuilderIntegration.php';
        if (file_exists($integration_file)) {
            require_once $integration_file;
            \ProRank\SEO\Integrations\BeaverBuilder\BeaverBuilderIntegration::get_instance();
        }
    }

    private function check_competitors(): void {
        // Only check in admin
        if (!is_admin()) {
            return;
        }

        $detector = new \ProRank\SEO\Core\Compatibility\CompetitorDetector();
        $competitors = $detector->get_active_seo_competitors_details();
        $seo_competitors = $detector->get_active_seo_competitors();
        $seo_safe_mode_active = $detector->should_enable_seo_safe_mode();

        // Allow users who intentionally run both plugins to suppress the
        // conflict banner via prorank_conflict_settings['suppress_seo_notice'].
        // This keeps the warning visible for accidental conflicts on other sites.
        $conflict_settings = get_option('prorank_conflict_settings', []);
        if ($seo_safe_mode_active && !empty($conflict_settings['suppress_seo_notice'])) {
            return;
        }

        if (!empty($competitors)) {
            // Add a dismissible admin notice with deactivation links
            add_action('admin_notices', function() use ($competitors, $seo_competitors, $seo_safe_mode_active) {
                ?>
                <div class="notice notice-warning is-dismissible prorank-competitor-notice">
                    <p><strong><?php echo esc_html__('ProRank SEO Conflict Detection:', 'prorank-seo'); ?></strong></p>
                    <p><?php echo esc_html__('We\'ve detected other SEO plugins that may conflict with ProRank SEO. Running multiple SEO plugins can cause issues with your site\'s performance and search rankings.', 'prorank-seo'); ?></p>
                    <?php if ($seo_safe_mode_active && !empty($seo_competitors)) : ?>
                        <p>
                            <strong><?php echo esc_html__('SEO Safe Mode Active:', 'prorank-seo'); ?></strong>
                            <?php
                            echo esc_html(
                                sprintf(
/* translators: %s: list of active SEO plugins */
                                    __('ProRank SEO output is temporarily reduced to prevent duplicate tags while these SEO plugins are active: %s.', 'prorank-seo'),
                                    implode(', ', $seo_competitors)
                                )
                            );
                            ?>
                        </p>
                    <?php endif; ?>
                    
                    <div style="margin: 15px 0;">
                        <?php foreach ($competitors as $competitor): ?>
                            <div style="margin: 10px 0; padding: 10px; background: #fff; border-left: 4px solid #ff9800; display: flex; align-items: center; justify-content: space-between;">
                                <div>
                                    <strong><?php echo esc_html($competitor['name']); ?></strong>
                                    <span style="color: #666; margin-left: 10px;"><?php echo esc_html__('is currently active', 'prorank-seo'); ?></span>
                                </div>
                                <div>
                                    <a href="<?php echo esc_url($competitor['deactivate_url']); ?>" 
                                       class="button button-primary"
                                       onclick="return confirm('<?php
echo esc_js(
    sprintf(
        /* translators: %s: plugin name */
        __( 'Are you sure you want to deactivate %s?', 'prorank-seo' ),
        $competitor['name']
    )
);
?>');">
                                        <?php echo esc_html__('Deactivate', 'prorank-seo'); ?>
                                    </a>
                                    <a href="<?php echo esc_url(admin_url('plugins.php')); ?>" 
                                       class="button" 
                                       style="margin-left: 5px;">
                                        <?php echo esc_html__('Manage Plugins', 'prorank-seo'); ?>
                                    </a>
                                </div>
                            </div>
                        <?php endforeach; ?>
                    </div>
                    
                    <p style="margin-top: 15px;">
                        <strong><?php echo esc_html__('Recommendation:', 'prorank-seo'); ?></strong> 
                        <?php echo esc_html__('We recommend deactivating other SEO plugins before using ProRank SEO to avoid conflicts and ensure optimal performance.', 'prorank-seo'); ?>
                    </p>
                    
                    <p>
                        <a href="<?php echo esc_url(admin_url('admin.php?page=prorank-site-audit#?tab=import-export')); ?>" class="button button-secondary">
                            <?php echo esc_html__('Import Settings from Other SEO Plugins', 'prorank-seo'); ?>
                        </a>
                    </p>
                </div>
                <?php
            });
        }
    }

    /**
     * Warn when multiple Google tag emitters are configured.
     *
     * Detection-only by design: removing or consolidating tags automatically can
     * break Ads, analytics, conversion tracking, and Google Tag Coverage.
     */
    public function render_google_tag_conflict_notice(): void {
        if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) {
            return;
        }

        if ( (bool) get_user_meta( get_current_user_id(), 'prorank_ignore_google_tag_conflict_notice', true ) ) {
            return;
        }

        $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
        $screen_id = is_object( $screen ) ? (string) ( $screen->id ?? '' ) : '';
        $show_on_screen = $screen_id === 'dashboard' || stripos( $screen_id, 'prorank' ) !== false;

        if ( ! apply_filters( 'prorank_show_google_tag_conflict_notice', $show_on_screen, $screen_id ) ) {
            return;
        }

        $result = ( new GoogleTagConflictDetector() )->detect();
        if ( empty( $result['has_conflict'] ) || empty( $result['sources'] ) || ! is_array( $result['sources'] ) ) {
            return;
        }

        $ignore_url = wp_nonce_url(
            admin_url( 'admin-post.php?action=prorank_ignore_google_tag_conflict_notice' ),
            'prorank_ignore_google_tag_conflict_notice'
        );

        ?>
        <div class="notice notice-warning is-dismissible prorank-google-tag-conflict-notice" data-prorank-notice="google-tag-conflict">
            <p>
                <strong><?php echo esc_html__( 'ProRank Google tag warning:', 'prorank-seo' ); ?></strong>
                <?php echo esc_html__( 'Multiple Google tag emitters are configured. This can increase JavaScript execution time and may duplicate Analytics or Ads events.', 'prorank-seo' ); ?>
            </p>
            <ul style="margin-left: 18px; list-style: disc;">
                <?php foreach ( $result['sources'] as $source ) : ?>
                    <li>
                        <strong><?php echo esc_html( (string) ( $source['label'] ?? '' ) ); ?></strong>
                        <?php
                        $ids = isset( $source['ids'] ) && is_array( $source['ids'] ) ? array_filter( array_map( 'strval', $source['ids'] ) ) : [];
                        if ( ! empty( $ids ) ) {
                            echo ': ' . esc_html( implode( ', ', $ids ) );
                        }
                        ?>
                    </li>
                <?php endforeach; ?>
            </ul>
            <p>
                <strong><?php echo esc_html__( 'Recommendation:', 'prorank-seo' ); ?></strong>
                <?php echo esc_html( (string) ( $result['recommendation'] ?? '' ) ); ?>
            </p>
            <p>
                <a href="<?php echo esc_url( $ignore_url ); ?>" class="button button-secondary">
                    <?php echo esc_html__( 'Ignore this warning', 'prorank-seo' ); ?>
                </a>
            </p>
        </div>
        <?php
    }

    /**
     * Persistently dismiss the duplicate Google tag warning for this admin user.
     */
    public function handle_ignore_google_tag_conflict_notice(): void {
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_die( esc_html__( 'You do not have permission to ignore this warning.', 'prorank-seo' ) );
        }

        check_admin_referer( 'prorank_ignore_google_tag_conflict_notice' );

        update_user_meta( get_current_user_id(), 'prorank_ignore_google_tag_conflict_notice', 1 );

        $redirect = wp_get_referer();
        if ( ! $redirect ) {
            $redirect = admin_url( 'admin.php?page=prorank-seo' );
        }

        wp_safe_redirect( $redirect );
        exit;
    }

    /**
     * Enforce performance conflict safe mode before modules initialize.
     *
     * This prevents cache/minify/lazy-load stacks from being enabled in
     * parallel with competing performance plugins.
     *
     * @return void
     */
    public function maybe_apply_performance_conflict_safe_mode(): void {
        try {
            $detector = new \ProRank\SEO\Core\Compatibility\CompetitorDetector();
            if ( ! $detector->should_enable_performance_safe_mode() ) {
                return;
            }

            $active_conflicts = $detector->get_active_performance_competitors();
            if ( empty( $active_conflicts ) ) {
                return;
            }

            $suppressed_modules = $detector->get_performance_safe_mode_suppressions();
            if ( empty( $suppressed_modules ) ) {
                return;
            }

            $settings = get_option( 'prorank_settings', [] );
            if ( ! is_array( $settings ) ) {
                $decoded = json_decode( (string) $settings, true );
                $settings = is_array( $decoded ) ? $decoded : [];
            }

            if ( ! isset( $settings['active_modules'] ) || ! is_array( $settings['active_modules'] ) ) {
                $settings['active_modules'] = [];
            }

            $changed = false;
            foreach ( $suppressed_modules as $module_slug ) {
                if ( $this->is_truthy_setting( $settings['active_modules'][ $module_slug ] ?? null ) ) {
                    $settings['active_modules'][ $module_slug ] = false;
                    $changed = true;
                }
            }

            $groups = get_option( 'prorank_seo_module_groups', [] );
            if ( ! is_array( $groups ) ) {
                $groups = [];
            }

            $performance_enabled = false;
            foreach ( $this->get_performance_child_module_slugs( array_keys( $settings['active_modules'] ) ) as $module_slug ) {
                if ( in_array( $module_slug, $suppressed_modules, true ) ) {
                    continue;
                }

                if ( $this->is_truthy_setting( $settings['active_modules'][ $module_slug ] ?? null ) ) {
                    $performance_enabled = true;
                    break;
                }
            }

            if ( $this->is_truthy_setting( $settings['active_modules']['performance'] ?? false ) !== $performance_enabled ) {
                $settings['active_modules']['performance'] = $performance_enabled;
                $changed = true;
            }

            if ( $this->is_truthy_setting( $groups['performance'] ?? false ) !== $performance_enabled ) {
                $groups['performance'] = $performance_enabled;
                update_option( 'prorank_seo_module_groups', $groups );
                $changed = true;
            }

            if ( $changed ) {
                update_option( 'prorank_settings', $settings );
            }

            if ( ! $performance_enabled ) {
                $this->hard_disable_performance_runtime();
                return;
            }

            foreach ( $suppressed_modules as $module_slug ) {
                $this->disable_performance_runtime_for_module( $module_slug );
            }
            $this->refresh_performance_runtime();
        } catch ( \Throwable $e ) {
            prorank_log( '[ProRank SEO] Performance safe mode bootstrap failed: ' . $e->getMessage() );
        }
    }
    
    /**
     * Get competitor plugins
     *
     * @return array
     */
    private function get_competitor_plugins(): array {
        $detector = new \ProRank\SEO\Core\Compatibility\CompetitorDetector();
        return $detector->get_active_seo_competitors();
    }
    
    /**
     * Get service container
     *
     * @return ServiceContainer
     */
    public function get_container(): ServiceContainer {
        return $this->services;
    }
    
    /**
     * Initialize frontend head output
     */
    public function init_frontend_head_output(): void {
        // Only initialize on frontend requests
        if (!is_admin() && !wp_doing_ajax() && !wp_doing_cron()) {
            try {
                $head_output = new HeadOutput($this);
                $head_output->init();
            } catch (\Throwable $e) {
                prorank_log('[ProRank SEO] Failed to initialize HeadOutput: ' . $e->getMessage());
            }

            // Initialize Site Basics Output (verification tags, social meta, org schema)
            try {
                $site_basics_output = new SiteBasicsOutput();
            } catch (\Throwable $e) {
                prorank_log('[ProRank SEO] Failed to initialize SiteBasicsOutput: ' . $e->getMessage());
            }
        }
    }

    /**
     * Initialize virtual IndexNow key responder.
     */
    public function init_indexnow_virtual_file(): void {
        if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
            return;
        }
        try {
            $virtual_file = new IndexNowVirtualFile();
            $virtual_file->init();
        } catch (\Throwable $e) {
            prorank_log('[ProRank SEO] Failed to initialize IndexNowVirtualFile: ' . $e->getMessage());
        }
    }

    /**
     * Initialize speculation rules (safe prefetch/prerender)
     */
    public function init_speculation_rules(): void {
        if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
            return;
        }
        if (!$this->is_performance_module_active('speculation_rules')) {
            return;
        }
        // Respect module/group state and explicit cache settings. Never run by default.
        $cache_settings = get_option('prorank_cache_settings', []);
        $prefetch_enabled = is_array($cache_settings) && (
            $this->is_truthy_setting($cache_settings['prefetch_enabled'] ?? null)
            || $this->is_truthy_setting($cache_settings['prerender_enabled'] ?? null)
        );
        if (!$prefetch_enabled) {
            return;
        }
        // Suppress WordPress core's speculation rules since we provide our own
        add_filter('wp_speculation_rules_configuration', '__return_empty_array', 999);
        remove_action('wp_footer', 'wp_print_speculation_rules');
        // Skip if SpeculationRulesModule is handling this (avoid duplicates)
        $mm = $this->modules();
        if ($mm && isset($mm->get_active_modules()["speculation_rules"])) {
            return;
        }
        try {
            $speculation = new SpeculationRules();
            $speculation->init();
        } catch (\Throwable $e) {
            prorank_log('[ProRank SEO] Failed to initialize SpeculationRules: ' . $e->getMessage());
        }
    }

    /**
     * Initialize resource hints fallback.
     *
     * Ensures settings-driven DNS/font/link hints still work when the
     * `resource_hints` module is disabled in Module Manager.
     */
    public function init_resource_hints_fallback(): void {
        if ( is_admin() || wp_doing_ajax() || wp_doing_cron() ) {
            return;
        }

        // Module handles this when enabled.
        if ( $this->is_performance_module_active( 'resource_hints' ) ) {
            return;
        }

        $cache_settings = get_option( 'prorank_cache_settings', [] );
        if ( ! is_array( $cache_settings ) ) {
            return;
        }

        $dns_enabled   = $this->is_truthy_setting( $cache_settings['dns_prefetch_enabled'] ?? null );
        $font_enabled  = $this->is_truthy_setting( $cache_settings['font_preload_enabled'] ?? null );
        $link_enabled  = $this->is_truthy_setting( $cache_settings['link_preload_enabled'] ?? null );
        $any_enabled   = $dns_enabled || $font_enabled || $link_enabled;

        if ( ! $any_enabled ) {
            return;
        }

        try {
            $resource_hints = new \ProRank\SEO\Modules\Performance\ResourceHintsModule();

            if ( $dns_enabled ) {
                add_filter( 'wp_resource_hints', [ $resource_hints, 'add_dns_prefetch_hints' ], 10, 2 );
            }

            if ( $font_enabled ) {
                add_action( 'wp_head', [ $resource_hints, 'add_font_preload_links' ], 2 );
            }

            if ( $link_enabled ) {
                add_action( 'wp_enqueue_scripts', [ $resource_hints, 'enqueue_link_preload_script' ] );
            }
        } catch ( \Throwable $e ) {
            prorank_log( '[ProRank SEO] Failed to initialize resource hints fallback: ' . $e->getMessage() );
        }
    }

    /**
     * Initialize third-party defer for INP improvements
     */
    public function init_third_party_defer(): void {
        if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
            return;
        }

        $asset_settings = get_option('prorank_asset_optimization_settings', []);
        $cache_settings = get_option('prorank_cache_settings', []);

        $inp_enabled = $this->is_performance_module_active('inp_optimizer')
            && is_array($asset_settings)
            && $this->is_truthy_setting($asset_settings['inp_optimizer_enabled'] ?? null);

        $tracker_delay_enabled = is_array($cache_settings)
            && (
                (
                    $this->is_performance_module_active('js_opt')
                    && (
                        $this->is_truthy_setting($cache_settings['tracker_delay_enabled'] ?? null)
                        || $this->is_truthy_setting($cache_settings['js_delay'] ?? null)
                    )
                )
                || $this->is_tracker_delay_only_js_mode()
            );

        $advanced_settings = $this->get_runtime_advanced_settings();
        $governance_opt_in_enabled =
            $this->is_truthy_setting($advanced_settings['defer_consent_scripts'] ?? null)
            || (!array_key_exists('delay_google_services', $advanced_settings) || $this->is_truthy_setting($advanced_settings['delay_google_services']))
            || (!array_key_exists('delay_review_widgets', $advanced_settings) || $this->is_truthy_setting($advanced_settings['delay_review_widgets']))
            || (!array_key_exists('delay_identity_widgets', $advanced_settings) || $this->is_truthy_setting($advanced_settings['delay_identity_widgets']));

        $js_defer_governance_enabled = $governance_opt_in_enabled || (
            is_array($cache_settings)
            && $this->is_truthy_setting($cache_settings['js_defer'] ?? null)
            && $governance_opt_in_enabled
        );

        if (!$inp_enabled && !$tracker_delay_enabled && !$js_defer_governance_enabled) {
            return;
        }

        try {
            $defer = new ThirdPartyDefer();
            $defer->init();
        } catch (\Throwable $e) {
            prorank_log('[ProRank SEO] Failed to initialize ThirdPartyDefer: ' . $e->getMessage());
        }
    }

    /**
     * Initialize lazy media enhancer
     */
    public function init_lazy_media(): void {
        if (is_admin()) {
            return;
        }
        if (
            !$this->is_performance_module_active('lazyload')
            && !$this->is_performance_module_active('image_optimization')
        ) {
            return;
        }
        $image_settings = get_option('prorank_image_optimization_settings', []);
        $lazy_enabled = $this->is_lazyload_enabled_in_image_settings($image_settings);
        if (!$lazy_enabled) {
            return;
        }
        try {
            $lazy = new LazyMedia();
            $lazy->init();
        } catch (\Throwable $e) {
            prorank_log('[ProRank SEO] Failed to initialize LazyMedia: ' . $e->getMessage());
        }
    }

    /**
     * Whether lazyload is enabled in the image settings payload.
     *
     * Accept the legacy umbrella flag as a valid source of truth so older or
     * mixed payloads still boot lazy media features consistently.
     *
     * @param mixed $image_settings
     * @return bool
     */
    private function is_lazyload_enabled_in_image_settings($image_settings): bool {
        return is_array($image_settings) && (
            $this->is_truthy_setting($image_settings['lazyload_images'] ?? null) ||
            $this->is_truthy_setting($image_settings['lazyload_iframes'] ?? null) ||
            $this->is_truthy_setting($image_settings['lazyload_videos'] ?? null) ||
            $this->is_truthy_setting($image_settings['lazyload_enabled'] ?? null)
        );
    }

    /**
     * Initialize a safe CSS-async fallback for free/performance baseline.
     *
     * This runs as a safe second pass when css_async is enabled. The dedicated
     * css_opt module can own UCSS/minify without touching every remaining
     * stylesheet tag, so this fallback no-ops on tags already owned by ProRank
     * and handles the leftover safe cases.
     *
     * @return void
     */
    public function init_css_async_fallback(): void {
        if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
            return;
        }

        $cache_settings = get_option('prorank_cache_settings', []);
        if (!is_array($cache_settings) || !$this->is_truthy_setting($cache_settings['css_async'] ?? null)) {
            return;
        }

        if ($this->should_disable_css_async_fallback_for_current_stack()) {
            return;
        }

        add_filter('style_loader_tag', [$this, 'apply_css_async_fallback'], 20, 4);
    }

    /**
     * Disable the lightweight CSS async fallback on builder-sensitive stacks
     * when compatibility mode is enabled.
     *
     * The fallback is lighter than the full css_opt module. Keep it off by
     * default on the riskiest builder stacks, but allow a curated Elementor
     * path where only low-risk widget/animation styles are made async while
     * base theme and builder CSS stays blocking.
     *
     * @return bool
     */
    private function should_disable_css_async_fallback_for_current_stack(): bool {
        if (!$this->is_performance_compatibility_mode_enabled()) {
            return false;
        }

        $stack = $this->get_performance_stack_signature();
        $builder = (string) ($stack['builder'] ?? '');
        if ($builder === '') {
            return false;
        }

        $default_allowed = $this->can_use_curated_css_async_fallback_on_builder_stack($builder, $stack);

        return !(bool) apply_filters(
            'prorank_allow_css_async_fallback_on_builder_stack',
            $default_allowed,
            $builder,
            $stack
        );
    }

    /**
     * Decide whether a builder stack can use the curated CSS async fallback.
     *
     * @param string              $builder Builder slug from stack signature.
     * @param array<string,mixed> $stack   Stack signature.
     * @return bool
     */
    private function can_use_curated_css_async_fallback_on_builder_stack(string $builder, array $stack): bool {
        if ($builder !== 'elementor') {
            return false;
        }

        return !empty($stack['builder']);
    }

    /**
     * Convert non-critical stylesheet tags to non-blocking stylesheet loading.
     *
     * @param string $html   Original stylesheet tag.
     * @param string $handle Style handle.
     * @param string $href   Style URL.
     * @param string $media  Media attribute.
     * @return string
     */
    public function apply_css_async_fallback(string $html, string $handle, string $href, string $media): string {
        if ($href === '') {
            return $html;
        }

        $tag_lc = strtolower($html);
        if (
            strpos($tag_lc, '<link') === false ||
            strpos($tag_lc, 'stylesheet') === false ||
            strpos($tag_lc, 'rel=') === false
        ) {
            return $html;
        }

        $owned_markers = [
            'rel=\'preload\'',
            'rel="preload"',
            'data-prorank-async',
            'data-prorank-delay',
            'data-prorank-css-owner',
        ];
        foreach ($owned_markers as $marker) {
            if (strpos($tag_lc, $marker) !== false) {
                return $html;
            }
        }

        if (!preg_match('/\.css(?:$|\?)/i', $href)) {
            return $html;
        }

        if ($this->should_keep_blocking_stylesheet($handle, $href, $media)) {
            return $this->try_inline_small_css_fallback($handle, $href, $media) ?? $html;
        }

        $href_esc = esc_url($href);
        $handle_attr = sanitize_key($handle);
        $id_attr = $handle_attr !== '' ? " id='{$handle_attr}-css-async'" : '';

        $delivery = (string) apply_filters(
            'prorank_css_async_fallback_delivery',
            'media-print',
            $handle,
            $href,
            $media
        );

        // Use media=print instead of rel=preload for the curated fallback.
        // Preloading every async stylesheet promotes non-LCP CSS into the high
        // priority lane and can starve the LCP image on mobile Slow 4G. The
        // dedicated CssOptimizeModule still has its own small preload budget.
        if (strtolower(trim($delivery)) === 'preload') {
            $async = "<link rel='preload' as='style'{$id_attr} href='{$href_esc}' onload=\"this.onload=null;this.rel='stylesheet'\" data-prorank-async='true' data-prorank-css-owner='plugin_async_fallback' />";
        } else {
            $target_media = ($media !== '' && strtolower($media) !== 'all') ? (string) $media : 'all';
            $onload = esc_attr("this.onload=null;this.media='" . $target_media . "'");
            $async = "<link rel='stylesheet'{$id_attr} href='{$href_esc}' media='print' onload=\"{$onload}\" data-prorank-async='true' data-prorank-css-owner='plugin_async_fallback' />";
        }

        $noscript = "<noscript><link rel='stylesheet' href='{$href_esc}' media='all' /></noscript>"; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet

        return $async . $noscript;
    }

    /**
     * Inline small local stylesheets that still need to stay on the critical path.
     *
     * @param string $handle Style handle.
     * @param string $href   Style URL.
     * @param string $media  Media attribute.
     * @return string|null Inline style tag, or null when not eligible.
     */
    private function try_inline_small_css_fallback(string $handle, string $href, string $media): ?string {
        $max_bytes = (int) apply_filters('prorank_css_async_fallback_inline_max_bytes', 8192, $handle, $href);
        if ($max_bytes < 256) {
            return null;
        }

        $path = $this->css_fallback_url_to_path($href);
        if ($path === null || !is_readable($path)) {
            return null;
        }

        $size = filesize($path);
        if ($size === false || $size <= 0 || $size > $max_bytes) {
            return null;
        }

        $css = trim((string) file_get_contents($path));
        if ($css === '') {
            return '<!-- ProRank: skipped empty ' . esc_html($handle) . ' -->';
        }

        $css = str_ireplace('</style', '<\\/style', $css);
        $media_attr = ($media !== '' && strtolower($media) !== 'all')
            ? ' media="' . esc_attr($media) . '"'
            : '';

        return sprintf(
            '<style id="%s-inline-css" data-prorank-inline-css="fallback"%s>%s</style>',
            esc_attr($handle),
            $media_attr,
            $css // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- trusted local CSS file content
        );
    }

    /**
     * Resolve a same-site CSS URL to an absolute filesystem path.
     *
     * @param string $url CSS URL.
     * @return string|null
     */
    private function css_fallback_url_to_path(string $url): ?string {
        $url = trim($url);
        if ($url === '') {
            return null;
        }

        if (strpos($url, '//') === 0) {
            $url = (is_ssl() ? 'https:' : 'http:') . $url;
        }

        $url = strtok($url, '?#');
        if (!is_string($url) || $url === '') {
            return null;
        }

        $parsed = wp_parse_url($url);
        if (!is_array($parsed) || empty($parsed['path']) || !is_string($parsed['path'])) {
            return null;
        }

        $asset_host = isset($parsed['host']) ? strtolower((string) $parsed['host']) : '';
        if ($asset_host !== '') {
            $site_hosts = [];
            $home_host = wp_parse_url(home_url('/'), PHP_URL_HOST);
            $site_host = wp_parse_url(site_url('/'), PHP_URL_HOST);
            if (is_string($home_host) && $home_host !== '') {
                $site_hosts[] = strtolower($home_host);
            }
            if (is_string($site_host) && $site_host !== '') {
                $site_hosts[] = strtolower($site_host);
            }

            $normalized_asset_host = preg_replace('/^www\./', '', $asset_host);
            $matched = false;
            foreach (array_unique($site_hosts) as $site_host_value) {
                $normalized_site_host = preg_replace('/^www\./', '', $site_host_value);
                if ($asset_host === $site_host_value || $normalized_asset_host === $normalized_site_host) {
                    $matched = true;
                    break;
                }
            }
            if (!$matched) {
                return null;
            }
        }

        $path = rawurldecode((string) $parsed['path']);
        $site_path = wp_parse_url(home_url('/'), PHP_URL_PATH);
        if (!is_string($site_path) || $site_path === '') {
            $site_path = '/';
        }
        if (substr($site_path, -1) !== '/') {
            $site_path .= '/';
        }
        if ($site_path !== '/' && strpos($path, $site_path) === 0) {
            $path = '/' . ltrim(substr($path, strlen($site_path)), '/');
        }

        $candidate = wp_normalize_path(trailingslashit(ABSPATH) . ltrim($path, '/'));
        $abspath = wp_normalize_path(trailingslashit(ABSPATH));
        if (strpos($candidate, $abspath) !== 0 || !file_exists($candidate)) {
            return null;
        }

        return $candidate;
    }

    /**
     * Decide if a stylesheet should remain render-blocking.
     *
     * @param string $handle Style handle.
     * @param string $href   Style URL.
     * @param string $media  Media attribute.
     * @return bool
     */
    private function should_keep_blocking_stylesheet(string $handle, string $href, string $media): bool {
        // Keep admin-bar and logged-in UX stable.
        if (is_user_logged_in()) {
            return true;
        }

        $media_normalized = strtolower(trim((string) $media));
        if ($media_normalized !== '' && $media_normalized !== 'all' && $media_normalized !== 'screen') {
            return true;
        }

        $handle_lc = strtolower($handle);
        $href_lc = strtolower($href);
        $stack = $this->get_performance_stack_signature();

        if ($this->can_async_owned_base_builder_stylesheet($handle_lc, $href_lc, $stack)) {
            return false;
        }

        // Keep Elementor base/header/search/nav CSS blocking until ProRank has
        // proven structural critical CSS ownership for the current request.
        // The ownership branch above is intentionally checked first; otherwise
        // the stronger gate is unreachable for the exact base CSS it is meant
        // to move out of the critical path.
        if ($this->is_layout_sensitive_builder_stylesheet($handle_lc, $href_lc, $stack)) {
            return true;
        }

        // Keep core/theme/page-builder critical path blocking.
        $blocking_handle_patterns = array_merge(
            ['astra-theme-css'],
            PerformanceCompatibilityRegistry::get_builder_blocking_style_handle_patterns(),
            [
                'wp-block-library',
                'global-styles',
                'classic-theme-styles',
                'dashicons',
                'admin-bar',
                'wp-emoji',
            ]
        );

        foreach ($blocking_handle_patterns as $pattern) {
            if ($pattern !== '' && strpos($handle_lc, $pattern) !== false) {
                return true;
            }
        }

        // Keep critical core/theme assets blocking even when handle names vary.
        $blocking_href_patterns = array_merge(
            [
                '/wp-includes/css/',
                '/wp-content/themes/',
            ],
            PerformanceCompatibilityRegistry::get_builder_blocking_style_href_patterns()
        );

        foreach ($blocking_href_patterns as $pattern) {
            if ($pattern !== '' && strpos($href_lc, $pattern) !== false) {
                return true;
            }
        }

        if ($this->should_only_async_curated_builder_styles($stack)) {
            return !$this->is_curated_builder_async_stylesheet($handle_lc, $href_lc);
        }

        return false;
    }

    /**
     * Limit builder CSS async fallback to a curated Elementor-safe subset.
     *
     * @param array<string,mixed> $stack Stack signature.
     * @return bool
     */
    private function should_only_async_curated_builder_styles(array $stack): bool {
        if (!$this->is_performance_compatibility_mode_enabled()) {
            return false;
        }

        return (string) ($stack['builder'] ?? '') === 'elementor';
    }

    /**
     * Determine whether a stylesheet is safe to async on Elementor stacks.
     *
     * Keep this list narrow. The goal is to remove render-blocking from
     * decorative/interactive widget CSS without touching structural theme or
     * builder base CSS that influences first paint and layout.
     *
     * @param string $handle_lc Lower-cased style handle.
     * @param string $href_lc   Lower-cased style URL.
     * @return bool
     */
    private function is_curated_builder_async_stylesheet(string $handle_lc, string $href_lc): bool {
        if ($this->is_layout_sensitive_builder_stylesheet($handle_lc, $href_lc, ['builder' => 'elementor'])) {
            return false;
        }

        $handle_patterns = [
            'e-animation-',
            'swiper-css',
            'e-swiper',
            'widget-image-box',
            'widget-icon-box',
            'widget-video',
            'widget-flip-box',
            'widget-nested-carousel',
            'widget-image-carousel',
            'widget-nested-accordion',
            'widget-loop-common',
            'widget-loop-grid',
            'widget-animated-headline',
            'widget-spacer',
        ];

        foreach ($handle_patterns as $pattern) {
            if ($pattern !== '' && strpos($handle_lc, $pattern) !== false) {
                return true;
            }
        }

        $href_patterns = [
            '/elementor/assets/lib/animations/styles/',
            '/elementor/assets/lib/swiper/',
            '/elementor/assets/css/conditionals/e-swiper',
            '/elementor/assets/css/widget-image-box',
            '/elementor/assets/css/widget-icon-box',
            '/elementor/assets/css/widget-video',
            '/elementor-pro/assets/css/widget-flip-box',
            '/elementor-pro/assets/css/widget-nested-carousel',
            '/elementor/assets/css/widget-image-carousel',
            '/elementor/assets/css/widget-nested-accordion',
            '/elementor-pro/assets/css/widget-loop-common',
            '/elementor-pro/assets/css/widget-loop-grid',
            '/elementor-pro/assets/css/widget-animated-headline',
            '/elementor/assets/css/widget-spacer',
        ];

        foreach ($href_patterns as $pattern) {
            if ($pattern !== '' && strpos($href_lc, $pattern) !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * Whether stable layout critical CSS exists for the current request.
     *
     * This is the safety gate for widening Elementor async fallback beyond the
     * lowest-risk decorative widgets.
     *
     * @return bool
     */
    private function has_stable_builder_critical_css(): bool {
        if ($this->builder_critical_css_ready !== null) {
            return $this->builder_critical_css_ready;
        }

        $asset_settings = get_option('prorank_asset_optimization_settings', []);
        if (!is_array($asset_settings) || !$this->is_truthy_setting($asset_settings['critical_css_enabled'] ?? null)) {
            $this->builder_critical_css_ready = false;
            return false;
        }

        $css_map = $this->get_builder_critical_css_map();
        if (empty($css_map)) {
            $this->builder_critical_css_ready = false;
            return false;
        }

        foreach ($this->get_builder_critical_css_context_candidates() as $candidate) {
            $css = $css_map[$candidate] ?? '';
            if (is_string($css) && $css !== '' && $this->is_builder_layout_critical_css($css)) {
                $this->builder_critical_css_ready = true;
                return true;
            }
        }

        $this->builder_critical_css_ready = false;
        return false;
    }

    /**
     * Allow async ownership of the main theme/builder base CSS only when the
     * current request has stronger structural critical CSS coverage.
     *
     * This is the step competitors effectively cash in once they own the
     * above-the-fold path: the base stylesheet can stop blocking because the
     * request already has enough layout/header/hero CSS inline.
     *
     * @param string              $handle_lc Lower-cased style handle.
     * @param string              $href_lc   Lower-cased style href.
     * @param array<string,mixed> $stack     Stack signature.
     * @return bool
     */
    private function can_async_owned_base_builder_stylesheet(string $handle_lc, string $href_lc, array $stack): bool {
        if (!$this->should_only_async_curated_builder_styles($stack)) {
            return false;
        }

        if (!$this->has_owned_base_builder_critical_css()) {
            return false;
        }

        foreach (['neve-style', 'elementor-frontend', 'widget-nav-menu', 'neve-elementor-widgets-styles'] as $pattern) {
            if ($pattern !== '' && strpos($handle_lc, $pattern) !== false) {
                return true;
            }
        }

        foreach ([
            '/themes/neve/',
            '/elementor/assets/css/frontend',
            '/elementor-pro/assets/css/widget-nav-menu',
            '/neve-pro-addon/includes/modules/elementor_booster/assets/css/style',
        ] as $pattern) {
            if ($pattern !== '' && strpos($href_lc, $pattern) !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * Determine whether a stylesheet controls Elementor/Neve layout-critical
     * header or responsive visibility behavior and must stay blocking.
     *
     * @param string              $handle_lc Lower-cased handle.
     * @param string              $href_lc   Lower-cased href.
     * @param array<string,mixed> $stack     Stack signature.
     * @return bool
     */
    private function is_layout_sensitive_builder_stylesheet(string $handle_lc, string $href_lc, array $stack): bool {
        if ((string) ($stack['builder'] ?? '') !== 'elementor') {
            return false;
        }

        $is_widget_image_variant = strpos($handle_lc, 'widget-image-') !== false || strpos($href_lc, '/widget-image-') !== false;
        $is_base_widget_image = !$is_widget_image_variant && (
            strpos($handle_lc, 'widget-image') !== false ||
            strpos($href_lc, '/elementor/assets/css/widget-image.') !== false
        );
        if ($is_base_widget_image) {
            return true;
        }

        $handle_patterns = [
            'neve-style',
            'elementor-frontend',
            'widget-nav-menu',
            'widget-search-form',
            'widget-heading',
            'neve-elementor-widgets-styles',
            'neve-blog-pro',
            'prorank-elementor-widgets',
        ];

        foreach ($handle_patterns as $pattern) {
            if ($pattern !== '' && strpos($handle_lc, $pattern) !== false) {
                return true;
            }
        }

        $href_patterns = [
            '/themes/neve/',
            '/elementor/assets/css/frontend',
            '/elementor/assets/css/widget-heading',
            '/elementor-pro/assets/css/widget-nav-menu',
            '/elementor-pro/assets/css/widget-search-form',
            '/neve-pro-addon/includes/modules/blog_pro/assets/style',
            '/neve-pro-addon/includes/modules/elementor_booster/assets/css/style',
            '/prorank-seo-premium/includes/integrations/elementor/assets/css/elementor-widgets.css',
            '/prorank-seo-free/includes/integrations/elementor/assets/css/elementor-widgets.css',
        ];

        foreach ($href_patterns as $pattern) {
            if ($pattern !== '' && strpos($href_lc, $pattern) !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * Whether the current request has enough structural critical CSS to move
     * the main theme/builder base stylesheets off the blocking path.
     *
     * @return bool
     */
    private function has_owned_base_builder_critical_css(): bool {
        if ($this->builder_base_css_ownership_ready !== null) {
            return $this->builder_base_css_ownership_ready;
        }

        if (!$this->has_stable_builder_critical_css()) {
            $this->builder_base_css_ownership_ready = false;
            return false;
        }

        foreach ($this->get_builder_critical_css_context_candidates() as $candidate) {
            $css = $this->get_builder_critical_css_map()[$candidate] ?? '';
            if (is_string($css) && $css !== '' && $this->is_owned_base_builder_critical_css($css)) {
                $this->builder_base_css_ownership_ready = true;
                return true;
            }
        }

        $this->builder_base_css_ownership_ready = false;
        return false;
    }

    /**
     * Check whether critical CSS is strong enough to own first paint without
     * the full blocking theme/builder base stylesheets.
     *
     * @param string $css Critical CSS candidate.
     * @return bool
     */
    private function is_owned_base_builder_critical_css(string $css): bool {
        $normalized = preg_replace('/\s+/', '', strtolower($css));
        if (!is_string($normalized) || $normalized === '') {
            return false;
        }

        $groups = [
            'body' => ['body{', 'body,', 'body.'],
            'shell' => ['#page{', '#page,', '.site{', '.site,', '.wrapper', '.e-con', '.nv-site-wrap'],
            'header' => ['.site-header', '.nv-header', '.elementor-location-header', '.header-main', '.header-top', '.header-bottom', '.header-menu-sidebar', '.builder-item--', 'header{', 'header,'],
            'nav' => ['nav{', 'nav,', '.navigation', '.nav-menu', '.menu-item', '.header-menu', '.nv-nav-wrap', '.builder-item--primary-menu'],
            'content' => ['.entry-content', '.site-content', '#primary', '.content-area', '.site-main', 'main{'],
            'builder' => ['.elementor-element-', '.elementor-widget', '.elementor-container', '.elementor-column', '.e-con'],
        ];

        $covered = [];
        foreach ($groups as $group => $patterns) {
            foreach ($patterns as $pattern) {
                if ($pattern !== '' && strpos($normalized, strtolower(str_replace(' ', '', $pattern))) !== false) {
                    $covered[$group] = true;
                    break;
                }
            }
        }

        $coverage_count = count($covered);
        if ($coverage_count < 5) {
            return false;
        }

        if (empty($covered['header']) || empty($covered['content']) || empty($covered['builder'])) {
            return false;
        }

        return strlen($normalized) >= 1400;
    }

    /**
     * Build current request context candidates for critical CSS lookup.
     *
     * @return array<int,string>
     */
    private function get_builder_critical_css_context_candidates(): array {
        $candidates = [];

        if (is_front_page()) {
            $candidates[] = 'homepage';
            $candidates[] = 'front_page';
            $front_page_id = (int) get_option('page_on_front');
            if ($front_page_id > 0) {
                $candidates[] = 'page_' . $front_page_id;
            }
        } elseif (is_home()) {
            $candidates[] = 'home';
            $candidates[] = 'blog';
            $candidates[] = 'homepage';
            $posts_page_id = (int) get_option('page_for_posts');
            if ($posts_page_id > 0) {
                $candidates[] = 'page_' . $posts_page_id;
            }
        } elseif (is_singular()) {
            $post_id = (int) get_queried_object_id();
            if ($post_id > 0) {
                $post_type = get_post_type($post_id);
                if (is_string($post_type) && $post_type !== '') {
                    $candidates[] = 'single_' . $post_type;
                    $candidates[] = $post_type . '_' . $post_id;
                }
                $candidates[] = 'page_' . $post_id;
            }
        } elseif (is_archive()) {
            $candidates[] = 'archive';
        } elseif (is_404()) {
            $candidates[] = '404';
        }

        return array_values(array_unique(array_filter($candidates, static fn($value) => is_string($value) && $value !== '')));
    }

    /**
     * Collect generated/stored critical CSS candidates for the current request.
     *
     * @return array<string,string>
     */
    private function get_builder_critical_css_map(): array {
        $css_map = [];

        $generator_data = get_option('prorank_critical_css_data', []);
        if (is_array($generator_data)) {
            $legacy_map = $generator_data['critical_css'] ?? null;
            if (is_array($legacy_map)) {
                foreach ($legacy_map as $key => $css) {
                    if (is_string($key) && is_string($css) && $css !== '') {
                        $css_map[$key] = $css;
                    }
                }
            }
        }

        foreach (['front_page', 'home', 'blog', 'archive', '404'] as $context) {
            $css = get_option('prorank_critical_css_' . $context, '');
            if (is_string($css) && $css !== '') {
                $css_map[$context] = $css;
            }
        }

        $post_id = (int) get_queried_object_id();
        if ($post_id > 0) {
            $post_type = get_post_type($post_id);
            if (is_string($post_type) && $post_type !== '') {
                $single_css = get_option('prorank_critical_css_single_' . $post_type, '');
                if (is_string($single_css) && $single_css !== '') {
                    $css_map['single_' . $post_type] = $single_css;
                }
            }
        }

        return $css_map;
    }

    /**
     * Check whether critical CSS covers enough structural layout.
     *
     * @param string $css Critical CSS candidate.
     * @return bool
     */
    private function is_builder_layout_critical_css(string $css): bool {
        $normalized = preg_replace('/\s+/', '', strtolower($css));
        if (!is_string($normalized) || $normalized === '') {
            return false;
        }

        $groups = [
            ['body{', 'body,', 'body.'],
            ['#page{', '#page,', '.site{', '.site,', '.wrapper', '.e-con'],
            ['.site-header', 'header{', 'header,', '.nv-header', '.elementor-location-header'],
            ['nav{', 'nav,', '.navigation', '.menu{', '.nav-menu', '.menu-item'],
            ['.entry-content', '.site-content', '#primary', '.content-area', '.site-main', 'main{', '.elementor-section', '.elementor-widget'],
        ];

        $covered = 0;
        foreach ($groups as $patterns) {
            foreach ($patterns as $pattern) {
                if (strpos($normalized, strtolower(str_replace(' ', '', $pattern))) !== false) {
                    $covered++;
                    break;
                }
            }
        }

        return $covered >= 3 && strlen($normalized) >= 600;
    }

    /**
     * Initialize lightweight JS defer fallback.
     *
     * This only runs when js_defer is enabled and the dedicated js_opt module
     * is NOT active (e.g. suppressed on builder sites by the compatibility
     * engine). Unlike js_opt, this path does NOT activate the output buffer,
     * does NOT touch non-enqueued inline scripts, and does NOT delay anything.
     * It simply adds the `defer` attribute to eligible enqueued scripts via
     * the `script_loader_tag` filter — the lowest-risk JS optimization.
     *
     * @return void
     */
    public function init_js_defer_fallback(): void {
        if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
            return;
        }

        // When js_opt is active in full mode it handles defer itself. In the
        // narrow governance-only mode we still need the lightweight fallback
        // because js_opt is only owning raw third-party governance.
        if ($this->is_performance_module_active('js_opt') && !$this->is_low_risk_raw_js_mode()) {
            return;
        }

        $cache_settings = get_option('prorank_cache_settings', []);
        if (!is_array($cache_settings) || !$this->is_truthy_setting($cache_settings['js_defer'] ?? null)) {
            return;
        }

        add_filter('script_loader_tag', [$this, 'apply_js_defer_fallback'], 10, 3);
    }

    /**
     * Initialize the CookieYes asset bridge.
     *
     * CookieYes cloud mode serves tiny branding/close SVGs from its CDN with
     * short cache policy. Rewrite those specific asset URLs to inline data
     * URIs early so the consent runtime keeps working without extra CDN image
     * fetches on each pageview.
     *
     * @return void
     */
    public function init_cookieyes_asset_bridge(): void {
        if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
            return;
        }

        if (!$this->should_enable_cookieyes_asset_bridge()) {
            return;
        }

        add_action('wp_head', [$this, 'print_cookieyes_asset_bridge'], 0);
    }

    /**
     * Start a dedicated Trustindex loader bridge on frontend requests.
     *
     * Keep review-widget vendor rewrites independent from the wider
     * JS-governance buffer chain so fresh uncached renders still benefit.
     *
     * @return void
     */
    public function init_trustindex_loader_bridge(): void {
        if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
            return;
        }

        $advanced_settings = $this->get_runtime_advanced_settings();
        if (!$this->is_truthy_setting($advanced_settings['delay_review_widgets'] ?? null)) {
            return;
        }

        add_action('template_redirect', [$this, 'start_trustindex_loader_bridge'], 0);
    }

    /**
     * Start the dedicated Trustindex bridge buffer when the request is safe.
     *
     * @return void
     */
    public function start_trustindex_loader_bridge(): void {
        if (is_user_logged_in()) {
            return;
        }

        if (is_preview() || is_customize_preview() || is_feed()) {
            return;
        }

        ob_start([$this, 'process_trustindex_loader_bridge']);
    }

    /**
     * Dedupe/localize Trustindex loader tags outside the generic buffer path.
     *
     * @param string $html
     * @return string
     */
    public function process_trustindex_loader_bridge(string $html): string {
        if ($html === '' || stripos($html, 'cdn.trustindex.io/loader.js') === false) {
            return $html;
        }

        try {
            $defer = new ThirdPartyDefer();
            $html = $defer->dedupe_trustindex_loader_scripts($html);
            $html = $defer->localize_trustindex_loader_scripts($html);
        } catch (\Throwable $e) {
            prorank_log('[ProRank SEO] Trustindex bridge failed: ' . $e->getMessage());
            return $html;
        }

        return $html;
    }

    /**
     * Whether the CookieYes asset bridge should run.
     *
     * @return bool
     */
    private function should_enable_cookieyes_asset_bridge(): bool {
        $plugins = $this->get_active_plugin_identifiers();
        if (!in_array('cookie-law-info/cookie-law-info.php', $plugins, true)) {
            return false;
        }

        $settings = get_option('cky_settings', []);
        if (!is_array($settings)) {
            return false;
        }

        $account = $settings['account'] ?? [];
        if (!is_array($account)) {
            return false;
        }

        return !empty($account['connected']) && !empty($account['website_key']);
    }

    /**
     * Print an early runtime shim that rewrites CookieYes cloud asset URLs.
     *
     * @return void
     */
    public function print_cookieyes_asset_bridge(): void {
        $powered_svg = 'data:image/svg+xml;base64, ';
        $close_svg   = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iMTAiIHZpZXdCb3g9IjAgMCAxMCAxMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTAuNTcyODk5IDAuMDAzMjcyMDlDMC40NTk2OTEgMC4wMDMyMDAzMiAwLjM0OTAwNiAwLjAzNjcxNiAwLjI1NDg1NCAwLjA5OTU3NzFDMC4xNjA3MDEgMC4xNjI0MzggMC4wODczMTQ2IDAuMjUxODE4IDAuMDQzOTgxOSAwLjM1NjQwNUMwLjAwMDY0OTIyOCAwLjQ2MDk5MiAtMC4wMTA2ODE0IDAuNTc2MDg0IDAuMDExNDI0MiAwLjY4NzExM0MwLjAzMzUyOTkgMC43OTgxNDIgMC4wODgwNzc5IDAuOTAwMTE4IDAuMTY4MTY0IDAuOTgwMTMyTDQuMTg5MjggNUwwLjE2ODE2NCA5LjAxOTg3QzAuMDYwNDkwNSA5LjEyNzU0IDAgOS4yNzM1OCAwIDkuNDI1ODVDMCA5LjU3ODEyIDAuMDYwNDkwNSA5LjcyNDE2IDAuMTY4MTY0IDkuODMxODRDMC4yNzU4MzggOS45Mzk1MSAwLjQyMTg3NSAxMCAwLjU3NDE0OCAxMEMwLjcyNjQyMiAxMCAwLjg3MjQ1OSA5LjkzOTUxIDAuOTgwMTMzIDkuODMxODRMNS4wMDEyNSA1LjgxMTk3TDkuMDIyMzcgOS44MzE4NEM5LjEzMDIzIDkuOTM4MzYgOS4yNzU1IDkuOTk4NDQgOS40MjcxIDkuOTk5MjNDOS41MDIzIDkuOTk5NTggOS41NzY4MSA5Ljk4NDk3IDkuNjQ2MyA5Ljk1NjIzQzkuNzE1NzkgOS45Mjc0OSA5Ljc3ODg2IDkuODg1MiA5LjgzMTg0IDkuODMxODRDOS45MzkyNCA5LjcyNDAyIDkuOTk5NTUgOS41NzgwNCA5Ljk5OTU1IDkuNDI1ODVDOS45OTk1NSA5LjI3MzY3IDkuOTM5MjQgOS4xMjc2OCA5LjgzMTg0IDkuMDE5ODdMNS44MTA3MiA1TDkuODMxODQgMC45ODAxMzJDOS44ODUxNSAwLjkyNjgxOCA5LjkyNzQ0IDAuODYzNTI0IDkuOTU2MyAwLjc5Mzg2NUM5Ljk4NTE1IDAuNzI0MjA2IDEwIDAuNjQ5NTQ3IDEwIDAuNTc0MTQ4QzEwIDAuNDk4NzUgOS45ODUxNSAwLjQyNDA5IDkuOTU2MyAwLjM1NDQzMUM5LjkyNzQ0IDAuMjg0NzcyIDkuODg1MTUgMC4yMjE0NzkgOS44MzE4NCAwLjE2ODE2NEM5Ljc3ODUyIDAuMTE0ODQ5IDkuNzE1MjMgMC4wNzI1NTggOS42NDU1NyAwLjA0MzcwNDRDOS41NzU5MSAwLjAxNDg1MDcgOS41MDEyNSAwIDkuNDI1ODUgMEM5LjM1MDQ1IDAgOS4yNzU3OSAwLjAxNDg1MDcgOS4yMDYxNCAwLjA0MzcwNDRDOS4xMzY0OCAwLjA3MjU1OCA5LjA3MzE4IDAuMTE0ODQ5IDkuMDE5ODcgMC4xNjgxNjRMNC45OTgxMyA0LjE5MDUzTDAuOTc2Mzg1IDAuMTcwNjYyQzAuODY4OTAxIDAuMDYzNTY0MiAwLjcyMzM4MyAwLjAwMzM4MTEzIDAuNTcxNjUgMC4wMDMyNzIwOUgwLjU3Mjg5OVoiIGZpbGw9IiM4MjhFQTIiLz4KPC9zdmc+';
        ?>
        <script id="prorank-cookieyes-asset-bridge">
        (function() {
            if (window.__prorankCookieYesAssetBridge) {
                return;
            }
            window.__prorankCookieYesAssetBridge = true;

            var poweredSvg = <?php echo wp_json_encode($powered_svg); ?>;
            var closeSvg = <?php echo wp_json_encode($close_svg); ?>;
            var patterns = [
                { regex: /https?:\/\/cdn-cookieyes\.com\/(?:assets\/)?images\/poweredbtcky\.svg/gi, value: poweredSvg },
                { regex: /https?:\/\/cdn-cookieyes\.com\/(?:assets\/)?images\/close\.svg/gi, value: closeSvg }
            ];
            var googleProviderPatterns = [
                /googletagmanager\.com/i,
                /google-analytics\.com/i,
                /analytics\.google\.com/i
            ];

            function rewriteValue(value) {
                if (typeof value !== 'string' || value === '') {
                    return value;
                }

                var rewritten = value;
                patterns.forEach(function(entry) {
                    rewritten = rewritten.replace(entry.regex, entry.value);
                });
                return rewritten;
            }

            function patchStyleDeclaration(style) {
                if (!style) {
                    return;
                }

                ['background', 'backgroundImage', 'content', 'maskImage', 'webkitMaskImage'].forEach(function(prop) {
                    try {
                        if (typeof style[prop] === 'string' && style[prop] !== '') {
                            var next = rewriteValue(style[prop]);
                            if (next !== style[prop]) {
                                style[prop] = next;
                            }
                        }
                    } catch (e) {}
                });
            }

            function allowCookieYesGoogleConsentMode() {
                try {
                    if (!window._ckyGcm || window._ckyGcm.status !== true) {
                        return false;
                    }

                    var cookieYesStore = null;
                    if (window.cookieyes && window.cookieyes._ckyStore && Array.isArray(window.cookieyes._ckyStore._providersToBlock)) {
                        cookieYesStore = window.cookieyes._ckyStore;
                    } else if (window._ckyStore && Array.isArray(window._ckyStore._providersToBlock)) {
                        cookieYesStore = window._ckyStore;
                    } else if (window._ckyConfig && Array.isArray(window._ckyConfig._providersToBlock)) {
                        cookieYesStore = window._ckyConfig;
                    }

                    if (!cookieYesStore) {
                        return false;
                    }

                    var filteredProviders = cookieYesStore._providersToBlock.filter(function(provider) {
                        var matcher = '';
                        if (provider) {
                            if (provider.url) {
                                matcher = String(provider.url);
                            } else if (provider.re) {
                                matcher = String(provider.re);
                            }
                        }

                        if (matcher === '') {
                            return true;
                        }

                        return !googleProviderPatterns.some(function(pattern) {
                            return pattern.test(matcher);
                        });
                    });

                    if (filteredProviders.length === cookieYesStore._providersToBlock.length) {
                        return false;
                    }

                    cookieYesStore._providersToBlock.splice(0, cookieYesStore._providersToBlock.length);
                    filteredProviders.forEach(function(provider) {
                        cookieYesStore._providersToBlock.push(provider);
                    });

                    return true;
                } catch (e) {
                    return false;
                }
            }

            function scheduleCookieYesGoogleConsentMode() {
                if (allowCookieYesGoogleConsentMode()) {
                    return;
                }

                var attempts = 0;
                var timer = window.setInterval(function() {
                    attempts += 1;
                    if (allowCookieYesGoogleConsentMode() || attempts >= 40) {
                        window.clearInterval(timer);
                    }
                }, 100);
            }

            function patchNode(node) {
                if (!node || node.nodeType !== 1) {
                    return;
                }

                ['src', 'href', 'data-src', 'srcset', 'style'].forEach(function(attr) {
                    try {
                        if (!node.hasAttribute(attr)) {
                            return;
                        }
                        var current = node.getAttribute(attr);
                        var next = rewriteValue(current);
                        if (next !== current) {
                            node.setAttribute(attr, next);
                        }
                    } catch (e) {}
                });

                patchStyleDeclaration(node.style);

                if (node.querySelectorAll) {
                    node.querySelectorAll('[src],[href],[data-src],[srcset],[style]').forEach(function(child) {
                        patchNode(child);
                    });
                }
            }

            var originalSetAttribute = Element.prototype.setAttribute;
            Element.prototype.setAttribute = function(name, value) {
                if (typeof value === 'string' && /^(src|href|data-src|srcset|style)$/i.test(name)) {
                    value = rewriteValue(value);
                }
                return originalSetAttribute.call(this, name, value);
            };

            var originalInsertAdjacentHTML = Element.prototype.insertAdjacentHTML;
            Element.prototype.insertAdjacentHTML = function(position, html) {
                return originalInsertAdjacentHTML.call(this, position, rewriteValue(html));
            };

            var innerHtmlDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
            if (innerHtmlDescriptor && typeof innerHtmlDescriptor.set === 'function') {
                Object.defineProperty(Element.prototype, 'innerHTML', {
                    configurable: true,
                    enumerable: innerHtmlDescriptor.enumerable,
                    get: innerHtmlDescriptor.get,
                    set: function(value) {
                        return innerHtmlDescriptor.set.call(this, rewriteValue(value));
                    }
                });
            }

            var imageSrcDescriptor = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, 'src');
            if (imageSrcDescriptor && typeof imageSrcDescriptor.set === 'function') {
                Object.defineProperty(HTMLImageElement.prototype, 'src', {
                    configurable: true,
                    enumerable: imageSrcDescriptor.enumerable,
                    get: imageSrcDescriptor.get,
                    set: function(value) {
                        return imageSrcDescriptor.set.call(this, rewriteValue(value));
                    }
                });
            }

            var cssSetProperty = CSSStyleDeclaration.prototype.setProperty;
            CSSStyleDeclaration.prototype.setProperty = function(propertyName, value, priority) {
                if (typeof value === 'string') {
                    value = rewriteValue(value);
                }
                return cssSetProperty.call(this, propertyName, value, priority);
            };

            patchNode(document.documentElement);

            var observer = new MutationObserver(function(mutations) {
                mutations.forEach(function(mutation) {
                    if (mutation.type === 'attributes' && mutation.target) {
                        patchNode(mutation.target);
                        return;
                    }

                    mutation.addedNodes.forEach(function(node) {
                        patchNode(node);
                    });
                });
            });

            observer.observe(document.documentElement, {
                childList: true,
                subtree: true,
                attributes: true,
                attributeFilter: ['src', 'href', 'data-src', 'srcset', 'style']
            });

            scheduleCookieYesGoogleConsentMode();
        })();
        </script>
        <?php
    }

    /**
     * Initialize the Elementor visibility bridge for above-the-fold animations.
     *
     * Elementor keeps animated widgets hidden until its frontend runtime
     * initializes. When ProRank defers that runtime, the hero copy can remain
     * invisible long after the hero image has loaded, which harms mobile LCP.
     * Bridge that gap narrowly for viewport-visible widgets only.
     *
     * @return void
     */
    public function init_elementor_visibility_bridge(): void {
        if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
            return;
        }

        if (!$this->should_enable_elementor_visibility_bridge()) {
            return;
        }

        add_action('wp_head', [$this, 'print_elementor_visibility_bridge'], 3);
    }

    /**
     * Determine whether the Elementor visibility bridge should run.
     *
     * @return bool
     */
    private function should_enable_elementor_visibility_bridge(): bool {
        if (!defined('ELEMENTOR_VERSION')) {
            return false;
        }

        $stack = $this->get_performance_stack_signature();
        if ((string) ($stack['builder'] ?? '') !== 'elementor') {
            return false;
        }

        $cache_settings = get_option('prorank_cache_settings', []);
        $asset_settings = get_option('prorank_asset_optimization_settings', []);

        if (!is_array($cache_settings)) {
            $cache_settings = [];
        }

        if (!is_array($asset_settings)) {
            $asset_settings = [];
        }

        return $this->is_truthy_setting($cache_settings['js_defer'] ?? null)
            || $this->is_truthy_setting($asset_settings['css_async'] ?? null)
            || $this->is_low_risk_raw_js_mode();
    }

    /**
     * Print a lightweight bridge that reveals above-the-fold Elementor widgets
     * as soon as they exist in the DOM.
     *
     * @return void
     */
    public function print_elementor_visibility_bridge(): void {
        ?>
        <script id="prorank-elementor-visibility-bridge">
        (function() {
            if (window.__prorankElementorVisibilityBridge) {
                return;
            }
            window.__prorankElementorVisibilityBridge = true;

            var stopAt = Date.now() + 8000;
            var domObserver = null;
            var visibilityObserver = null;
            var queued = false;
            var revealed = typeof WeakSet !== 'undefined' ? new WeakSet() : null;

            function getDeviceMode() {
                if (window.matchMedia && window.matchMedia('(max-width: 767px)').matches) {
                    return 'mobile';
                }

                if (window.matchMedia && window.matchMedia('(max-width: 1024px)').matches) {
                    return 'tablet';
                }

                return 'desktop';
            }

            function getAnimationKeys() {
                var prefixes = [''];
                var mode = getDeviceMode();

                if (mode === 'mobile') {
                    prefixes.unshift('_mobile');
                    prefixes.unshift('_tablet');
                    prefixes.unshift('_desktop');
                } else if (mode === 'tablet') {
                    prefixes.unshift('_tablet');
                    prefixes.unshift('_desktop');
                } else {
                    prefixes.unshift('_desktop');
                }

                var keys = [];
                ['animation', '_animation'].forEach(function(base) {
                    prefixes.forEach(function(suffix) {
                        keys.push(base + suffix);
                    });
                });

                return keys;
            }

            function parseSettings(element) {
                if (!element || !element.dataset || !element.dataset.settings) {
                    return null;
                }

                try {
                    return JSON.parse(element.dataset.settings);
                } catch (e) {
                    return null;
                }
            }

            function shouldHandle(element) {
                if (!element || element.dataset.prorankVisibilityDone === '1') {
                    return false;
                }

                if (revealed && revealed.has(element)) {
                    return false;
                }

                return true;
            }

            function persistSettings(element, settings) {
                if (!element || !settings) {
                    return;
                }

                ['animation', '_animation', 'animation_mobile', '_animation_mobile', 'animation_tablet', '_animation_tablet', 'animation_desktop', '_animation_desktop'].forEach(function(key) {
                    delete settings[key];
                });

                element.dataset.settings = JSON.stringify(settings);
            }

            function revealElement(element) {
                if (!shouldHandle(element)) {
                    return;
                }

                if (revealed) {
                    revealed.add(element);
                }

                if (visibilityObserver) {
                    visibilityObserver.unobserve(element);
                }

                var settings = parseSettings(element);
                var animation = '';

                if (settings) {
                    getAnimationKeys().some(function(key) {
                        if (settings[key]) {
                            animation = settings[key];
                            return true;
                        }
                        return false;
                    });
                }

                var apply = function() {
                    element.classList.remove('elementor-invisible');
                    element.style.visibility = 'visible';
                    element.style.opacity = '1';
                    element.style.animation = 'none';
                    element.style.animationDelay = '0s';
                    element.style.animationDuration = '0s';

                    if (animation && animation !== 'none') {
                        element.classList.remove(animation);
                    }
                    element.classList.remove('animated');

                    if (settings) {
                        persistSettings(element, settings);
                    }

                    element.dataset.prorankVisibilityDone = '1';
                };

                apply();
            }

            function collectInvisibleElements(root) {
                var elements = [];

                if (!root) {
                    return elements;
                }

                if (root.nodeType === 1 && root.matches && root.matches('.elementor-invisible, .elementor-element.animated, .elementor-widget.animated')) {
                    elements.push(root);
                }

                if (root.querySelectorAll) {
                    root.querySelectorAll('.elementor-invisible, .elementor-element.animated, .elementor-widget.animated').forEach(function(element) {
                        elements.push(element);
                    });
                }

                return elements;
            }

            function processCandidates(root) {
                var toReveal = [];

                collectInvisibleElements(root || document).forEach(function(element) {
                    if (!shouldHandle(element)) {
                        return;
                    }

                    if (!visibilityObserver) {
                        toReveal.push(element);
                        return;
                    }

                    if (element.dataset.prorankVisibilityObserved !== '1') {
                        element.dataset.prorankVisibilityObserved = '1';
                        visibilityObserver.observe(element);
                    }
                });

                toReveal.forEach(revealElement);

                if (Date.now() > stopAt) {
                    teardown();
                }
            }

            function queueProcess(root) {
                if (Date.now() > stopAt) {
                    teardown();
                    return;
                }

                if (queued) {
                    return;
                }

                queued = true;

                var runner = function() {
                    queued = false;
                    processCandidates(root || document);
                };

                if (window.requestAnimationFrame) {
                    window.requestAnimationFrame(runner);
                    return;
                }

                window.setTimeout(runner, 0);
            }

            function teardown() {
                if (domObserver) {
                    domObserver.disconnect();
                    domObserver = null;
                }

                if (visibilityObserver) {
                    visibilityObserver.disconnect();
                    visibilityObserver = null;
                }
            }

            function start() {
                if ('IntersectionObserver' in window) {
                    visibilityObserver = new IntersectionObserver(function(entries) {
                        entries.forEach(function(entry) {
                            if (entry.isIntersecting) {
                                revealElement(entry.target);
                            }
                        });
                    }, {
                        root: null,
                        rootMargin: '64px 0px 20% 0px',
                        threshold: 0
                    });
                }

                queueProcess(document);

                if (!domObserver && document.documentElement) {
                    domObserver = new MutationObserver(function(mutations) {
                        mutations.forEach(function(mutation) {
                            mutation.addedNodes.forEach(function(node) {
                                if (node && node.nodeType === 1) {
                                    queueProcess(node);
                                }
                            });
                        });
                    });
                    domObserver.observe(document.documentElement, {
                        childList: true,
                        subtree: true,
                        attributes: true,
                        attributeFilter: ['class']
                    });
                }
            }

            start();
            window.addEventListener('load', function() { queueProcess(document); }, { passive: true, once: true });
            document.addEventListener('DOMContentLoaded', function() { queueProcess(document); }, { passive: true, once: true });
            window.setTimeout(function() { queueProcess(document); }, 800);
        })();
        </script>
        <?php
    }

    /**
     * Initialize the inline-script execution contract guard.
     *
     * Some themes register a script as async while also attaching synchronous
     * inline `after` code that expects jQuery or the main runtime to already
     * exist. Guard that centrally so ProRank can stabilize those requests
     * without theme-specific patches on live sites.
     *
     * @return void
     */
    public function init_inline_script_contract_guard(): void {
        if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
            return;
        }

        if (!$this->should_enable_inline_script_contract_guard()) {
            return;
        }

        add_action('wp_enqueue_scripts', [$this, 'repair_invalid_async_inline_script_contracts'], 10000);
        add_action('wp_print_footer_scripts', [$this, 'print_inline_contract_jquery_core_fallback'], 0);

        Buffer::init();
        add_filter('prorank_output_buffer', [$this, 'repair_invalid_async_inline_script_contracts_in_html'], 11);
    }

    /**
     * Whether the inline-script contract guard should run.
     *
     * @return bool
     */
    private function should_enable_inline_script_contract_guard(): bool {
        $advanced_settings = $this->get_runtime_advanced_settings();

        return $this->is_performance_compatibility_mode_enabled()
            || $this->is_truthy_setting($advanced_settings['structural_guard_enabled'] ?? null);
    }

    /**
     * Repair invalid async contracts before WordPress prints scripts.
     *
     * If a handle has inline `after` code that references jQuery, it cannot be
     * async unless it also owns a correct dependency graph. Remove the async
     * flag and add the jQuery dependency so WordPress prints a valid order.
     *
     * @return void
     */
    public function repair_invalid_async_inline_script_contracts(): void {
        $wp_scripts = wp_scripts();
        if (!is_object($wp_scripts)) {
            return;
        }

        $handles = $this->get_inline_after_jquery_handles();
        $this->inline_script_contract_handles = $handles;
        foreach ($handles as $handle) {
            if (!isset($wp_scripts->registered[$handle])) {
                continue;
            }

            $script = $wp_scripts->registered[$handle];
            $deps = is_array($script->deps) ? $script->deps : [];
            if (!in_array('jquery', $deps, true) && isset($wp_scripts->registered['jquery'])) {
                $deps[] = 'jquery';
                $script->deps = array_values(array_unique($deps));
                wp_enqueue_script('jquery');
            }

            if (is_array($script->extra)) {
                unset($script->extra['async'], $script->extra['strategy']);
            }

            $wp_scripts->add_data($handle, 'async', false);
            $wp_scripts->add_data($handle, 'strategy', false);
        }

    }

    /**
     * Patch known theme inline-after jQuery contracts into lazy-safe binders.
     *
     * This narrows one of the last synchronous head blockers on Neve sites:
     * the added_to_cart listener is not needed for first paint, so it can wait
     * for jQuery to become available later without breaking functionality.
     *
     * @return void
     */
    public function patch_known_inline_after_jquery_contracts(): void {
        if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
            return;
        }

        $wp_scripts = wp_scripts();
        if (!is_object($wp_scripts) || !isset($wp_scripts->registered['neve-script'])) {
            return;
        }

        $extra = $wp_scripts->registered['neve-script']->extra ?? [];
        $after = isset($extra['after']) && is_array($extra['after']) ? $extra['after'] : [];
        if ($after === []) {
            return;
        }

        $changed = false;
        foreach ($after as $index => $code) {
            if (!is_string($code) || $code === '') {
                continue;
            }

            $patched = $this->rewrite_neve_added_to_cart_inline_after($code);
            if ($patched !== $code) {
                $after[$index] = $patched;
                $changed = true;
            }
        }

        if ($changed) {
            $wp_scripts->registered['neve-script']->extra['after'] = $after;
        }
    }

    /**
     * Move the jQuery chain to the footer when no head contract still needs it.
     *
     * @return void
     */
    public function move_safe_jquery_chain_to_footer(): void {
        if (is_admin() || wp_doing_ajax() || wp_doing_cron() || is_user_logged_in()) {
            return;
        }

        $cache_settings = get_option('prorank_cache_settings', []);
        if (!is_array($cache_settings) || !$this->is_truthy_setting($cache_settings['js_defer'] ?? null)) {
            return;
        }

        $wp_scripts = wp_scripts();
        if (!is_object($wp_scripts)) {
            return;
        }

        if ($this->has_head_inline_after_jquery_contracts($wp_scripts)) {
            return;
        }

        if ($this->has_head_jquery_dependents($wp_scripts)) {
            return;
        }

        foreach (['jquery-core', 'jquery-migrate', 'jquery'] as $handle) {
            if (!isset($wp_scripts->registered[$handle])) {
                continue;
            }

            $wp_scripts->add_data($handle, 'group', 1);
            if (!isset($wp_scripts->groups) || !is_array($wp_scripts->groups)) {
                continue;
            }

            $wp_scripts->groups[$handle] = 1;
        }
    }

    /**
     * Decide at head-print time whether jquery-core can leave the head.
     *
     * @return void
     */
    public function prepare_safe_jquery_core_footer_relocation(): void {
        $this->relocate_jquery_core_to_footer = false;

        if (is_admin() || wp_doing_ajax() || wp_doing_cron() || is_user_logged_in()) {
            return;
        }

        $cache_settings = get_option('prorank_cache_settings', []);
        if (!is_array($cache_settings) || !$this->is_truthy_setting($cache_settings['js_defer'] ?? null)) {
            return;
        }

        $wp_scripts = wp_scripts();
        if (!is_object($wp_scripts) || !isset($wp_scripts->registered['jquery-core'])) {
            return;
        }

        if ($this->has_head_inline_after_jquery_contracts($wp_scripts)) {
            return;
        }

        if ($this->has_head_jquery_dependents($wp_scripts)) {
            return;
        }

        $this->relocate_jquery_core_to_footer = true;
    }

    /**
     * Print jquery-core before footer scripts when head relocation is active.
     *
     * @return void
     */
    public function print_safe_relocated_jquery_core(): void {
        if (!$this->relocate_jquery_core_to_footer) {
            return;
        }

        $jquery_tag = $this->build_jquery_core_script_tag();
        if ($jquery_tag === '') {
            return;
        }

        echo $jquery_tag;
    }

    /**
     * Print jquery-core explicitly before footer scripts when WordPress did
     * not emit it for a request that has an invalid async+jQuery contract.
     *
     * @return void
     */
    public function print_inline_contract_jquery_core_fallback(): void {
        $wp_scripts = wp_scripts();
        if (!is_object($wp_scripts) || !isset($wp_scripts->registered['jquery-core'])) {
            return;
        }

        $handles = $this->inline_script_contract_handles !== []
            ? $this->inline_script_contract_handles
            : $this->get_inline_after_jquery_handles();
        $pending_has_jquery = $this->has_pending_jquery_dependent_scripts($wp_scripts);

        if ($handles === [] && !$pending_has_jquery) {
            return;
        }

        $this->inline_script_contract_handles = $handles;

        if (in_array('jquery-core', (array) ($wp_scripts->done ?? []), true)) {
            return;
        }

        if (!in_array('jquery', (array) ($wp_scripts->queue ?? []), true)) {
            wp_enqueue_script('jquery');
        }

        wp_print_scripts(['jquery-core']);
    }

    /**
     * Detect queued/to-do scripts that still depend on jquery/jquery-core.
     *
     * @param object $wp_scripts
     * @return bool
     */
    private function has_pending_jquery_dependent_scripts(object $wp_scripts): bool {
        $pending = array_values(array_unique(array_merge(
            is_array($wp_scripts->queue ?? null) ? $wp_scripts->queue : [],
            is_array($wp_scripts->to_do ?? null) ? $wp_scripts->to_do : []
        )));

        foreach ($pending as $handle) {
            if (!isset($wp_scripts->registered[$handle])) {
                continue;
            }

            $deps = (array) ($wp_scripts->registered[$handle]->deps ?? []);
            if (in_array('jquery', $deps, true) || in_array('jquery-core', $deps, true)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Whether any head-group scripts still have inline-after code that
     * references jQuery.
     *
     * @param object $wp_scripts
     * @return bool
     */
    private function has_head_inline_after_jquery_contracts(object $wp_scripts): bool {
        $pending = array_values(array_unique(array_merge(
            is_array($wp_scripts->queue ?? null) ? $wp_scripts->queue : [],
            is_array($wp_scripts->to_do ?? null) ? $wp_scripts->to_do : []
        )));

        foreach ($pending as $handle) {
            if (!is_string($handle) || $handle === '') {
                continue;
            }

            if (!$this->script_after_inline_references_jquery($handle)) {
                continue;
            }

            $group = 0;
            if (isset($wp_scripts->groups[$handle])) {
                $group = (int) $wp_scripts->groups[$handle];
            } else {
                $group = (int) $wp_scripts->get_data($handle, 'group');
            }

            if ($group === 0) {
                return true;
            }
        }

        return false;
    }

    /**
     * Whether any head-group scripts still depend on jQuery.
     *
     * @param object $wp_scripts
     * @return bool
     */
    private function has_head_jquery_dependents(object $wp_scripts): bool {
        $jquery_handles = ['jquery', 'jquery-core', 'jquery-migrate'];
        $pending = array_values(array_unique(array_merge(
            is_array($wp_scripts->queue ?? null) ? $wp_scripts->queue : [],
            is_array($wp_scripts->to_do ?? null) ? $wp_scripts->to_do : []
        )));

        foreach ($pending as $handle) {
            if (in_array((string) $handle, $jquery_handles, true)) {
                continue;
            }

            if (!isset($wp_scripts->registered[$handle])) {
                continue;
            }

            $deps = (array) ($wp_scripts->registered[$handle]->deps ?? []);
            if (!in_array('jquery', $deps, true) && !in_array('jquery-core', $deps, true)) {
                continue;
            }

            $group = 0;
            if (isset($wp_scripts->groups[$handle])) {
                $group = (int) $wp_scripts->groups[$handle];
            } else {
                $group = (int) $wp_scripts->get_data($handle, 'group');
            }

            if ($group === 0) {
                return true;
            }
        }

        return false;
    }

    /**
     * Rewrite Neve's added_to_cart inline-after block into a lazy jQuery binder.
     *
     * @param string $code
     * @return string
     */
    private function rewrite_neve_added_to_cart_inline_after(string $code): string {
        if (stripos($code, 'added_to_cart') === false
            || stripos($code, 'responsive-nav-cart') === false
            || stripos($code, '})(jQuery)') === false) {
            return $code;
        }

        $replacement = "(function(){var init=function(){var q=window['j'+'Query'];if(!q||!q.fn||!q.fn.on||document.body.hasAttribute('data-prorank-neve-cart-bound')){return;}document.body.setAttribute('data-prorank-neve-cart-bound','1');q('body').on('added_to_cart',function(){var responsiveCart=document.querySelector('.responsive-nav-cart');if(responsiveCart){responsiveCart.classList.remove('cart-is-empty');}});};if(document.readyState==='complete'||document.readyState==='interactive'){setTimeout(init,0);}document.addEventListener('DOMContentLoaded',init,{once:true});window.addEventListener('load',init,{once:true});})();";

        $pattern = '~\(\s*function\s*\(\$\)\s*\{\s*\$\(\s*[\'"]body[\'"]\s*\)\.on\(\s*[\'"]added_to_cart[\'"]\s*,\s*function\s*\(\)\s*\{\s*var responsiveCart = document\.querySelector\(\s*[\'"]\.responsive-nav-cart[\'"]\s*\);\s*if\s*\(\s*responsiveCart\s*\)\s*\{\s*responsiveCart\.classList\.remove\(\s*[\'"]cart-is-empty[\'"]\s*\);\s*\}\s*\}\s*\)\s*;\s*\}\)\s*\(\s*jQuery\s*\)\s*;?~is';

        $patched = preg_replace($pattern, $replacement, $code, 1);

        return is_string($patched) ? $patched : $code;
    }

    /**
     * Repair invalid async contracts in final HTML after third-party rewrites.
     *
     * This is the last line of defence for cases where another optimizer or
     * theme rewrite drops the required jQuery tag or preserves a stale async
     * attribute after ProRank already fixed the registered script data.
     *
     * @param string $html
     * @return string
     */
    public function repair_invalid_async_inline_script_contracts_in_html(string $html): string {
        if ($html === '' || stripos($html, '<script') === false) {
            return $html;
        }

        $handles = $this->inline_script_contract_handles !== []
            ? $this->inline_script_contract_handles
            : $this->get_inline_after_jquery_handles();
        if ($handles !== []) {
            foreach ($handles as $handle) {
                $tag_id = preg_quote($handle . '-js', '/');
                $after_id = preg_quote($handle . '-js-after', '/');

                if (!preg_match('/<script\b[^>]*\bid=["\']' . $tag_id . '["\'][^>]*>/i', $html)) {
                    continue;
                }

                if (!preg_match('/<script\b[^>]*\bid=["\']' . $after_id . '["\'][^>]*>[\s\S]*?(?:\bjQuery\b|\$\s*\()[\s\S]*?<\/script>/i', $html)) {
                    continue;
                }

                $html = (string) preg_replace_callback(
                    '/<script\b([^>]*)\bid=["\']' . $tag_id . '["\']([^>]*)>/i',
                    static function (array $matches) use ($tag_id): string {
                        $attrs = trim((string) ($matches[1] ?? '') . ' id="' . preg_replace('/\\\\/', '', $tag_id) . '" ' . (string) ($matches[2] ?? ''));
                        $attrs = preg_replace('/\sasync(?:=(["\']).*?\1)?/i', '', $attrs);
                        $attrs = preg_replace('/\sdata-wp-strategy=(["\'])async\1/i', '', $attrs);
                        $attrs = preg_replace('/\s+/', ' ', trim((string) $attrs));

                        return '<script' . ($attrs !== '' ? ' ' . $attrs : '') . '>';
                    },
                    $html,
                    1
                );

                if ($this->html_contains_jquery_core_tag($html)) {
                    continue;
                }

                $jquery_tag = $this->build_jquery_core_script_tag();
                if ($jquery_tag === '') {
                    continue;
                }

                $html = (string) preg_replace(
                    '/(<script\b[^>]*\bid=["\']' . $tag_id . '["\'][^>]*>)/i',
                    $jquery_tag . "\n" . '$1',
                    $html,
                    1
                );
            }
        }

        return $this->move_safe_jquery_core_to_footer_in_html($html);
    }

    /**
     * Move jquery-core out of the head when the rendered head no longer has
     * any real synchronous jQuery contract left.
     *
     * @param string $html
     * @return string
     */
    private function move_safe_jquery_core_to_footer_in_html(string $html): string {
        if (!$this->should_move_jquery_core_to_footer_in_html($html)) {
            return $html;
        }

        if (!preg_match('/<script\b[^>]*\bid=(["\'])jquery-core-js\1[^>]*>[\s\S]*?<\/script>\s*/i', $html, $matches)) {
            return $html;
        }

        $jquery_tag = (string) ($matches[0] ?? '');
        if ($jquery_tag === '') {
            return $html;
        }

        $html = (string) preg_replace('/<script\b[^>]*\bid=(["\'])jquery-core-js\1[^>]*>[\s\S]*?<\/script>\s*/i', '', $html, 1);

        foreach ([
            '/(<script\b[^>]*\bid=["\']neve-script-js-extra["\'])/i',
            '/(<script\b[^>]*\bid=["\']elementor-webpack-runtime-js["\'])/i',
            '/(<\/body>)/i',
        ] as $anchor) {
            if (preg_match($anchor, $html) !== 1) {
                continue;
            }

            return (string) preg_replace($anchor, $jquery_tag . "\n" . '$1', $html, 1);
        }

        return $html;
    }

    /**
     * Whether the final HTML is safe for jquery-core footer relocation.
     *
     * @param string $html
     * @return bool
     */
    private function should_move_jquery_core_to_footer_in_html(string $html): bool {
        $cache_settings = get_option('prorank_cache_settings', []);
        if (!is_array($cache_settings) || !$this->is_truthy_setting($cache_settings['js_defer'] ?? null)) {
            return false;
        }

        if (stripos($html, 'id="jquery-core-js"') === false && stripos($html, "id='jquery-core-js'") === false) {
            return false;
        }

        $head_end = stripos($html, '</head>');
        if ($head_end === false) {
            return false;
        }

        $head_html = substr($html, 0, $head_end);
        if (!is_string($head_html) || $head_html === '') {
            return false;
        }

        if (preg_match('/<script\b[^>]*\bid=["\'][^"\']+-js-after["\'][^>]*>[\s\S]*?(?:\bjQuery\b|\$\s*\()[\s\S]*?<\/script>/i', $head_html) === 1) {
            return false;
        }

        return true;
    }

    /**
     * Get handles whose inline `after` payload references jQuery.
     *
     * @return array<int,string>
     */
    private function get_inline_after_jquery_handles(): array {
        $wp_scripts = wp_scripts();
        if (!is_object($wp_scripts) || !is_array($wp_scripts->registered ?? null)) {
            return [];
        }

        $handles = array_values(array_unique(array_merge(
            array_keys($wp_scripts->registered),
            is_array($wp_scripts->queue ?? null) ? $wp_scripts->queue : [],
            is_array($wp_scripts->to_do ?? null) ? $wp_scripts->to_do : []
        )));

        $matches = [];
        foreach ($handles as $handle) {
            if (!$this->script_has_inline_after($handle) || !$this->script_after_inline_references_jquery($handle)) {
                continue;
            }

            $matches[] = (string) $handle;
        }

        return $matches;
    }

    /**
     * Whether a registered script's inline `after` payload references jQuery.
     *
     * @param string $handle
     * @return bool
     */
    private function script_after_inline_references_jquery(string $handle): bool {
        if ($handle === '') {
            return false;
        }

        $wp_scripts = wp_scripts();
        if (!is_object($wp_scripts) || !isset($wp_scripts->registered[$handle])) {
            return false;
        }

        $extra = $wp_scripts->registered[$handle]->extra ?? [];
        $after = $extra['after'] ?? [];
        if (is_string($after)) {
            $after = [$after];
        }

        if (!is_array($after) || $after === []) {
            return false;
        }

        foreach ($after as $snippet) {
            if (!is_string($snippet) || $snippet === '') {
                continue;
            }

            if (preg_match('/\b(?:jQuery|\$\s*\()/i', $snippet) === 1) {
                return true;
            }
        }

        return false;
    }

    /**
     * Whether the final HTML already includes the jQuery core tag.
     *
     * @param string $html
     * @return bool
     */
    private function html_contains_jquery_core_tag(string $html): bool {
        return stripos($html, 'id="jquery-core-js"') !== false
            || stripos($html, "id='jquery-core-js'") !== false
            || stripos($html, '/wp-includes/js/jquery/jquery.min.js') !== false;
    }

    /**
     * Build the current jQuery core tag from the registered script object.
     *
     * @return string
     */
    private function build_jquery_core_script_tag(): string {
        $wp_scripts = wp_scripts();
        if (!is_object($wp_scripts) || !isset($wp_scripts->registered['jquery-core'])) {
            return '';
        }

        $registered = $wp_scripts->registered['jquery-core'];
        $src = (string) ($registered->src ?? '');
        if ($src === '') {
            return '';
        }

        if (!preg_match('#^https?://#i', $src)) {
            $src = strpos($src, '/') === 0 ? site_url($src) : site_url('/' . ltrim($src, '/'));
        }

        $version = $registered->ver ?? false;
        if (!empty($version) && strpos($src, 'ver=') === false) {
            $src = add_query_arg('ver', rawurlencode((string) $version), $src);
        }

        return wp_get_script_tag([
            'id'  => 'jquery-core-js',
            'src' => $src,
        ]);
    }

    /**
     * Add `defer` attribute to eligible enqueued script tags.
     *
     * @param string $tag    Full `<script>` tag.
     * @param string $handle Script handle.
     * @param string $src    Script source URL.
     * @return string
     */
    public function apply_js_defer_fallback(string $tag, string $handle, string $src): string {
        // Only touch external scripts (those with a src attribute).
        if ($src === '' || strpos($tag, ' src=') === false) {
            return $tag;
        }

        if ($handle === 'jquery-core' && $this->relocate_jquery_core_to_footer) {
            return '';
        }

        // Respect WordPress-managed execution strategy.
        if (stripos($tag, 'data-wp-strategy=') !== false) {
            return $tag;
        }

        // Already has defer or async — nothing to do.
        if (preg_match('/\s(?:defer|async)[\s>\/=]/i', $tag)) {
            return $tag;
        }

        // Respect opt-out attributes from other plugins/themes.
        if (stripos($tag, 'data-no-defer') !== false || stripos($tag, 'data-cfasync="false"') !== false) {
            return $tag;
        }

        if ($this->should_keep_blocking_script($handle, $src)) {
            return $tag;
        }

        // Scripts with inline 'after' code rely on the external file having
        // finished execution before the inline <script> runs. Deferring the
        // external file breaks that ordering because the inline sibling stays
        // synchronous while the external load is postponed.
        if ($this->script_has_inline_after($handle)) {
            return $tag;
        }

        // Insert defer before the closing > of the <script tag.
        return preg_replace('/(<script\b[^>]*)(>)/i', '$1 defer$2', $tag, 1);
    }

    /**
     * Whether a registered script has inline 'after' code attached.
     *
     * wp_add_inline_script( $handle, $code, 'after' ) emits a synchronous
     * <script> block immediately after the external tag. If the external tag
     * is deferred, the inline code runs first and references undefined vars.
     *
     * @param string $handle Script handle.
     * @return bool
     */
    private function script_has_inline_after(string $handle): bool {
        if ($handle === '') {
            return false;
        }

        $wp_scripts = wp_scripts();
        if (!is_object($wp_scripts) || !isset($wp_scripts->registered[$handle])) {
            return false;
        }

        $extra = $wp_scripts->registered[$handle]->extra ?? [];
        if (!is_array($extra)) {
            return false;
        }

        // 'after' is the dangerous one — it expects the external script to
        // have already executed. 'before' and 'data' (wp_localize_script)
        // run before the external tag, so they are safe with defer.
        return !empty($extra['after']);
    }

    /**
     * Decide if a script must remain render-blocking.
     *
     * Conservative for the safe profile: blocks ProRank loaders, polyfills,
     * jQuery, payment/booking/map scripts, page-builder runtime, and captcha.
     *
     * @param string $handle Script handle.
     * @param string $src    Script source URL.
     * @return bool
     */
    private function should_keep_blocking_script(string $handle, string $src): bool {
        // Keep admin-bar and logged-in UX stable.
        if (is_user_logged_in()) {
            return true;
        }

        // ProRank's own loader/facade scripts must remain synchronous.
        $prorank_sync_handles = [
            'prorank-delay-loader',
            'prorank-jquery-facade',
            'prorank-wp-shim',
            'prorank-cls-fix',
            'prorank-async-css-loader',
            'prorank-rum-beacon',
            'prorank-lcp-beacon',
            'prorank-inp-monitor',
        ];

        if (in_array($handle, $prorank_sync_handles, true)) {
            return true;
        }

        // WordPress core script packages rely on the global `wp` namespace and
        // on each other in tightly-coupled ways. Keep them synchronous in the
        // lightweight defer fallback to avoid frontend ordering regressions.
        if ($handle === 'wp-polyfill'
            || $handle === 'regenerator-runtime'
            || strpos($handle, 'wp-') === 0
            || strpos($src_lc = strtolower($src), '/wp-includes/js/') !== false) {
            return true;
        }

        // jQuery — many inline scripts assume synchronous jQuery. Deferring
        // it without a facade (which only js_opt provides) will break them.
        if (in_array($handle, ['jquery', 'jquery-core', 'jquery-migrate'], true)) {
            return true;
        }

        $src_lc = isset($src_lc) ? $src_lc : strtolower($src);
        $handle_lc = strtolower($handle);

        // Payment processors: must stay sync on checkout/booking flows.
        $payment_patterns = [
            'stripe', 'paypal', 'braintree', 'klarna', 'squareup', 'square.',
            'wc-checkout', 'woocommerce-checkout',
            '/woocommerce/assets/js/frontend/checkout',
        ];

        foreach ($payment_patterns as $p) {
            if (strpos($handle_lc, $p) !== false || strpos($src_lc, $p) !== false) {
                return true;
            }
        }

        // Booking systems: calendar/date-picker logic breaks if deferred.
        $booking_patterns = [
            'bookly', 'amelia', 'latepoint', 'booknetic',
            'simply-schedule-appointments',
        ];

        foreach ($booking_patterns as $p) {
            if (strpos($handle_lc, $p) !== false || strpos($src_lc, $p) !== false) {
                return true;
            }
        }

        // Google Maps: synchronous callback expected by many themes/plugins.
        if (strpos($src_lc, 'maps.googleapis.com/maps/api/js') !== false
            || strpos($src_lc, 'maps.gstatic.com') !== false) {
            return true;
        }

        // reCAPTCHA / hCaptcha / Turnstile: render callbacks expect sync load.
        $captcha_patterns = [
            'recaptcha', 'hcaptcha', 'challenges.cloudflare.com/turnstile',
            'google.com/recaptcha', 'gstatic.com/recaptcha',
        ];

        foreach ($captcha_patterns as $p) {
            if (strpos($handle_lc, $p) !== false || strpos($src_lc, $p) !== false) {
                return true;
            }
        }

        // Page builder runtime scripts: keep the full Thrive/Elementor/Divi
        // frontend bootstrap synchronous in the lightweight defer fallback.
        // Thrive's mobile navigation and interactive modules can fail to
        // initialize when these scripts are deferred on builder-heavy pages.
        $builder_exact_handles = PerformanceCompatibilityRegistry::get_builder_blocking_script_exact_handles();

        if (in_array($handle_lc, $builder_exact_handles, true)) {
            return true;
        }

        $builder_patterns = PerformanceCompatibilityRegistry::get_builder_blocking_script_handle_patterns();

        foreach ($builder_patterns as $p) {
            if (strpos($handle_lc, $p) !== false) {
                return true;
            }
        }

        $builder_src_patterns = PerformanceCompatibilityRegistry::get_builder_blocking_script_src_patterns();

        foreach ($builder_src_patterns as $p) {
            if (strpos($src_lc, $p) !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * Initialize RUM beacon (INP/CLS/LCP)
     */
    public function init_rum_beacon(): void {
        if (is_admin()) {
            return;
        }
        if (
            !$this->is_performance_module_active('performance_monitor')
            && !$this->is_performance_module_active('inp_optimizer')
            && !$this->is_performance_module_active('lcp_beacon')
        ) {
            return;
        }
        try {
            $rum = new RumBeacon();
            $rum->init();
        } catch (\Throwable $e) {
            prorank_log('[ProRank SEO] Failed to initialize RumBeacon: ' . $e->getMessage());
        }
    }

    /**
     * Synchronize runtime performance settings when an individual module is toggled.
     *
     * @param string $module_slug Module slug.
     * @param bool   $enabled     Enabled state.
     * @return void
     */
    public function handle_module_toggle_sync(string $module_slug, bool $enabled): void {
        // If the performance group itself was disabled, hard-disable runtime options.
        if ($module_slug === 'performance' && !$enabled) {
            $this->hard_disable_performance_runtime();
            return;
        }

        // For any individual performance module toggle, purge runtime cache so
        // stale HTML/CSS/JS artifacts don't continue to affect rendering.
        if ($this->is_performance_child_module($module_slug)) {
            if ($enabled) {
                $this->apply_performance_defaults_for_module($module_slug);
            } else {
                $this->disable_performance_runtime_for_module($module_slug);
            }
            $this->refresh_performance_runtime();
        }
    }

    /**
     * Synchronize runtime performance settings when a module group is toggled.
     *
     * @param string $group_slug Group slug.
     * @param bool   $enabled    Enabled state.
     * @return void
     */
    public function handle_module_group_toggle_sync(string $group_slug, bool $enabled): void {
        if ($group_slug !== 'performance') {
            return;
        }

        if (!$enabled) {
            $this->hard_disable_performance_runtime();
            return;
        }

        $this->apply_recommended_performance_defaults();
        $this->refresh_performance_runtime();
    }

    /**
     * Apply a conservative performance baseline when the performance group
     * is enabled. Existing explicit user values are preserved.
     *
     * @return void
     */
    private function apply_recommended_performance_defaults(): void {
        // Force a safe high-impact baseline when users explicitly enable the
        // performance group. They can still change any setting afterwards.
        $this->force_performance_option_values(
            'prorank_cache_settings',
            [
                'enabled' => true,
                'cache_enabled' => true,
                'browser_cache_enabled' => true,
                'dns_prefetch_enabled' => true,
                'dns_prefetch_hosts' => "fonts.googleapis.com\nfonts.gstatic.com\nwww.googletagmanager.com",
                'js_defer' => true,
                'css_minify' => true,
                'css_async' => true,
            ]
        );

        $this->force_performance_option_values(
            'prorank_asset_optimization_settings',
            [
                'js_minify_enabled' => true,
                'minify_html' => true,
                'remove_html_comments' => true,
                'font_display_swap' => true,
            ]
        );

        // Keep lazy-loading conservative by default to avoid CLS regressions
        // on some themes; still enable modern image delivery/compression.
        $this->force_performance_option_values(
            'prorank_image_optimization_settings',
            [
                'image_dimensions_enabled' => true,
                'webp_enabled' => true,
                'smart_compression' => true,
                'lazyload_images' => false,
                'lazyload_iframes' => false,
                'lazyload_videos' => false,
            ]
        );
    }

    /**
     * Ensure a toggled performance child module has the minimal runtime keys
     * enabled so the module takes effect immediately.
     *
     * @param string $module_slug Module slug.
     * @return void
     */
    private function apply_performance_defaults_for_module(string $module_slug): void {
        switch ($module_slug) {
            case 'cache':
            case 'modern_cache':
                $this->force_performance_option_values('prorank_cache_settings', [
                    'enabled' => true,
                    'cache_enabled' => true,
                ]);
                break;

            case 'browser_cache':
                $this->force_performance_option_values('prorank_cache_settings', [
                    'browser_cache_enabled' => true,
                ]);
                break;

            case 'resource_hints':
                $this->force_performance_option_values('prorank_cache_settings', [
                    'dns_prefetch_enabled' => true,
                ]);
                $cache_settings = get_option('prorank_cache_settings', []);
                if (is_array($cache_settings) && empty(trim((string) ($cache_settings['dns_prefetch_hosts'] ?? '')))) {
                    $cache_settings['dns_prefetch_hosts'] = "fonts.googleapis.com\nfonts.gstatic.com\nwww.googletagmanager.com";
                    update_option('prorank_cache_settings', $cache_settings);
                }
                break;

            case 'js_opt':
                $this->force_performance_option_values('prorank_cache_settings', [
                    'tracker_delay_enabled' => true,
                ]);
                $this->force_performance_option_values('prorank_asset_optimization_settings', [
                    'js_minify_enabled' => true,
                ]);
                break;

            case 'css_opt':
                $this->force_performance_option_values('prorank_cache_settings', [
                    'css_minify' => true,
                    'css_async' => true,
                ]);
                break;

            case 'font_optimization':
                $this->force_performance_option_values('prorank_asset_optimization_settings', [
                    'host_google_fonts_locally' => true,
                    'font_display_swap' => true,
                ]);
                break;

            case 'lazyload':
                $this->force_performance_option_values('prorank_image_optimization_settings', [
                    'lazyload_images' => true,
                    'lazyload_iframes' => true,
                    'lazyload_videos' => true,
                    'lazyload_enabled' => true,
                ]);
                break;

            case 'image_optimization':
                $this->force_performance_option_values('prorank_image_optimization_settings', [
                    'image_dimensions_enabled' => true,
                    'webp_enabled' => true,
                    'smart_compression' => true,
                    'automatic_optimization' => true,
                ]);
                break;

            case 'js_minify':
                $this->force_performance_option_values('prorank_asset_optimization_settings', [
                    'js_minify_enabled' => true,
                ]);
                break;

            case 'html_minify':
                $this->force_performance_option_values('prorank_asset_optimization_settings', [
                    'minify_html' => true,
                    'remove_html_comments' => true,
                ]);
                break;

            case 'inp_optimizer':
                $this->force_performance_option_values('prorank_asset_optimization_settings', [
                    'inp_optimizer_enabled' => true,
                ]);
                break;

            case 'lcp_optimizer':
            case 'lcp_beacon':
                $this->force_performance_option_values('prorank_asset_optimization_settings', [
                    'lcp_optimizer_enabled' => true,
                    'lcp_fetchpriority_enabled' => true,
                    'lcp_preload_lcp' => true,
                    'lcp_remove_above_fold_lazy' => true,
                ]);
                break;
        }
    }

    /**
     * Merge defaults into a settings option without overriding explicit values.
     *
     * @param string               $option_name Option name.
     * @param array<string, mixed> $defaults    Defaults to apply only when missing.
     * @return void
     */
    private function merge_performance_option_defaults(string $option_name, array $defaults): void {
        $settings = get_option($option_name, []);
        if (!is_array($settings)) {
            $settings = [];
        }

        $changed = false;
        foreach ($defaults as $key => $value) {
            if (!array_key_exists($key, $settings)) {
                $settings[$key] = $value;
                $changed = true;
            }
        }

        if ($changed) {
            update_option($option_name, $settings);
        }
    }

    /**
     * Force explicit settings values for a given option.
     *
     * @param string               $option_name Option name.
     * @param array<string, mixed> $values      Values to force.
     * @return void
     */
    private function force_performance_option_values(string $option_name, array $values): void {
        $settings = get_option($option_name, []);
        if (!is_array($settings)) {
            $settings = [];
        }

        $changed = false;
        foreach ($values as $key => $value) {
            if (!array_key_exists($key, $settings) || $settings[$key] !== $value) {
                $settings[$key] = $value;
                $changed = true;
            }
        }

        if ($changed) {
            update_option($option_name, $settings);
        }
    }

    /**
     * Disable runtime settings for a specific performance child module.
     *
     * Keeps Module Manager and the settings pages aligned so turning a module
     * off does not leave the underlying runtime flags enabled.
     *
     * @param string $module_slug Module slug.
     * @return void
     */
    private function disable_performance_runtime_for_module(string $module_slug): void {
        switch ($module_slug) {
            case 'cache':
            case 'modern_cache':
                $this->force_performance_option_values('prorank_cache_settings', [
                    'enabled' => false,
                    'cache_enabled' => false,
                ]);
                break;

            case 'browser_cache':
                $this->force_performance_option_values('prorank_cache_settings', [
                    'browser_cache_enabled' => false,
                ]);
                break;

            case 'cache_preload':
                $this->force_performance_option_values('prorank_cache_settings', [
                    'preload_enabled' => false,
                ]);
                break;

            case 'resource_hints':
                $this->force_performance_option_values('prorank_cache_settings', [
                    'dns_prefetch_enabled' => false,
                    'font_preload_enabled' => false,
                    'link_preload_enabled' => false,
                    'dns_prefetch_hosts' => '',
                ]);
                break;

            case 'speculation_rules':
                $this->force_performance_option_values('prorank_cache_settings', [
                    'prefetch_enabled' => false,
                    'prerender_enabled' => false,
                ]);
                break;

            case 'js_opt':
                $this->force_performance_option_values('prorank_cache_settings', [
                    'js_defer' => false,
                    'js_delay' => false,
                    'js_delay_jquery' => false,
                    'js_delay_elementor_modules' => false,
                    'tracker_delay_enabled' => false,
                ]);
                $this->force_performance_option_values('prorank_asset_optimization_settings', [
                    'js_advanced_enabled' => false,
                ]);
                break;

            case 'css_opt':
                $this->force_performance_option_values('prorank_cache_settings', [
                    'css_minify' => false,
                    'css_combine' => false,
                    'css_async' => false,
                ]);
                break;

            case 'font_optimization':
                $this->force_performance_option_values('prorank_asset_optimization_settings', [
                    'host_google_fonts_locally' => false,
                    'font_display_swap' => false,
                ]);
                break;

            case 'lazyload':
                $this->force_performance_option_values('prorank_image_optimization_settings', [
                    'lazyload_images' => false,
                    'lazyload_iframes' => false,
                    'lazyload_videos' => false,
                    'lazyload_enabled' => false,
                ]);
                break;

            case 'image_optimization':
                $this->force_performance_option_values('prorank_image_optimization_settings', [
                    'image_dimensions_enabled' => false,
                    'webp_enabled' => false,
                    'avif_enabled' => false,
                    'jxl_enabled' => false,
                    'jpegli_enabled' => false,
                    'optimize_on_upload' => false,
                    'smart_compression' => false,
                    'background_mode' => false,
                    'png_to_jpeg' => false,
                    'cmyk_to_rgb' => false,
                    'remove_exif' => false,
                    'optimize_pdf' => false,
                    'generate_retina' => false,
                    'automatic_optimization' => false,
                ]);
                break;

            case 'js_minify':
                $this->force_performance_option_values('prorank_asset_optimization_settings', [
                    'js_minify_enabled' => false,
                    'js_minify_inline' => false,
                ]);
                break;

            case 'html_minify':
                $this->force_performance_option_values('prorank_asset_optimization_settings', [
                    'minify_html' => false,
                    'remove_html_comments' => false,
                ]);
                break;

            case 'inp_optimizer':
                $this->force_performance_option_values('prorank_asset_optimization_settings', [
                    'inp_optimizer_enabled' => false,
                ]);
                break;

            case 'lcp_optimizer':
            case 'lcp_beacon':
                $this->force_performance_option_values('prorank_asset_optimization_settings', [
                    'lcp_optimizer_enabled' => false,
                    'lcp_fetchpriority_enabled' => false,
                    'lcp_preload_lcp' => false,
                    'lcp_optimize_first_image' => false,
                    'lcp_remove_above_fold_lazy' => false,
                ]);
                break;
        }
    }

    /**
     * Check whether the module belongs to the performance group.
     *
     * @param string $module_slug Module slug.
     * @return bool
     */
    private function is_performance_child_module(string $module_slug): bool {
        try {
            $mm = $this->modules();
            if (!$mm || !$mm->has_module($module_slug)) {
                return false;
            }

            $module = $mm->get_module($module_slug);
            return is_object($module)
                && method_exists($module, 'get_parent_slug')
                && $module->get_parent_slug() === 'performance';
        } catch (\Throwable $e) {
            return false;
        }
    }

    /**
     * Clear runtime performance caches after settings/rule/module changes.
     *
     * @return void
     */
    public function clear_performance_runtime_cache(): void {
        try {
            $mm = $this->modules();
            if ($mm) {
                $module_methods = [
                    'modern_cache' => ['clear_all_cache'],
                    'cache' => ['clear_all_cache'],
                    'css_optimize' => ['clear_css_cache'],
                    'js_minify' => ['clear_cache'],
                    'js_optimize' => ['clear_combine_cache'],
                ];

                foreach ($module_methods as $slug => $methods) {
                    $module = $mm->get_module($slug);
                    if (!is_object($module)) {
                        continue;
                    }
                    foreach ($methods as $method) {
                        if (method_exists($module, $method)) {
                            $module->{$method}();
                        }
                    }
                }
            }

            if (function_exists('wp_cache_flush')) {
                wp_cache_flush();
            }
        } catch (\Throwable $e) {
            prorank_log('[ProRank SEO] Failed to clear performance runtime cache: ' . $e->getMessage());
        }
    }

    /**
     * Clear runtime caches and purge supported host/proxy caches so stale HTML
     * does not survive a performance settings change.
     *
     * @return void
     */
    public function refresh_performance_runtime(): void {
        $this->clear_performance_runtime_cache();
        $this->purge_global_host_cache();
    }

    /**
     * Purge supported host/proxy caches for the site's global structural URLs.
     *
     * @return void
     */
    public function purge_global_host_cache(): void {
        try {
            $this->purge_supported_host_caches( $this->collect_global_urls() );
        } catch ( \Throwable $e ) {
            prorank_log( '[ProRank SEO] Failed to purge global host cache: ' . $e->getMessage() );
        }
    }

    /**
     * Whether the performance group is currently enabled.
     *
     * @return bool
     */
    private function is_performance_group_enabled(): bool {
        $settings = get_option('prorank_settings', []);
        if (!is_array($settings)) {
            $decoded = json_decode((string) $settings, true);
            $settings = is_array($decoded) ? $decoded : [];
        }
        $active_modules = isset($settings['active_modules']) && is_array($settings['active_modules'])
            ? $settings['active_modules']
            : [];
        if (array_key_exists('performance', $active_modules) && !$this->is_truthy_setting($active_modules['performance'])) {
            return false;
        }

        $group_states = get_option('prorank_seo_module_groups', []);
        if (is_array($group_states) && array_key_exists('performance', $group_states)) {
            return $this->is_truthy_setting($group_states['performance']);
        }

        // If no explicit group state exists yet, treat as enabled only when a
        // performance child module is active.
        $mm = $this->modules();
        if (!$mm) {
            return false;
        }
        foreach ($mm->get_modules() as $slug => $module) {
            if (is_object($module) && method_exists($module, 'get_parent_slug') && $module->get_parent_slug() === 'performance') {
                if ($mm->is_active((string) $slug)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Whether a specific performance module is active.
     *
     * @param string $slug Module slug.
     * @return bool
     */
    private function is_performance_module_active(string $slug): bool {
        if (in_array($slug, $this->get_suppressed_performance_modules(), true)) {
            return false;
        }

        try {
            $mm = $this->modules();
            if (!$mm || !$mm->has_module($slug)) {
                return false;
            }
            return $mm->is_active($slug);
        } catch (\Throwable $e) {
            return false;
        }
    }

    /**
     * Runtime safety guard for performance features.
     *
     * If the performance group is disabled, force-disable stale runtime toggles
     * and cache artifacts that may still affect frontend rendering.
     *
     * @return void
     */
    public function enforce_performance_runtime_safety(): void {
        if ($this->is_performance_group_enabled()) {
            return;
        }

        if (!$this->has_enabled_performance_runtime_flags()) {
            return;
        }

        $this->hard_disable_performance_runtime();
    }

    /**
     * Check whether any performance runtime flags are still enabled.
     *
     * @return bool
     */
    private function has_enabled_performance_runtime_flags(): bool {
        $cache_settings = get_option('prorank_cache_settings', []);
        if (is_array($cache_settings)) {
            foreach ([
                'enabled',
                'cache_enabled',
                'preload_enabled',
                'link_preload_enabled',
                'prefetch_enabled',
                'prerender_enabled',
                'browser_cache_enabled',
                'js_defer',
                'js_delay',
                'js_delay_jquery',
                'js_delay_elementor_modules',
                'css_minify',
                'css_combine',
                'css_async',
                'edge_cache_enabled',
                'preload_ai_enabled',
                'early_hints_enabled',
                'critical_css_enabled',
                'override_core',
                'link_fallback',
                'dns_prefetch_enabled',
                'font_preload_enabled',
                'htaccess_serving',
            ] as $key) {
                if ($this->is_truthy_setting($cache_settings[$key] ?? null)) {
                    return true;
                }
            }
        }

        $asset_settings = get_option('prorank_asset_optimization_settings', []);
        if (is_array($asset_settings)) {
            foreach ([
                'minify_html',
                'remove_html_comments',
                'css_minify',
                'css_combine',
                'js_minify_enabled',
                'js_minify_inline',
                'host_google_fonts_locally',
                'font_display_swap',
            ] as $key) {
                if ($this->is_truthy_setting($asset_settings[$key] ?? null)) {
                    return true;
                }
            }
        }

        $image_settings = get_option('prorank_image_optimization_settings', []);
        if (is_array($image_settings)) {
            foreach ([
                'image_dimensions_enabled',
                'optimize_on_upload',
                'lazyload_images',
                'lazyload_iframes',
                'lazyload_videos',
                'webp_enabled',
                'avif_enabled',
                'jxl_enabled',
                'smart_compression',
            ] as $key) {
                if ($this->is_truthy_setting($image_settings[$key] ?? null)) {
                    return true;
                }
            }
        }

        if ($this->is_truthy_setting(get_option('prorank_page_cache_enabled', false))) {
            return true;
        }

        if (class_exists('\ProRank\SEO\Cache\AdvancedCache') && \ProRank\SEO\Cache\AdvancedCache::is_installed()) {
            return true;
        }

        return false;
    }

    /**
     * Normalize bool-like option values.
     *
     * Strings such as "false" should not be treated as enabled.
     *
     * @param mixed $value
     * @return bool
     */
    private function is_truthy_setting($value): bool {
        if (is_bool($value)) {
            return $value;
        }

        if (is_int($value) || is_float($value)) {
            return ((int) $value) !== 0;
        }

        if (is_string($value)) {
            $normalized = strtolower(trim($value));
            if ($normalized === '' || in_array($normalized, ['0', 'false', 'no', 'off', 'disabled'], true)) {
                return false;
            }
            if (in_array($normalized, ['1', 'true', 'yes', 'on', 'enabled'], true)) {
                return true;
            }
        }

        return !empty($value);
    }

    /**
     * Reconcile performance child module flags from persisted option settings.
     *
     * Performance child modules are settings-derived runtime lanes. Their
     * active state should follow the current settings/suppression graph rather
     * than preserving stale flags from older saves.
     *
     * @return void
     */
    public function reconcile_performance_active_modules_from_settings(): void {
        if ($this->is_performance_group_explicitly_disabled()) {
            $this->normalize_explicitly_disabled_performance_group();
            return;
        }

        $this->maybe_backfill_safe_performance_defaults();

        $flags = $this->apply_performance_module_suppression($this->get_expected_performance_module_flags_from_settings());
        if (empty($flags)) {
            return;
        }

        $settings = get_option('prorank_settings', []);
        if (!is_array($settings)) {
            $decoded = json_decode((string) $settings, true);
            $settings = is_array($decoded) ? $decoded : [];
        }

        $active_modules = isset($settings['active_modules']) && is_array($settings['active_modules'])
            ? $settings['active_modules']
            : [];

        $changed = false;
        foreach ($flags as $module_slug => $enabled) {
            if (!array_key_exists($module_slug, $active_modules) || (bool) $active_modules[$module_slug] !== (bool) $enabled) {
                $active_modules[$module_slug] = (bool) $enabled;
                $changed = true;
            }
        }

        $performance_child_slugs = $this->get_performance_child_module_slugs(array_keys($active_modules));
        $performance_group_enabled = false;
        foreach ($performance_child_slugs as $module_slug) {
            if ($this->is_truthy_setting($active_modules[$module_slug] ?? false)) {
                $performance_group_enabled = true;
                break;
            }
        }

        if (!array_key_exists('performance', $active_modules) || $this->is_truthy_setting($active_modules['performance'] ?? false) !== $performance_group_enabled) {
            $active_modules['performance'] = $performance_group_enabled;
            $changed = true;
        }

        $group_states = get_option('prorank_seo_module_groups', []);
        if (!is_array($group_states)) {
            $group_states = [];
        }

        $group_changed = !array_key_exists('performance', $group_states);

        if ($changed) {
            $settings['active_modules'] = $active_modules;
            update_option('prorank_settings', $settings);
        }

        if ($group_changed) {
            $group_states['performance'] = $performance_group_enabled;
            update_option('prorank_seo_module_groups', $group_states);
        }
    }

    /**
     * Backfill safe performance defaults for older installs that already have
     * the performance group enabled but predate the current baseline.
     *
     * This only fills missing keys. It does not override explicit user choices.
     *
     * @return void
     */
    private function maybe_backfill_safe_performance_defaults(): void {
        if (!$this->is_performance_group_enabled()) {
            return;
        }

        $this->merge_performance_option_defaults(
            'prorank_cache_settings',
            [
                'browser_cache_enabled' => true,
                'preload_enabled' => true,
                'dns_prefetch_enabled' => true,
                'dns_prefetch_hosts' => "fonts.googleapis.com\nfonts.gstatic.com\nwww.googletagmanager.com",
            ]
        );

        $this->merge_performance_option_defaults(
            'prorank_asset_optimization_settings',
            [
                'font_display_swap' => true,
            ]
        );
    }

    /**
     * Apply profile- and rollback-based performance safeguards before modules boot.
     *
     * @return void
     */
    public function apply_performance_engine_safeguards(): void {
        $suppressed = $this->get_suppressed_performance_modules();
        $stack = $this->get_performance_stack_signature();

        $status = [
            'profile' => $this->get_performance_profile(),
            'compatibility_mode' => $this->is_performance_compatibility_mode_enabled(),
            'auto_rollback' => $this->is_performance_auto_rollback_enabled(),
            'stack' => $stack,
            'suppressed_modules' => $suppressed,
            'updated_at' => time(),
        ];

        if (empty($suppressed)) {
            update_option('prorank_performance_engine_status', $status, false);
            return;
        }

        $settings = get_option('prorank_settings', []);
        if (!is_array($settings)) {
            $decoded = json_decode((string) $settings, true);
            $settings = is_array($decoded) ? $decoded : [];
        }

        $active_modules = isset($settings['active_modules']) && is_array($settings['active_modules'])
            ? $settings['active_modules']
            : [];

        $changed = false;
        foreach ($suppressed as $module_slug) {
            if ($this->is_truthy_setting($active_modules[$module_slug] ?? false)) {
                $active_modules[$module_slug] = false;
                $changed = true;
            }
        }

        $performance_enabled = false;
        foreach ($this->get_performance_child_module_slugs(array_keys($active_modules)) as $module_slug) {
            if ($this->is_truthy_setting($active_modules[$module_slug] ?? false)) {
                $performance_enabled = true;
                break;
            }
        }

        if ($this->is_truthy_setting($active_modules['performance'] ?? false) !== $performance_enabled) {
            $active_modules['performance'] = $performance_enabled;
            $changed = true;
        }

        if ($changed) {
            $settings['active_modules'] = $active_modules;
            update_option('prorank_settings', $settings);
        }

        $group_states = get_option('prorank_seo_module_groups', []);
        if (!is_array($group_states)) {
            $group_states = [];
        }

        if (!array_key_exists('performance', $group_states) || $this->is_truthy_setting($group_states['performance']) !== $performance_enabled) {
            $group_states['performance'] = $performance_enabled;
            update_option('prorank_seo_module_groups', $group_states);
        }

        update_option('prorank_performance_engine_status', $status, false);
    }

    /**
     * Read performance engine settings from advanced settings with sane defaults.
     *
     * @return array<string,mixed>
     */
    private function get_performance_engine_settings(): array {
        $defaults = [
            'performance_profile' => 'safe',
            'performance_compatibility_mode' => true,
            'performance_auto_rollback' => true,
            'structural_guard_enabled' => true,
        ];

        $settings = get_option('prorank_seo_advanced_settings', []);
        if (!is_array($settings) || $settings === []) {
            $legacy = get_option('prorank_advanced_settings', []);
            if (is_array($legacy) && $legacy !== []) {
                $settings = $legacy;
            }
        }

        return wp_parse_args(is_array($settings) ? $settings : [], $defaults);
    }

    /**
     * Load runtime advanced settings with legacy fallback for governance lanes.
     *
     * @return array<string,mixed>
     */
    private function get_runtime_advanced_settings(): array {
        $settings = get_option('prorank_seo_advanced_settings', []);
        if (!is_array($settings)) {
            $settings = [];
        }

        $legacy = get_option('prorank_advanced_settings', []);
        if (!is_array($legacy) || $legacy === []) {
            return $settings;
        }

        foreach ([
            'delay_google_services',
            'delay_review_widgets',
            'defer_consent_scripts',
            'delay_identity_widgets',
        ] as $key) {
            if (!array_key_exists($key, $settings) && array_key_exists($key, $legacy)) {
                $settings[$key] = $legacy[$key];
            }
        }

        return $settings;
    }

    /**
     * Get the active performance profile.
     *
     * @return string
     */
    private function get_performance_profile(): string {
        $profile = sanitize_key((string) ($this->get_performance_engine_settings()['performance_profile'] ?? 'safe'));

        return in_array($profile, ['safe', 'balanced', 'aggressive'], true)
            ? $profile
            : 'safe';
    }

    /**
     * Whether compatibility-aware suppression is enabled.
     *
     * @return bool
     */
    private function is_performance_compatibility_mode_enabled(): bool {
        return $this->is_truthy_setting($this->get_performance_engine_settings()['performance_compatibility_mode'] ?? true);
    }

    /**
     * Whether automatic rollback is enabled.
     *
     * @return bool
     */
    private function is_performance_auto_rollback_enabled(): bool {
        return $this->is_truthy_setting($this->get_performance_engine_settings()['performance_auto_rollback'] ?? true);
    }

    /**
     * Detect the active frontend stack for compatibility decisions.
     *
     * @return array<string,mixed>
     */
    private function get_performance_stack_signature(): array {
        $plugins = $this->get_active_plugin_identifiers();
        $plugin_haystack = strtolower(implode("\n", $plugins));

        $theme = wp_get_theme();
        $theme_haystack = strtolower(implode(' ', array_filter([
            (string) $theme->get('Name'),
            (string) $theme->get_template(),
            (string) $theme->get_stylesheet(),
        ])));

        $builder = '';
        if (strpos($plugin_haystack, 'thrive-visual-editor') !== false || strpos($theme_haystack, 'thrive') !== false) {
            $builder = 'thrive';
        } elseif (strpos($plugin_haystack, 'elementor') !== false || strpos($theme_haystack, 'elementor') !== false) {
            $builder = 'elementor';
        } elseif (strpos($plugin_haystack, 'et-core') !== false || strpos($theme_haystack, 'divi') !== false) {
            $builder = 'divi';
        } elseif (strpos($plugin_haystack, 'beaver-builder-lite-version') !== false || strpos($theme_haystack, 'beaver') !== false) {
            $builder = 'beaver';
        } elseif (strpos($plugin_haystack, 'bricks') !== false || strpos($theme_haystack, 'bricks') !== false) {
            $builder = 'bricks';
        } elseif (strpos($plugin_haystack, 'oxygen') !== false || strpos($theme_haystack, 'oxygen') !== false) {
            $builder = 'oxygen';
        } elseif (strpos($plugin_haystack, 'breakdance') !== false || strpos($theme_haystack, 'breakdance') !== false) {
            $builder = 'breakdance';
        }

        $host_cache = '';
        if (strpos($plugin_haystack, 'sg-cachepress') !== false) {
            $host_cache = 'siteground';
        } elseif (strpos($plugin_haystack, 'litespeed-cache') !== false) {
            $host_cache = 'litespeed';
        } elseif (strpos($plugin_haystack, 'cloudflare') !== false) {
            $host_cache = 'cloudflare';
        }

        $cache_plugin = '';
        foreach ([
            'wp-rocket/wp-rocket.php' => 'wp-rocket',
            'litespeed-cache/litespeed-cache.php' => 'litespeed-cache',
            'sg-cachepress/sg-cachepress.php' => 'siteground-optimizer',
            'autoptimize/autoptimize.php' => 'autoptimize',
            'perfmatters/perfmatters.php' => 'perfmatters',
            'flying-press/flying-press.php' => 'flyingpress',
            'flying-scripts/flying-scripts.php' => 'flying-scripts',
            'w3-total-cache/w3-total-cache.php' => 'w3-total-cache',
            'wp-fastest-cache/wpFastestCache.php' => 'wp-fastest-cache',
            'hummingbird-performance/wp-hummingbird.php' => 'hummingbird',
            'breeze/breeze.php' => 'breeze',
            'wp-optimize/wp-optimize.php' => 'wp-optimize',
            'nitropack/nitropack.php' => 'nitropack',
            'swift-performance-lite/performance.php' => 'swift-performance-lite',
            'speedycache/speedycache.php' => 'speedycache',
            'asset-cleanup/asset-cleanup.php' => 'asset-cleanup',
            'asset-cleanup-pro/asset-cleanup.php' => 'asset-cleanup-pro',
            'async-javascript/async-javascript.php' => 'async-javascript',
            'wp-super-cache/wp-cache.php' => 'wp-super-cache',
        ] as $needle => $label) {
            if (strpos($plugin_haystack, strtolower($needle)) !== false) {
                $cache_plugin = $label;
                break;
            }
        }

        return [
            'builder' => $builder,
            'host_cache' => $host_cache,
            'cache_plugin' => $cache_plugin,
            'woocommerce' => strpos($plugin_haystack, 'woocommerce/woocommerce.php') !== false,
            'contact_forms' => strpos($plugin_haystack, 'wpforms-lite/wpforms.php') !== false
                || strpos($plugin_haystack, 'contact-form-7/wp-contact-form-7.php') !== false
                || strpos($plugin_haystack, 'gravityforms/gravityforms.php') !== false,
            'theme' => $theme_haystack,
        ];
    }

    /**
     * Get active plugin file identifiers.
     *
     * @return array<int,string>
     */
    private function get_active_plugin_identifiers(): array {
        $plugins = get_option('active_plugins', []);
        if (!is_array($plugins)) {
            $plugins = [];
        }

        if (is_multisite()) {
            $sitewide = get_site_option('active_sitewide_plugins', []);
            if (is_array($sitewide)) {
                $plugins = array_merge($plugins, array_keys($sitewide));
            }
        }

        return array_values(array_unique(array_map('strval', $plugins)));
    }

    /**
     * Return modules suppressed by profile/stack compatibility.
     *
     * @param array<string,mixed> $stack Stack signature.
     * @return array<int,string>
     */
    private function get_profile_compatibility_suppressions(array $stack): array {
        $profile = $this->get_performance_profile();
        $suppressed = [];
        $has_builder = !empty($stack['builder']);
        $has_external_cache = !empty($stack['host_cache']) || !empty($stack['cache_plugin']);
        $low_risk_raw_js_mode = $this->is_low_risk_raw_js_mode();

        if ($profile === 'safe') {
            if ($has_builder) {
                // Always suppress high-risk CSS operations on builder sites.
                $suppressed = array_merge($suppressed, ['css_opt', 'critical_css', 'unused_css']);

                // js_opt activates Buffer::init() and HTML-level script
                // rewriting, so it stays suppressed on builder sites.
                // Lightweight defer is handled separately by the
                // init_js_defer_fallback() path which only hooks
                // script_loader_tag without any output buffer mutation.
                // Allow the narrower governance/tracker-only raw path to run.
                if (!$low_risk_raw_js_mode) {
                    $suppressed[] = 'js_opt';
                }
            }

            if ($has_external_cache) {
                $suppressed = array_merge($suppressed, ['cache', 'modern_cache', 'cache_preload']);
            }
        } elseif ($profile === 'balanced') {
            if ($has_builder) {
                if (!$this->can_use_cloud_unused_css_on_builder_stack((string) ($stack['builder'] ?? ''), $stack)) {
                    $suppressed[] = 'unused_css';
                }

                if (in_array((string) ($stack['builder'] ?? ''), ['thrive', 'elementor', 'divi', 'bricks', 'oxygen', 'breakdance'], true)) {
                    $suppressed[] = 'css_opt';

                    if (!$this->can_use_cloud_critical_css_on_builder_stack((string) ($stack['builder'] ?? ''), $stack)) {
                        $suppressed[] = 'critical_css';
                    }
                }

                // Builder sites can still use the lightweight js_defer
                // fallback when js_defer is enabled, but the full js_opt
                // module performs HTML-level script rewriting and interaction
                // delay. Keep that suppressed by default on builder stacks in
                // balanced mode unless the site is using the narrow raw JS
                // governance path.
                if (!$low_risk_raw_js_mode) {
                    $suppressed[] = 'js_opt';
                }
            }

            if ($has_external_cache) {
                $suppressed = array_merge($suppressed, ['cache', 'modern_cache', 'cache_preload']);
            }
        }

        if (($profile === 'safe' || $profile === 'balanced') && !empty($stack['woocommerce']) && !$low_risk_raw_js_mode) {
            $suppressed[] = 'js_opt';
        }

        return array_values(array_unique($suppressed));
    }

    /**
     * Whether a builder stack can safely use cloud critical CSS in balanced mode.
     *
     * @param string              $builder Builder slug from stack signature.
     * @param array<string,mixed> $stack   Stack signature.
     * @return bool
     */
    private function can_use_cloud_critical_css_on_builder_stack(string $builder, array $stack): bool {
        return $builder === 'elementor' && !empty($stack['builder']);
    }

    /**
     * Whether a builder stack can safely use cloud UCSS in balanced mode.
     *
     * UCSS is only allowed here when ProRank is using the cloud analyzer. Local
     * fallback remains suppressed on builder stacks because it is broader and
     * less predictable than the cloud target-stylesheet flow.
     *
     * @param string              $builder Builder slug from stack signature.
     * @param array<string,mixed> $stack   Stack signature.
     * @return bool
     */
    private function can_use_cloud_unused_css_on_builder_stack(string $builder, array $stack): bool {
        if ($builder !== 'elementor' || empty($stack['builder'])) {
            return false;
        }

        $asset_settings = get_option('prorank_asset_optimization_settings', []);
        if (!is_array($asset_settings)) {
            return false;
        }

        return !array_key_exists('unused_css_use_cloud', $asset_settings)
            || $this->is_truthy_setting($asset_settings['unused_css_use_cloud']);
    }

    /**
     * Whether JS optimization is only being used for tracker interaction delay.
     *
     * In this mode we should not treat the site as having broad JS rewriting
     * enabled, so compatibility suppression can stay strict without blocking
     * the low-risk tracker-only path.
     *
     * @return bool
     */
    private function is_tracker_delay_only_js_mode(): bool {
        $cache_settings = get_option('prorank_cache_settings', []);
        if (!is_array($cache_settings)) {
            $cache_settings = [];
        }

        $asset_settings = get_option('prorank_asset_optimization_settings', []);
        if (!is_array($asset_settings)) {
            $asset_settings = [];
        }

        return $this->is_truthy_setting($cache_settings['tracker_delay_enabled'] ?? null)
            && !$this->is_truthy_setting($cache_settings['js_defer'] ?? null)
            && !$this->is_truthy_setting($cache_settings['js_delay'] ?? null)
            && !$this->is_truthy_setting($asset_settings['js_advanced_enabled'] ?? null);
    }

    /**
     * Whether JS is in the narrow third-party governance mode.
     *
     * @return bool
     */
    private function is_governance_only_js_mode(): bool {
        $cache_settings = get_option('prorank_cache_settings', []);
        if (!is_array($cache_settings)) {
            $cache_settings = [];
        }

        $asset_settings = get_option('prorank_asset_optimization_settings', []);
        if (!is_array($asset_settings)) {
            $asset_settings = [];
        }

        $advanced_settings = $this->get_runtime_advanced_settings();
        $governance_delay_enabled =
            $this->is_truthy_setting($advanced_settings['delay_google_services'] ?? null)
            || $this->is_truthy_setting($advanced_settings['delay_review_widgets'] ?? null)
            || $this->is_truthy_setting($advanced_settings['delay_identity_widgets'] ?? null);

        if (!$governance_delay_enabled) {
            return false;
        }

        return !$this->is_truthy_setting($cache_settings['js_delay'] ?? null)
            && !$this->is_truthy_setting($cache_settings['tracker_delay_enabled'] ?? null)
            && !$this->is_truthy_setting($asset_settings['js_advanced_enabled'] ?? null);
    }

    /**
     * Whether the current JS runtime is limited to the low-risk raw path.
     *
     * @return bool
     */
    private function is_low_risk_raw_js_mode(): bool {
        return $this->is_tracker_delay_only_js_mode() || $this->is_governance_only_js_mode();
    }

    /**
     * Return modules currently suppressed by automatic rollback.
     *
     * @return array<int,string>
     */
    private function get_temporarily_rolled_back_modules(): array {
        if (!$this->is_performance_auto_rollback_enabled()) {
            return [];
        }

        $rollbacks = get_option('prorank_performance_rollbacks', []);
        if (!is_array($rollbacks) || $rollbacks === []) {
            return [];
        }

        $now = time();
        $active = [];
        $changed = false;

        foreach ($rollbacks as $slug => $entry) {
            if (!is_array($entry)) {
                unset($rollbacks[$slug]);
                $changed = true;
                continue;
            }

            $disabled_until = (int) ($entry['disabled_until'] ?? 0);
            $last = (int) ($entry['last'] ?? 0);

            if ($disabled_until > $now) {
                $active[] = (string) $slug;
                continue;
            }

            if ($disabled_until > 0 || ($last > 0 && $last < ($now - DAY_IN_SECONDS))) {
                unset($rollbacks[$slug]);
                $changed = true;
            }
        }

        if ($changed) {
            update_option('prorank_performance_rollbacks', $rollbacks, false);
        }

        return array_values(array_unique(array_filter($active)));
    }

    /**
     * Determine strict suppressions that must always win over manual overrides.
     *
     * This includes competitor safe-mode suppressions and temporary automatic
     * rollbacks. Users can opt out of profile-based compatibility guidance, but
     * they should not be able to punch through hard safety layers.
     *
     * @return array<int,string>
     */
    private function get_strict_suppressed_performance_modules(): array {
        $suppressed = [];

        try {
            $detector = new \ProRank\SEO\Core\Compatibility\CompetitorDetector();
            if ( $detector->should_enable_performance_safe_mode() ) {
                $suppressed = array_merge( $suppressed, $detector->get_performance_safe_mode_suppressions() );
            }
        } catch ( \Throwable $e ) {
            // Ignore conflict detector failures and fall back below.
        }

        $suppressed = array_merge($suppressed, $this->get_temporarily_rolled_back_modules());

        return array_values(array_unique(array_filter($suppressed)));
    }

    /**
     * Determine modules that should be suppressed for the current environment.
     *
     * @return array<int,string>
     */
    private function get_suppressed_performance_modules(): array {
        $suppressed = $this->get_strict_suppressed_performance_modules();

        if ($this->is_performance_compatibility_mode_enabled()) {
            $suppressed = array_merge($suppressed, $this->get_profile_compatibility_suppressions($this->get_performance_stack_signature()));
        }

        return array_values(array_unique(array_filter($suppressed)));
    }

    /**
     * Apply profile- and rollback-based suppression to module flags.
     *
     * @param array<string,bool> $flags
     * @return array<string,bool>
     */
    private function apply_performance_module_suppression(array $flags): array {
        // Manual overrides are only allowed to bypass profile guidance.
        // Competitor safe mode and auto-rollbacks remain authoritative.
        $overrides = get_option('prorank_performance_module_overrides', []);
        if (!is_array($overrides)) {
            $overrides = [];
        }

        foreach ($this->get_strict_suppressed_performance_modules() as $module_slug) {
            if (array_key_exists($module_slug, $flags)) {
                $flags[$module_slug] = false;
            }
        }

        foreach ($this->is_performance_compatibility_mode_enabled()
            ? $this->get_profile_compatibility_suppressions($this->get_performance_stack_signature())
            : [] as $module_slug) {
            if (!empty($overrides[$module_slug])) {
                continue;
            }
            if (array_key_exists($module_slug, $flags)) {
                $flags[$module_slug] = false;
            }
        }

        return $flags;
    }

    /**
     * Whether the performance group has been explicitly switched off.
     *
     * @return bool
     */
    private function is_performance_group_explicitly_disabled(): bool {
        $group_states = get_option('prorank_seo_module_groups', []);
        if (is_array($group_states) && array_key_exists('performance', $group_states)) {
            return !$this->is_truthy_setting($group_states['performance']);
        }

        $settings = get_option('prorank_settings', []);
        if (!is_array($settings)) {
            $decoded = json_decode((string) $settings, true);
            $settings = is_array($decoded) ? $decoded : [];
        }

        $active_modules = isset($settings['active_modules']) && is_array($settings['active_modules'])
            ? $settings['active_modules']
            : [];

        return array_key_exists('performance', $active_modules)
            && !$this->is_truthy_setting($active_modules['performance']);
    }

    /**
     * Keep an explicitly disabled performance group disabled even when stale
     * option values from older releases are still present.
     *
     * @return void
     */
    private function normalize_explicitly_disabled_performance_group(): void {
        if ($this->has_enabled_performance_source_flags()) {
            $this->hard_disable_performance_runtime();
        }

        $settings = get_option('prorank_settings', []);
        if (!is_array($settings)) {
            $decoded = json_decode((string) $settings, true);
            $settings = is_array($decoded) ? $decoded : [];
        }

        $active_modules = isset($settings['active_modules']) && is_array($settings['active_modules'])
            ? $settings['active_modules']
            : [];

        $changed = false;
        foreach ($this->get_performance_child_module_slugs(array_keys($active_modules)) as $module_slug) {
            if (!array_key_exists($module_slug, $active_modules) || $this->is_truthy_setting($active_modules[$module_slug])) {
                $active_modules[$module_slug] = false;
                $changed = true;
            }
        }

        if (!array_key_exists('performance', $active_modules) || $this->is_truthy_setting($active_modules['performance'])) {
            $active_modules['performance'] = false;
            $changed = true;
        }

        if ($changed) {
            $settings['active_modules'] = $active_modules;
            update_option('prorank_settings', $settings);
        }

        $group_states = get_option('prorank_seo_module_groups', []);
        if (!is_array($group_states)) {
            $group_states = [];
        }

        if (!array_key_exists('performance', $group_states) || $this->is_truthy_setting($group_states['performance'])) {
            $group_states['performance'] = false;
            update_option('prorank_seo_module_groups', $group_states);
        }
    }

    /**
     * Build the expected performance child module flags from saved settings.
     *
     * @return array<string,bool>
     */
    private function get_expected_performance_module_flags_from_settings(): array {
        $cache_settings = get_option('prorank_cache_settings', []);
        if (!is_array($cache_settings)) {
            $cache_settings = [];
        }

        $asset_settings = get_option('prorank_asset_optimization_settings', []);
        if (!is_array($asset_settings)) {
            $asset_settings = [];
        }

        $image_settings = get_option('prorank_image_optimization_settings', []);
        if (!is_array($image_settings)) {
            $image_settings = [];
        }

        $page_cache_enabled = $this->is_truthy_setting(get_option('prorank_page_cache_enabled', false));
        $cache_enabled = $page_cache_enabled
            || $this->is_truthy_setting($cache_settings['cache_enabled'] ?? null)
            || $this->is_truthy_setting($cache_settings['enabled'] ?? null);
        $advanced_settings = $this->get_runtime_advanced_settings();
        $js_governance_delay_enabled =
            $this->is_truthy_setting($advanced_settings['delay_google_services'] ?? null)
            || $this->is_truthy_setting($advanced_settings['delay_review_widgets'] ?? null)
            || $this->is_truthy_setting($advanced_settings['delay_identity_widgets'] ?? null);

        return [
            'cache' => $cache_enabled,
            'modern_cache' => $cache_enabled,
            'browser_cache' => $this->is_truthy_setting($cache_settings['browser_cache_enabled'] ?? null),
            'cache_preload' => $this->is_truthy_setting($cache_settings['preload_enabled'] ?? null),
            'resource_hints' => $this->is_truthy_setting($cache_settings['dns_prefetch_enabled'] ?? null)
                || $this->is_truthy_setting($cache_settings['font_preload_enabled'] ?? null)
                || $this->is_truthy_setting($cache_settings['link_preload_enabled'] ?? null),
            'speculation_rules' => $this->is_truthy_setting($cache_settings['prefetch_enabled'] ?? null)
                || $this->is_truthy_setting($cache_settings['prerender_enabled'] ?? null),
            // css_async alone uses the lightweight style_loader_tag fallback.
            // Only boot the full css_opt module for higher-impact CSS work.
            'css_opt' => $this->is_truthy_setting($cache_settings['css_minify'] ?? null)
                || $this->is_truthy_setting($cache_settings['css_combine'] ?? null),
            'js_opt' => $this->is_truthy_setting($cache_settings['js_defer'] ?? null)
                || $this->is_truthy_setting($cache_settings['js_delay'] ?? null)
                || $this->is_truthy_setting($cache_settings['tracker_delay_enabled'] ?? null)
                || $js_governance_delay_enabled,
            'image_optimization' => $this->is_truthy_setting($image_settings['image_dimensions_enabled'] ?? null)
                || $this->is_truthy_setting($image_settings['webp_enabled'] ?? null)
                || $this->is_truthy_setting($image_settings['avif_enabled'] ?? null)
                || $this->is_truthy_setting($image_settings['jxl_enabled'] ?? null)
                || $this->is_truthy_setting($image_settings['jpegli_enabled'] ?? null)
                || $this->is_truthy_setting($image_settings['optimize_on_upload'] ?? null)
                || $this->is_truthy_setting($image_settings['smart_compression'] ?? null),
            'lazyload' => $this->is_lazyload_enabled_in_image_settings($image_settings),
            'js_minify' => $this->is_truthy_setting($asset_settings['js_minify_enabled'] ?? null),
            'html_minify' => $this->is_truthy_setting($asset_settings['minify_html'] ?? null)
                || $this->is_truthy_setting($asset_settings['remove_html_comments'] ?? null),
            'font_optimization' => $this->is_truthy_setting($asset_settings['host_google_fonts_locally'] ?? null)
                || $this->is_truthy_setting($asset_settings['font_display_swap'] ?? null),
            'inp_optimizer' => $this->is_truthy_setting($asset_settings['inp_optimizer_enabled'] ?? null),
            'lcp_beacon' => $this->is_truthy_setting($asset_settings['lcp_optimizer_enabled'] ?? null)
                || $this->is_truthy_setting($asset_settings['lcp_fetchpriority_enabled'] ?? null)
                || $this->is_truthy_setting($asset_settings['lcp_preload_lcp'] ?? null)
                || $this->is_truthy_setting($asset_settings['lcp_optimize_first_image'] ?? null)
                || $this->is_truthy_setting($asset_settings['lcp_remove_above_fold_lazy'] ?? null),
            'lcp_optimizer' => $this->is_truthy_setting($asset_settings['lcp_optimizer_enabled'] ?? null)
                || $this->is_truthy_setting($asset_settings['lcp_fetchpriority_enabled'] ?? null)
                || $this->is_truthy_setting($asset_settings['lcp_preload_lcp'] ?? null)
                || $this->is_truthy_setting($asset_settings['lcp_optimize_first_image'] ?? null)
                || $this->is_truthy_setting($asset_settings['lcp_remove_above_fold_lazy'] ?? null),
        ];
    }

    /**
     * Whether any underlying performance source setting is still enabled.
     *
     * @return bool
     */
    private function has_enabled_performance_source_flags(): bool {
        $cache_settings = get_option('prorank_cache_settings', []);
        if (!is_array($cache_settings)) {
            $cache_settings = [];
        }

        foreach ([
            'enabled',
            'cache_enabled',
            'preload_enabled',
            'link_preload_enabled',
            'prefetch_enabled',
            'prerender_enabled',
            'browser_cache_enabled',
            'js_defer',
            'js_delay',
            'js_delay_jquery',
            'js_delay_elementor_modules',
            'tracker_delay_enabled',
            'css_minify',
            'css_combine',
            'css_async',
            'edge_cache_enabled',
            'preload_ai_enabled',
            'early_hints_enabled',
            'override_core',
            'link_fallback',
            'dns_prefetch_enabled',
            'font_preload_enabled',
            'htaccess_serving',
        ] as $key) {
            if ($this->is_truthy_setting($cache_settings[$key] ?? null)) {
                return true;
            }
        }

        if (trim((string) ($cache_settings['dns_prefetch_hosts'] ?? '')) !== '') {
            return true;
        }

        $asset_settings = get_option('prorank_asset_optimization_settings', []);
        if (!is_array($asset_settings)) {
            $asset_settings = [];
        }

        foreach ([
            'minify_html',
            'remove_html_comments',
            'css_minify',
            'css_combine',
            'js_advanced_enabled',
            'js_minify_enabled',
            'js_minify_inline',
            'host_google_fonts_locally',
            'font_display_swap',
            'inp_optimizer_enabled',
            'lcp_optimizer_enabled',
            'lcp_fetchpriority_enabled',
            'lcp_preload_lcp',
            'lcp_optimize_first_image',
            'lcp_remove_above_fold_lazy',
        ] as $key) {
            if ($this->is_truthy_setting($asset_settings[$key] ?? null)) {
                return true;
            }
        }

        $image_settings = get_option('prorank_image_optimization_settings', []);
        if (!is_array($image_settings)) {
            $image_settings = [];
        }

        foreach ([
            'image_dimensions_enabled',
            'optimize_on_upload',
            'lazyload_images',
            'lazyload_iframes',
            'lazyload_videos',
            'lazyload_enabled',
            'webp_enabled',
            'avif_enabled',
            'jxl_enabled',
            'jpegli_enabled',
            'smart_compression',
            'background_mode',
            'png_to_jpeg',
            'cmyk_to_rgb',
            'remove_exif',
            'optimize_pdf',
            'generate_retina',
            'automatic_optimization',
        ] as $key) {
            if ($this->is_truthy_setting($image_settings[$key] ?? null)) {
                return true;
            }
        }

        return $this->is_truthy_setting(get_option('prorank_page_cache_enabled', false));
    }

    /**
     * Determine performance child module slugs from the registered modules list.
     *
     * @param array<int,string> $fallback_slugs Existing active module keys.
     * @return array<int,string>
     */
    private function get_performance_child_module_slugs(array $fallback_slugs = []): array {
        try {
            $mm = $this->modules();
            if ($mm) {
                $slugs = [];
                foreach ($mm->get_modules() as $slug => $module) {
                    if (
                        is_object($module)
                        && method_exists($module, 'get_parent_slug')
                        && $module->get_parent_slug() === 'performance'
                    ) {
                        $slugs[] = (string) $slug;

                        if (method_exists($module, 'get_slug')) {
                            $module_slug = (string) $module->get_slug();
                            if ($module_slug !== '') {
                                $slugs[] = $module_slug;
                            }
                        }
                    }
                }

                $slugs = array_values(array_unique(array_filter($slugs, static function ($slug) {
                    return $slug !== '' && $slug !== 'performance';
                })));

                if (!empty($slugs)) {
                    return $slugs;
                }
            }
        } catch (\Throwable $e) {
            // Fall back below when module registry is unavailable.
        }

        return array_values(array_unique(array_filter($fallback_slugs, static function ($slug) {
            return $slug !== '' && $slug !== 'performance';
        })));
    }

    /**
     * Force-disable performance runtime toggles and purge cache artifacts.
     *
     * @return void
     */
    private function hard_disable_performance_runtime(): void {
        $cache_settings = get_option('prorank_cache_settings', []);
        if (!is_array($cache_settings)) {
            $cache_settings = [];
        }
        foreach ([
            'enabled',
            'cache_enabled',
            'preload_enabled',
            'link_preload_enabled',
            'prefetch_enabled',
            'prerender_enabled',
            'browser_cache_enabled',
            'js_defer',
            'js_delay',
            'js_delay_jquery',
            'js_delay_elementor_modules',
            'tracker_delay_enabled',
            'css_minify',
            'css_combine',
            'css_async',
            'edge_cache_enabled',
            'preload_ai_enabled',
            'early_hints_enabled',
            'override_core',
            'link_fallback',
            'dns_prefetch_enabled',
            'dns_prefetch_hosts',
            'font_preload_enabled',
            'htaccess_serving',
        ] as $key) {
            $cache_settings[$key] = ($key === 'dns_prefetch_hosts') ? '' : false;
        }
        update_option('prorank_cache_settings', $cache_settings);

        $asset_settings = get_option('prorank_asset_optimization_settings', []);
        if (!is_array($asset_settings)) {
            $asset_settings = [];
        }
        foreach ([
            'minify_html',
            'remove_html_comments',
            'css_minify',
            'css_combine',
            'js_minify_enabled',
            'js_minify_inline',
            'host_google_fonts_locally',
            'font_display_swap',
            'js_advanced_enabled',
            'inp_optimizer_enabled',
            'lcp_optimizer_enabled',
            'lcp_fetchpriority_enabled',
            'lcp_preload_lcp',
            'lcp_optimize_first_image',
            'lcp_remove_above_fold_lazy',
            'css_async',
            'js_defer',
            'lazyload_enabled',
            'automatic_optimization',
        ] as $key) {
            $asset_settings[$key] = false;
        }
        update_option('prorank_asset_optimization_settings', $asset_settings);

        $image_settings = get_option('prorank_image_optimization_settings', []);
        if (!is_array($image_settings)) {
            $image_settings = [];
        }
        foreach ([
            'image_dimensions_enabled',
            'optimize_on_upload',
            'lazyload_images',
            'lazyload_iframes',
            'lazyload_videos',
            'webp_enabled',
            'avif_enabled',
            'jxl_enabled',
            'jpegli_enabled',
            'smart_compression',
            'background_mode',
            'png_to_jpeg',
            'cmyk_to_rgb',
            'remove_exif',
            'optimize_pdf',
            'generate_retina',
            'lazyload_enabled',
            'automatic_optimization',
        ] as $key) {
            $image_settings[$key] = false;
        }
        update_option('prorank_image_optimization_settings', $image_settings);

        update_option('prorank_page_cache_enabled', false);

        // Remove drop-in to guarantee no pre-WP cache interception remains active.
        if (class_exists('\ProRank\SEO\Cache\AdvancedCache')) {
            \ProRank\SEO\Cache\AdvancedCache::uninstall();
        }

        // Purge existing cache files/htaccess rules when module instance is available.
        $modern_cache = $this->modules()->get_module('modern_cache');
        if (is_object($modern_cache)) {
            if (method_exists($modern_cache, 'remove_htaccess_rules')) {
                $modern_cache->remove_htaccess_rules();
            }
            if (method_exists($modern_cache, 'clear_all_cache')) {
                $modern_cache->clear_all_cache();
            }
        }

        $this->refresh_performance_runtime();
    }

    /**
     * Initialize REST API
     */
    public function init_rest_api(): void {
        // Security validation checkpoint - REST API
        if (class_exists('\ProRank\SEO\Core\SecurityValidator')) {
            \ProRank\SEO\Core\SecurityValidator::validate('cp_rest');
        }
        
        // Add CORS headers for REST API requests
        add_filter('rest_pre_serve_request', function($served, $result, $request, $server) {
            // Only add CORS headers for our endpoints
            $route = $request->get_route();
            if (strpos($route, '/prorank-seo/') === 0) {
                // Get the origin
                $origin = get_http_origin();
                
                // If origin is from the same host (possibly different port), allow it
                $home_host = wp_parse_url(home_url(), PHP_URL_HOST);
                $origin_host = wp_parse_url($origin, PHP_URL_HOST);
                
                if ($origin && $origin_host === $home_host) {
                    header('Access-Control-Allow-Origin: ' . $origin);
                    header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
                    header('Access-Control-Allow-Headers: Content-Type, Authorization, X-WP-Nonce');
                    header('Access-Control-Allow-Credentials: true');
                }
            }
            
            return $served;
        }, 10, 4);
        
        // Initialize REST API controllers with correct dependencies
        $controllers = [];
        $premium_active = defined('PRORANK_PREMIUM_VERSION');
        
        // Core controllers
        if (class_exists('ProRank\SEO\Core\RestApi\DashboardController')) {
            $controllers[] = new DashboardController($this->modules(), $this->license());
        }
        
        if (class_exists('ProRank\SEO\Core\RestApi\ModulesController')) {
            $modules_controller = new ModulesController();
            $modules_controller->set_module_manager($this->modules());
            $controllers[] = $modules_controller;
        }

        if (class_exists('ProRank\SEO\Core\RestApi\WizardStateController')) {
            $controllers[] = new WizardStateController();
        }
        
        if (class_exists('ProRank\SEO\Core\RestApi\NotificationsFeedController')) {
            $controllers[] = new NotificationsFeedController(
                new NotificationsFeed($this->license(), $this->modules())
            );
        }
        
        if (class_exists('ProRank\SEO\Core\RestApi\SettingsController')) {
            $controllers[] = new SettingsController($this->settings());
        }
        
        if (class_exists('ProRank\SEO\Core\RestApi\CacheController')) {
            $controllers[] = new CacheController();
        }
        
        if (class_exists('ProRank\SEO\Core\RestApi\AdvancedSettingsController')) {
            $controllers[] = new AdvancedSettingsController();
        }
        
        if (class_exists('ProRank\SEO\Core\RestApi\DataController')) {
            $controllers[] = new DataController();
        }
        
        if (class_exists('ProRank\SEO\Core\RestApi\FeedbackController')) {
            $controllers[] = new FeedbackController();
        }
        
        if (class_exists('ProRank\SEO\Core\RestApi\SystemController')) {
            $controllers[] = new SystemController();
        }
        
        if (class_exists('ProRank\SEO\Core\RestApi\EnhancedAuditController')) {
            try {
                $controller = new EnhancedAuditController();
                $controllers[] = $controller;
            } catch (\Throwable $e) {
                prorank_log('[ProRank SEO] EnhancedAuditController instantiation failed: ' . $e->getMessage());
                if (defined('WP_DEBUG') && WP_DEBUG) {
                    prorank_log($e->getTraceAsString());
                }
            }
        }

        if (class_exists('ProRank\SEO\Core\RestApi\AuditSettingsController')) {
            $controllers[] = new \ProRank\SEO\Core\RestApi\AuditSettingsController();
        }

        if (class_exists('ProRank\SEO\Core\RestApi\AuditExportController')) {
            $controllers[] = new \ProRank\SEO\Core\RestApi\AuditExportController();
        }

        if (class_exists('ProRank\SEO\Core\RestApi\CwvDataController')) {
            $controllers[] = new CwvDataController();
        }

        // SEO Feature controllers
        if (class_exists('ProRank\SEO\Core\RestApi\MetaController')) {
            $controllers[] = new MetaController();
        }

        // Site-wide schema aggregation (Phase 1: cached, paginated, deduped graph).
        if (class_exists('ProRank\SEO\Core\RestApi\SchemaAggregationController')) {
            $controllers[] = new SchemaAggregationController();
        }

        // NLWeb-compatible /ask endpoint (Phase 4: list mode only).
        if (class_exists('ProRank\SEO\Core\RestApi\AskController')) {
            $controllers[] = new AskController();
        }

        // NLWeb-compatible /mcp endpoint (Phase 5A: MCP core methods over
        // JSON-RPC 2.0, tools/call ask delegates to AskQueryService).
        if (class_exists('ProRank\SEO\Core\RestApi\McpController')) {
            $controllers[] = new McpController();
        }

        if (class_exists('ProRank\SEO\Core\RestApi\OnboardingController')) {
            $controllers[] = new OnboardingController();
        }
        
        // Sitemap controllers
        if (class_exists('ProRank\SEO\Core\RestApi\SitemapsController')) {
            $controllers[] = new SitemapsController();
        }
        
        if (class_exists('ProRank\SEO\Core\RestApi\HtmlSitemapController')) {
            $controllers[] = new HtmlSitemapController();
        }
        
        // Content controllers
        if (class_exists('ProRank\SEO\Core\RestApi\ReadabilityController')) {
            $controllers[] = new ReadabilityController();
        }

        if (class_exists('ProRank\SEO\Core\RestApi\HeadlineAnalyzerController')) {
            $controllers[] = new HeadlineAnalyzerController();
        }

        // Some controllers are shipped in the Free plugin so UI components can call
        // their routes without getting 404s. Access control is enforced inside the
        // controllers (Premium plugin + license tier).
        // Skip if an internal-linking competitor (e.g. Link Whisper) is active.
        $il_detector = new \ProRank\SEO\Core\Compatibility\CompetitorDetector();
        if (!$il_detector->should_enable_internal_linking_safe_mode()) {
            if (class_exists('ProRank\SEO\Core\RestApi\LinkingController')) {
                $controllers[] = new LinkingController();
            }
        }
        
        // Technical SEO controllers
        if (class_exists('ProRank\SEO\Core\RestApi\RedirectsController')) {
            $controllers[] = new RedirectsController();
        }

        if (class_exists('ProRank\SEO\Core\RestApi\RedirectSettingsController')) {
            $controllers[] = new RedirectSettingsController();
        }

        if (class_exists('ProRank\SEO\Core\RestApi\RobotsIndexingController')) {
            $controllers[] = new RobotsIndexingController();
        }
        
        if (class_exists('ProRank\SEO\Core\RestApi\HeadCleanupController')) {
            $controllers[] = new HeadCleanupController();
        }
        
        if (class_exists('ProRank\SEO\Core\RestApi\UrlCleanupController')) {
            $controllers[] = new UrlCleanupController();
        }
        
        // Import/Export controller
        if (class_exists('ProRank\SEO\Core\RestApi\ImportExportController')) {
            $controllers[] = new ImportExportController($this->modules(), $this->license());
        }
        
        // Command Palette controller
        if (class_exists('ProRank\SEO\Core\RestApi\CommandPaletteController')) {
            $controllers[] = new CommandPaletteController($this->services->get('menus'));
        }
        
        // User Preferences controller
        if (class_exists('ProRank\SEO\Core\RestApi\UserPreferencesController')) {
            $controllers[] = new UserPreferencesController();
        }
        
        // Presets controller
        if (class_exists('ProRank\SEO\Core\RestApi\PresetsController')) {
            $controllers[] = new PresetsController($this->services->get('presets'));
        }
        
        // Settings Snapshot controller
        if (class_exists('ProRank\SEO\Core\RestApi\SettingsSnapshotController')) {
            $controllers[] = new SettingsSnapshotController($this->services->get('snapshots'));
        }
        
        // Cache controller
        if (class_exists('ProRank\SEO\Core\RestApi\CacheController')) {
            $controllers[] = new \ProRank\SEO\Core\RestApi\CacheController();
        }

        if (class_exists('ProRank\SEO\Core\RestApi\ImageOptimizationSettingsController')) {
            $controllers[] = new ImageOptimizationSettingsController();
        }
        
        // Image Optimization controller (available in free and premium).
        if (class_exists('ProRank\SEO\Core\RestApi\ImageOptimizationController')) {
            $controllers[] = new ImageOptimizationController();
        }

        // Images controller (available in free and premium).
        if (class_exists('ProRank\SEO\Core\RestApi\ImagesController')) {
            $controllers[] = new ImagesController();
        }
        
        // Font Optimization controller
        if (class_exists('ProRank\SEO\Core\RestApi\FontOptimizationController')) {
            $controllers[] = new FontOptimizationController();
        }
        
        // Database Optimization controller
        if (class_exists('ProRank\SEO\Core\RestApi\DatabaseOptimizationController')) {
            $controllers[] = new DatabaseOptimizationController();
        }
        
        // CSS Optimization controller
        if (class_exists('ProRank\SEO\Core\RestApi\CssOptimizationController')) {
            $controllers[] = new CssOptimizationController();
        }

        // Performance Settings controller (Critical CSS, Unused CSS, etc.)
        if (class_exists('ProRank\SEO\Core\RestApi\PerformanceSettingsController')) {
            $controllers[] = new PerformanceSettingsController();
        }

        if (class_exists('ProRank\SEO\Core\RestApi\CdnController')) {
            $controllers[] = new CdnController();
        }
        if (class_exists('ProRank\SEO\Core\RestApi\CdnSetupController')) {
            $controllers[] = new CdnSetupController();
        }

        // Version Management controller
        if (class_exists('ProRank\SEO\Core\RestApi\VersionController')) {
            $controllers[] = new VersionController();
        }
        
        // Browser Cache endpoint
        if (class_exists('ProRank\SEO\Api\Endpoints\BrowserCacheEndpoint')) {
            $controllers[] = new \ProRank\SEO\Api\Endpoints\BrowserCacheEndpoint();
        }
        
        // Register all routes
        foreach ( $controllers as $controller ) {
            if (method_exists($controller, 'register_routes')) {
                $controller->register_routes();
            }
        }
        
    }

    /**
     * Stream a protected plugin backup to an authenticated admin.
     *
     * @return void
     */
    public function handle_backup_download(): void {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('You do not have permission to download plugin backups.', 'prorank-seo'));
        }

        $backup_name = isset($_GET['backup']) ? sanitize_text_field(wp_unslash($_GET['backup'])) : '';

        check_admin_referer('prorank_download_backup_' . $backup_name);

        $version_manager = new \ProRank\SEO\Core\VersionManager();
        $backup_file     = $version_manager->resolve_backup_file($backup_name);

        if (is_wp_error($backup_file)) {
            wp_die(esc_html($backup_file->get_error_message()));
        }

        while (ob_get_level() > 0) {
            ob_end_clean();
        }

        nocache_headers();
        header('Content-Description: File Transfer');
        header('Content-Type: application/zip');
        header('Content-Disposition: attachment; filename="' . $backup_file['name'] . '"');
        header('Content-Transfer-Encoding: binary');
        header('Content-Length: ' . (string) $backup_file['size']);
        header('X-Content-Type-Options: nosniff');

        readfile($backup_file['path']);
        exit;
    }
    
    /**
     * Check license status and show admin notice if invalid
     *
     * @return void
     */
    private function check_license_status(): void {
        return;
    }
    
    /**
     * Run license check
     *
     * @return void
     */
    public function run_license_check(): void {
        return;
    }
    
    /**
     * Run license refresh (Action Scheduler task)
     *
     * @return void
     */
    public function run_license_refresh(): void {
        return;
    }
}
