<?php

/**
 * Performance Settings REST API Controller
 *
 * Handles performance module settings endpoints
 *
 * @package ProRank\SEO\Core\RestApi
 * @since   1.0.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Core\RestApi;

use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
use ProRank\SEO\Core\Compatibility\CompetitorDetector;

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


/**
 * Performance Settings controller class
 */


class PerformanceSettingsController extends BaseController {

    /**
     * Rest base
     *
     * @var string
     */
    protected $rest_base = 'performance';

    /**
     * Register routes
     *
     * @return void
     */
    public function register_routes(): void {
        // Performance Beta toggle endpoint
        register_rest_route(
            $this->namespace,
            '/performance-beta',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [$this, 'get_beta_status'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [$this, 'update_beta_status'],
                    'permission_callback' => [$this, 'check_permission'],
                    'args'                => [
                        'value' => [
                            'required' => true,
                            'type'     => 'boolean',
                        ],
                    ],
                ],
            ]
        );

        // Cache Settings (Page Cache, CDN, Preload, etc.)
        // Uses /performance/cache-settings to avoid SettingsController catch-all
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/cache-settings',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [$this, 'get_cache_settings'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [$this, 'update_cache_settings'],
                    'permission_callback' => [$this, 'check_permission'],
                    'args'                => [
                        'settings' => [
                            'required' => true,
                            'type'     => 'object',
                        ],
                    ],
                ],
            ]
        );

        // Image Optimization Settings
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/image-optimization/settings',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [$this, 'get_image_settings'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [$this, 'update_image_settings'],
                    'permission_callback' => [$this, 'check_permission'],
                    'args'                => [
                        'settings' => [
                            'required' => true,
                            'type'     => 'object',
                        ],
                    ],
                ],
            ]
        );

        // Asset Optimization Settings
        // Uses /performance/asset-settings to avoid SettingsController catch-all
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/asset-settings',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [$this, 'get_asset_settings'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [$this, 'update_asset_settings'],
                    'permission_callback' => [$this, 'check_permission'],
                    'args'                => [
                        'settings' => [
                            'required' => true,
                            'type'     => 'object',
                        ],
                    ],
                ],
            ]
        );

        // Database Optimization Settings
        register_rest_route(
            $this->namespace,
            '/settings/database',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [$this, 'get_database_settings'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [$this, 'update_database_settings'],
                    'permission_callback' => [$this, 'check_permission'],
                    'args'                => [
                        'settings' => [
                            'required' => true,
                            'type'     => 'object',
                        ],
                    ],
                ],
            ]
        );

        // Image optimization stats
        register_rest_route(
            $this->namespace,
            '/performance/image-optimization/stats',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [$this, 'get_image_stats'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );

        // RUM ingest (INP/CLS/LCP)
        $rum_route_args = [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [$this, 'ingest_rum'],
                'permission_callback' => '__return_true',
            ],
        ];

        // Register both variants to avoid host-level slash redirects converting
        // POST beacon requests into GET and causing noisy 404s.
        register_rest_route(
            $this->namespace,
            '/performance/rum',
            $rum_route_args
        );
        register_rest_route(
            $this->namespace,
            '/performance/rum/',
            $rum_route_args
        );

        // Image bulk optimization
        register_rest_route(
            $this->namespace,
            '/performance/image-optimization/bulk',
            [
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [$this, 'bulk_optimize_images'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );

        // Font stats
        register_rest_route(
            $this->namespace,
            '/performance/font-stats',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [$this, 'get_font_stats'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );

        // Database optimization actions
        register_rest_route(
            $this->namespace,
            '/database/optimize',
            [
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [$this, 'optimize_database'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );

        // Database stats
        register_rest_route(
            $this->namespace,
            '/database/stats',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [$this, 'get_database_stats'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );

        // Server info
        register_rest_route(
            $this->namespace,
            '/server/info',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [$this, 'get_server_info'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );

        // Performance conflict safe mode status
        register_rest_route(
            $this->namespace,
            '/performance/conflict-safe-mode/status',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [$this, 'get_performance_conflict_safe_mode_status'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );

        // Apply performance conflict safe mode immediately
        register_rest_route(
            $this->namespace,
            '/performance/conflict-safe-mode/apply',
            [
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [$this, 'apply_performance_conflict_safe_mode_now'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );

        // Bloat cleanup preset (safe defaults)
        register_rest_route(
            $this->namespace,
            '/performance/presets/bloat-cleanup',
            [
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [$this, 'apply_bloat_cleanup_preset'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );

        // htaccess cache serving status
        register_rest_route(
            $this->namespace,
            '/performance/htaccess/status',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [$this, 'get_htaccess_status'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );

        // Install htaccess rules
        register_rest_route(
            $this->namespace,
            '/performance/htaccess/install',
            [
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [$this, 'install_htaccess_rules'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );


        // Nginx cache config
        register_rest_route(
            $this->namespace,
            '/performance/nginx-config',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [$this, 'get_nginx_config'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );
        // Remove htaccess rules
        register_rest_route(
            $this->namespace,
            '/performance/htaccess/remove',
            [
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [$this, 'remove_htaccess_rules'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );
    }

    /**
     * Get performance beta status
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function get_beta_status(WP_REST_Request $request): WP_REST_Response {
        $value = get_option('prorank_performance_beta', false);

        return new WP_REST_Response([
            'value' => (bool) $value,
        ], 200);
    }

    /**
     * Update performance beta status
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function update_beta_status(WP_REST_Request $request): WP_REST_Response {
        $value = $request->get_param('value');
        update_option('prorank_performance_beta', $value);

        return new WP_REST_Response([
            'success' => true,
            'value' => $value,
        ], 200);
    }

    /**
     * Get cache settings
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function get_cache_settings(WP_REST_Request $request): WP_REST_Response {
        $defaults = [
            // Page Cache
            'cache_enabled' => false,
            'cache_lifetime' => 3600,
            'cache_exclude_urls' => '',

            // CDN
            'cdn_provider' => 'none',
            'cloudflare_zone_id' => '',
            'cloudflare_api_token' => '',
            'bunny_pullzone_id' => '',
            'bunny_api_key' => '',
            'fastly_service_id' => '',
            'fastly_api_token' => '',
            'cloudfront_distribution_id' => '',
            'cloudfront_access_key' => '',
            'cloudfront_secret_key' => '',
            'cloudfront_region' => '',
            'keycdn_zone_id' => '',
            'keycdn_api_key' => '',
            'cdn_webhook_url' => '',
            'cdn_webhook_secret' => '',

            // Preload (Pro)
            'preload_enabled' => false,
            'preload_mode' => 'sitemap',
            'preload_schedule' => 'daily',

            // Link Preloading
            'link_preload_enabled' => false,
            'link_preload_delay' => 50,

            // Speculation Rules (Pro)
            'prefetch_enabled' => false,
            'prefetch_eagerness' => 'moderate',
            'prefetch_query_strings' => false,
            'prerender_enabled' => false,
            'prerender_mode' => 'list',
            'prerender_eagerness' => 'conservative',
            'prerender_urls' => '',
            'prerender_auto_detect' => true,
            'max_prerender_urls' => 5,
            'speculation_exclude_patterns' => '',
            'enable_logged_in' => false,
            'override_core' => false,
            'link_fallback' => false,

            // DNS Prefetch
            'dns_prefetch_enabled' => false,
            'dns_prefetch_hosts' => '',
            'dynamic_resource_hints_enabled' => true,

            // Font Preloading
            'font_preload_enabled' => false,
            'font_preload_urls' => '',

            // Browser Cache
            'browser_cache_enabled' => false,
            'browser_cache_images' => '31536000',
            'browser_cache_css' => '31536000',
            'browser_cache_js' => '31536000',
            'browser_cache_fonts' => '31536000',
            'browser_cache_html' => '3600',

            // JS Optimization
            'js_defer' => false,
            'js_delay' => false,
            'tracker_delay_enabled' => false,
            'js_delay_jquery' => false,
            'js_delay_elementor_modules' => false,
            'js_exclude' => '',

            // CSS Optimization
            'css_minify' => false,
            'css_combine' => false,
            'css_async' => false,
            'inline_css_minify_enabled' => true,
            'inline_css_relocate_enabled' => true,
            'css_exclude' => '',
        ];

        $settings = get_option('prorank_cache_settings', $defaults);
        $parsed_settings = wp_parse_args($settings, $defaults);

        foreach ([
            'cache_enabled',
            'preload_enabled',
            'link_preload_enabled',
            'prefetch_enabled',
            'prefetch_query_strings',
            'prerender_enabled',
            'prerender_auto_detect',
            'enable_logged_in',
            'override_core',
            'link_fallback',
            'dns_prefetch_enabled',
            'dynamic_resource_hints_enabled',
            'font_preload_enabled',
            'browser_cache_enabled',
            'js_defer',
            'js_delay',
            'tracker_delay_enabled',
            'js_delay_jquery',
            'js_delay_elementor_modules',
            'css_minify',
            'css_combine',
            'css_async',
            'inline_css_minify_enabled',
            'inline_css_relocate_enabled',
        ] as $bool_key) {
            $parsed_settings[$bool_key] = !empty($parsed_settings[$bool_key]);
        }

        $parsed_settings['cache_enabled'] = !empty($parsed_settings['cache_enabled']) || !empty($parsed_settings['enabled']);
        $parsed_settings['enabled'] = $parsed_settings['cache_enabled'];

        $parsed_settings['cache_lifetime'] = max(
            0,
            (int) ($parsed_settings['cache_lifetime'] ?? $parsed_settings['lifetime'] ?? $defaults['cache_lifetime'])
        );
        $parsed_settings['lifetime'] = $parsed_settings['cache_lifetime'];

        $parsed_settings['cache_exclude_urls'] = sanitize_textarea_field(
            (string) ($parsed_settings['cache_exclude_urls'] ?? $parsed_settings['exclude_urls'] ?? '')
        );
        $parsed_settings['exclude_urls'] = $parsed_settings['cache_exclude_urls'];

        $parsed_settings['performance_conflict_safe_mode'] = $this->is_performance_conflict_safe_mode_enabled();
        $performance_conflicts = $this->get_active_performance_conflicts();

        return new WP_REST_Response([
            'settings' => $parsed_settings,
            'performance_conflicts' => [
                'count' => count($performance_conflicts),
                'plugins' => $performance_conflicts,
                'safe_mode_enabled' => $parsed_settings['performance_conflict_safe_mode'],
            ],
        ], 200);
    }

    /**
     * Update cache settings
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function update_cache_settings(WP_REST_Request $request): WP_REST_Response {
        $settings = $request->get_param('settings');

        if (!is_array($settings)) {
            return new WP_REST_Response([
                'error' => __('Invalid settings format', 'prorank-seo'),
            ], 400);
        }

        // Normalize booleans for cache settings
        foreach ([
            'cache_enabled',
            'preload_enabled',
            'link_preload_enabled',
            'prefetch_enabled',
            'prefetch_query_strings',
            'prerender_enabled',
            'prerender_auto_detect',
            'enable_logged_in',
            'override_core',
            'link_fallback',
            'dns_prefetch_enabled',
            'dynamic_resource_hints_enabled',
            'font_preload_enabled',
            'browser_cache_enabled',
            'js_defer',
            'js_delay',
            'tracker_delay_enabled',
            'js_delay_jquery',
            'js_delay_elementor_modules',
            'css_minify',
            'css_combine',
            'css_async',
            'inline_css_minify_enabled',
            'inline_css_relocate_enabled',
        ] as $bool_key) {
            if (isset($settings[$bool_key])) {
                $settings[$bool_key] = (bool) $settings[$bool_key];
            }
        }

        foreach (['cache_lifetime', 'link_preload_delay', 'max_prerender_urls'] as $int_key) {
            if (isset($settings[$int_key])) {
                $settings[$int_key] = max(0, (int) $settings[$int_key]);
            }
        }

        // Store conflict safe mode separately from cache option.
        if (array_key_exists('performance_conflict_safe_mode', $settings)) {
            $this->set_performance_conflict_safe_mode((bool) $settings['performance_conflict_safe_mode']);
            unset($settings['performance_conflict_safe_mode']);
        }

        $allowed_eagerness = ['conservative', 'moderate', 'eager', 'immediate'];
        if (isset($settings['prefetch_eagerness']) && !in_array($settings['prefetch_eagerness'], $allowed_eagerness, true)) {
            $settings['prefetch_eagerness'] = 'moderate';
        }
        if (isset($settings['prerender_eagerness']) && !in_array($settings['prerender_eagerness'], $allowed_eagerness, true)) {
            $settings['prerender_eagerness'] = 'conservative';
        }
        if (isset($settings['prerender_mode']) && !in_array($settings['prerender_mode'], ['list', 'document'], true)) {
            $settings['prerender_mode'] = 'list';
        }

        // Keep both keys in sync for backward compatibility.
        $settings['enabled'] = !empty($settings['cache_enabled']);
        update_option('prorank_cache_settings', $settings);
        $this->sync_performance_active_modules_from_cache_settings($settings);

        // Sync CDN provider settings to legacy options used by CdnManager
        $provider = sanitize_key($settings['cdn_provider'] ?? 'none');
        $allowed_providers = ['none', 'cloudflare', 'bunnycdn', 'fastly', 'cloudfront', 'keycdn', 'webhook'];
        if (!in_array($provider, $allowed_providers, true)) {
            $provider = 'none';
        }

        update_option('prorank_cdn_provider', $provider);
        update_option('prorank_cloudflare_zone_id', sanitize_text_field((string) ($settings['cloudflare_zone_id'] ?? '')));
        update_option('prorank_cloudflare_api_token', sanitize_text_field((string) ($settings['cloudflare_api_token'] ?? '')));
        update_option('prorank_bunny_pullzone_id', sanitize_text_field((string) ($settings['bunny_pullzone_id'] ?? '')));
        update_option('prorank_bunny_api_key', sanitize_text_field((string) ($settings['bunny_api_key'] ?? '')));
        update_option('prorank_fastly_service_id', sanitize_text_field((string) ($settings['fastly_service_id'] ?? '')));
        update_option('prorank_fastly_api_token', sanitize_text_field((string) ($settings['fastly_api_token'] ?? '')));
        update_option('prorank_cloudfront_distribution_id', sanitize_text_field((string) ($settings['cloudfront_distribution_id'] ?? '')));
        update_option('prorank_cloudfront_access_key', sanitize_text_field((string) ($settings['cloudfront_access_key'] ?? '')));
        update_option('prorank_cloudfront_secret_key', sanitize_text_field((string) ($settings['cloudfront_secret_key'] ?? '')));
        update_option('prorank_cloudfront_region', sanitize_text_field((string) ($settings['cloudfront_region'] ?? '')));
        update_option('prorank_keycdn_zone_id', sanitize_text_field((string) ($settings['keycdn_zone_id'] ?? '')));
        update_option('prorank_keycdn_api_key', sanitize_text_field((string) ($settings['keycdn_api_key'] ?? '')));
        update_option('prorank_cdn_webhook_url', esc_url_raw((string) ($settings['cdn_webhook_url'] ?? '')));
        update_option('prorank_cdn_webhook_secret', sanitize_text_field((string) ($settings['cdn_webhook_secret'] ?? '')));

        // Clear dashboard stats cache so changes reflect immediately
        delete_transient('prorank_dashboard_stats');

        $safe_mode_result = $this->enforce_performance_conflict_safe_mode();

        return new WP_REST_Response([
            'success' => true,
            'settings' => $settings,
            'safe_mode' => $safe_mode_result,
        ], 200);
    }

    /**
     * Get image optimization settings
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function get_image_settings(WP_REST_Request $request): WP_REST_Response {
        $defaults = [
            'optimization_method' => 'local',
            'webp_enabled' => true,
            'webp_quality' => 85,
            'avif_enabled' => false,
            'avif_quality' => 80,
            'jxl_enabled' => false,
            'jxl_quality' => 80,
            'jpegli_enabled' => false,
            'png_quality' => 80,
            'png_pipeline' => false,
            'smart_compression' => true,
            'compression_type' => 'glossy',
            'optimize_on_upload' => false,
            'background_mode' => false,
            'backup_originals' => true,
            'optimize_original_source' => false,
            'lazyload_images' => false,
            'lazyload_iframes' => false,
            'lazyload_videos' => false,
            'lazyload_threshold' => 200,
            'cdn_enabled' => false,
            'cdn_url' => '',
            'exclude_paths' => '',
            // Advanced settings
            'png_to_jpeg' => true,
            'cmyk_to_rgb' => true,
            'remove_exif' => true,
            'optimize_pdf' => false,
            'generate_retina' => false,
            'no_ai_training' => false,
            'resize_large_enabled' => false,
            'resize_max_width' => 2560,
            'resize_max_height' => 2560,
            'delivery_method' => 'picture',
            'cloud_opt_in' => false,
            'cloud_opt_in_at' => null,
        ];

        $settings = get_option('prorank_image_optimization_settings', []);
        $settings = $this->normalize_image_settings(
            wp_parse_args(is_array($settings) ? $settings : [], $defaults),
            $defaults
        );
        $settings['optimization_method'] = 'local';
        $settings['cloud_opt_in'] = false;
        $settings['cloud_opt_in_at'] = null;

        return new WP_REST_Response([
            'settings' => $this->build_image_settings_response($settings, $defaults),
        ], 200);
    }

    /**
     * Update image optimization settings
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function update_image_settings(WP_REST_Request $request): WP_REST_Response {
        $incoming = $request->get_param('settings');

        if (!is_array($incoming)) {
            return new WP_REST_Response([
                'error' => __('Invalid settings format', 'prorank-seo'),
            ], 400);
        }

        $defaults = [
            'optimization_method' => 'local',
            'webp_enabled' => true,
            'webp_quality' => 85,
            'avif_enabled' => false,
            'avif_quality' => 80,
            'jxl_enabled' => false,
            'jxl_quality' => 80,
            'jpegli_enabled' => false,
            'png_quality' => 80,
            'png_pipeline' => false,
            'smart_compression' => true,
            'compression_type' => 'glossy',
            'optimize_on_upload' => false,
            'background_mode' => false,
            'backup_originals' => true,
            'optimize_original_source' => false,
            'lazyload_images' => false,
            'lazyload_iframes' => false,
            'lazyload_videos' => false,
            'lazyload_threshold' => 200,
            'cdn_enabled' => false,
            'cdn_url' => '',
            'exclude_paths' => '',
            // Advanced settings
            'png_to_jpeg' => true,
            'cmyk_to_rgb' => true,
            'remove_exif' => true,
            'optimize_pdf' => false,
            'generate_retina' => false,
            'no_ai_training' => false,
            'resize_large_enabled' => false,
            'resize_max_width' => 2560,
            'resize_max_height' => 2560,
            'delivery_method' => 'picture',
            'cloud_opt_in' => false,
            'cloud_opt_in_at' => null,
        ];

        // Merge with existing settings to avoid losing keys on partial updates
        $existing = get_option('prorank_image_optimization_settings', []);
        $settings = wp_parse_args(is_array($existing) ? $existing : [], $defaults);
        $settings = array_merge($settings, $incoming);

        // Validation
        $settings['optimization_method'] = 'local';

        $settings['compression_type'] = in_array($settings['compression_type'], ['lossless', 'glossy', 'lossy', 'aggressive'], true)
            ? $settings['compression_type']
            : $defaults['compression_type'];

        $settings['delivery_method'] = $this->normalize_image_delivery_method((string) ($settings['delivery_method'] ?? ''));
        $settings['delivery_method'] = in_array($settings['delivery_method'], ['picture', 'accept_header', 'htaccess', 'cdn', 'none'], true)
            ? $settings['delivery_method']
            : $defaults['delivery_method'];

        foreach (['webp_quality', 'avif_quality', 'jxl_quality', 'png_quality'] as $key) {
            $settings[$key] = isset($settings[$key]) ? max(0, min(100, (int) $settings[$key])) : $defaults[$key];
        }

        $settings['lazyload_threshold'] = isset($settings['lazyload_threshold'])
            ? max(0, min(1000, (int) $settings['lazyload_threshold']))
            : $defaults['lazyload_threshold'];

        $settings['resize_max_width'] = isset($settings['resize_max_width'])
            ? max(320, min(8192, (int) $settings['resize_max_width']))
            : $defaults['resize_max_width'];
        $settings['resize_max_height'] = isset($settings['resize_max_height'])
            ? max(320, min(8192, (int) $settings['resize_max_height']))
            : $defaults['resize_max_height'];

        $settings['cdn_url'] = isset($settings['cdn_url']) ? esc_url_raw($settings['cdn_url']) : $defaults['cdn_url'];
        $settings['exclude_paths'] = isset($settings['exclude_paths']) ? sanitize_textarea_field($settings['exclude_paths']) : $defaults['exclude_paths'];

        // Cast remaining booleans
        foreach ([
            'webp_enabled',
            'avif_enabled',
            'jxl_enabled',
            'jpegli_enabled',
            'png_pipeline',
            'smart_compression',
            'optimize_on_upload',
            'background_mode',
            'backup_originals',
            'optimize_original_source',
            'lazyload_images',
            'lazyload_iframes',
            'lazyload_videos',
            'cdn_enabled',
            'png_to_jpeg',
            'cmyk_to_rgb',
            'remove_exif',
            'optimize_pdf',
            'generate_retina',
            'no_ai_training',
            'resize_large_enabled',
            'cloud_opt_in',
        ] as $bool_key) {
            $settings[$bool_key] = $this->normalize_boolean_setting($settings[$bool_key] ?? false);
        }

        $settings['cloud_opt_in'] = false;
        $settings['cloud_opt_in_at'] = null;

        update_option('prorank_image_optimization_settings', $settings);
        $this->sync_performance_active_modules_from_image_settings($settings);

        $safe_mode_result = $this->enforce_performance_conflict_safe_mode();

        return new WP_REST_Response([
            'success' => true,
            'settings' => $this->build_image_settings_response($settings, $defaults),
            'safe_mode' => $safe_mode_result,
        ], 200);
    }

    /**
     * Normalize a setting value into a strict boolean.
     *
     * @param mixed $value
     * @return bool
     */
    private function normalize_boolean_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 === '') {
                return false;
            }

            if (in_array($normalized, ['1', 'true', 'yes', 'on', 'enabled'], true)) {
                return true;
            }

            if (in_array($normalized, ['0', 'false', 'no', 'off', 'disabled'], true)) {
                return false;
            }
        }

        return !empty($value);
    }

    /**
     * Build a canonical image settings payload for REST responses.
     *
     * @param array<string,mixed> $settings
     * @param array<string,mixed> $defaults
     * @return array<string,mixed>
     */
    private function build_image_settings_response(array $settings, array $defaults): array {
        $canonical = array_intersect_key($settings, $defaults);
        return $this->normalize_image_settings(wp_parse_args($canonical, $defaults), $defaults);
    }

    /**
     * Normalize image optimization settings to canonical runtime values.
     *
     * @param array<string,mixed> $settings
     * @param array<string,mixed> $defaults
     * @return array<string,mixed>
     */
    private function normalize_image_settings(array $settings, array $defaults): array {
        $settings['optimization_method'] = 'local';

        $settings['compression_type'] = in_array(($settings['compression_type'] ?? ''), ['lossless', 'glossy', 'lossy', 'aggressive'], true)
            ? $settings['compression_type']
            : $defaults['compression_type'];

        $settings['delivery_method'] = $this->normalize_image_delivery_method((string) ($settings['delivery_method'] ?? ''));
        if (!in_array($settings['delivery_method'], ['picture', 'accept_header', 'htaccess', 'cdn', 'none'], true)) {
            $settings['delivery_method'] = $defaults['delivery_method'];
        }

        foreach ([
            'webp_enabled',
            'avif_enabled',
            'jxl_enabled',
            'jpegli_enabled',
            'png_pipeline',
            'smart_compression',
            'optimize_on_upload',
            'background_mode',
            'backup_originals',
            'optimize_original_source',
            'lazyload_images',
            'lazyload_iframes',
            'lazyload_videos',
            'cdn_enabled',
            'png_to_jpeg',
            'cmyk_to_rgb',
            'remove_exif',
            'optimize_pdf',
            'generate_retina',
            'no_ai_training',
            'resize_large_enabled',
            'cloud_opt_in',
        ] as $bool_key) {
            $settings[$bool_key] = $this->normalize_boolean_setting($settings[$bool_key] ?? false);
        }

        foreach (['webp_quality', 'avif_quality', 'jxl_quality', 'png_quality'] as $quality_key) {
            $settings[$quality_key] = isset($settings[$quality_key])
                ? max(0, min(100, (int) $settings[$quality_key]))
                : $defaults[$quality_key];
        }

        $settings['lazyload_threshold'] = isset($settings['lazyload_threshold'])
            ? max(0, min(1000, (int) $settings['lazyload_threshold']))
            : $defaults['lazyload_threshold'];
        $settings['resize_max_width'] = isset($settings['resize_max_width'])
            ? max(320, min(8192, (int) $settings['resize_max_width']))
            : $defaults['resize_max_width'];
        $settings['resize_max_height'] = isset($settings['resize_max_height'])
            ? max(320, min(8192, (int) $settings['resize_max_height']))
            : $defaults['resize_max_height'];

        return $settings;
    }

    /**
     * Map legacy image delivery method values to canonical values.
     *
     * @param string $delivery_method
     * @return string
     */
    private function normalize_image_delivery_method(string $delivery_method): string {
        switch ($delivery_method) {
            case 'accept':
                return 'accept_header';
            case 'rewrite':
                return 'htaccess';
            case 'picture_global':
                return 'picture';
            default:
                return $delivery_method;
        }
    }

    /**
     * Get asset optimization settings
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function get_asset_settings(WP_REST_Request $request): WP_REST_Response {
        $defaults = [
            // JavaScript Minification
            'js_minify_enabled' => false,
            'js_minify_inline' => false,
            // HTML Minification
            'minify_html' => false,
            'remove_html_comments' => true,
            // Font Optimization
            'host_google_fonts_locally' => false,
            'font_display_swap' => false,
        ];

        $settings = $this->normalize_asset_settings(
            wp_parse_args((array) get_option('prorank_asset_optimization_settings', []), $defaults),
            $defaults
        );
        $performance_conflicts = $this->get_active_performance_conflicts();

        return new WP_REST_Response([
            'settings' => $settings,
            'performance_conflicts' => [
                'count' => count($performance_conflicts),
                'plugins' => $performance_conflicts,
                'safe_mode_enabled' => $this->is_performance_conflict_safe_mode_enabled(),
            ],
        ], 200);
    }

    /**
     * Update asset optimization settings
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function update_asset_settings(WP_REST_Request $request): WP_REST_Response {
        $settings = $request->get_param('settings');
        $defaults = [
            'js_minify_enabled' => false,
            'js_minify_inline' => false,
            'minify_html' => false,
            'remove_html_comments' => true,
            'host_google_fonts_locally' => false,
            'font_display_swap' => false,
        ];

        if (!is_array($settings)) {
            return new WP_REST_Response([
                'error' => __('Invalid settings format', 'prorank-seo'),
            ], 400);
        }

        // Merge with existing to preserve unspecified keys
        $existing = get_option('prorank_asset_optimization_settings', []);
        $merged = wp_parse_args($settings, is_array($existing) ? $existing : []);
        $merged = $this->normalize_asset_settings($merged, $defaults);
        $merged['js_minify'] = $merged['js_minify_enabled'];

        update_option('prorank_asset_optimization_settings', $merged);
        $this->sync_performance_active_modules_from_asset_settings($merged);

        // Clear dashboard stats cache so changes reflect immediately
        delete_transient('prorank_dashboard_stats');

        $safe_mode_result = $this->enforce_performance_conflict_safe_mode();

        return new WP_REST_Response([
            'success' => true,
            'settings' => $this->normalize_asset_settings($merged, $defaults),
            'safe_mode' => $safe_mode_result,
        ], 200);
    }

    /**
     * Normalize asset optimization settings to canonical runtime values.
     *
     * @param array<string,mixed> $settings
     * @param array<string,mixed> $defaults
     * @return array<string,mixed>
     */
    private function normalize_asset_settings(array $settings, array $defaults): array {
        if (!array_key_exists('js_minify_enabled', $settings) && array_key_exists('js_minify', $settings)) {
            $settings['js_minify_enabled'] = $settings['js_minify'];
        }

        $canonical = array_intersect_key(wp_parse_args($settings, $defaults), $defaults);

        foreach ([
            'js_minify_enabled',
            'js_minify_inline',
            'minify_html',
            'remove_html_comments',
            'host_google_fonts_locally',
            'font_display_swap',
        ] as $bool_key) {
            $canonical[$bool_key] = $this->normalize_boolean_setting($canonical[$bool_key] ?? false);
        }

        return $canonical;
    }

    /**
     * Get database optimization settings
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function get_database_settings(WP_REST_Request $request): WP_REST_Response {
        $settings = get_option('prorank_database_settings', []);
        $settings = $this->sanitize_database_settings_payload(is_array($settings) ? $settings : []);

        return new WP_REST_Response([
            'settings' => $settings,
        ], 200);
    }

    /**
     * Update database optimization settings
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function update_database_settings(WP_REST_Request $request): WP_REST_Response {
        $settings = $request->get_param('settings');

        if (!is_array($settings)) {
            return new WP_REST_Response([
                'error' => __('Invalid settings format', 'prorank-seo'),
            ], 400);
        }

        $settings = $this->sanitize_database_settings_payload($settings);
        update_option('prorank_database_settings', $settings);

        return new WP_REST_Response([
            'success' => true,
            'settings' => $settings,
        ], 200);
    }

    /**
     * Return the supported database settings for the free plugin.
     *
     * Legacy flags such as opcache_enabled, memory_optimization, and
     * admin_ajax_optimization are intentionally excluded because they are not
     * runtime-controlled by this screen anymore.
     *
     * @return array<string,mixed>
     */
    private function get_database_settings_defaults(): array {
        return [
            'auto_cleanup_enabled' => false,
            'cleanup_schedule' => 'weekly',
            'cleanup_items' => [
                'revisions' => true,
                'drafts' => false,
                'trash' => true,
                'spam_comments' => true,
                'unapproved_comments' => false,
                'transients' => true,
                'expired_transients' => true,
                'orphaned_meta' => false,
                'action_scheduler' => false,
                'wpmailsmtp_logs' => false,
            ],
            'revision_limit' => 5,
            'trash_days' => 30,
            'action_scheduler_retention_days' => 7,
            'wpmailsmtp_log_retention_days' => 30,
            'heartbeat_control' => false,
            'heartbeat_frequency' => 60,
        ];
    }

    /**
     * Normalize cleanup item flags to the supported schema.
     *
     * @param mixed $items Incoming cleanup item payload.
     * @return array<string,bool>
     */
    private function sanitize_database_cleanup_items($items): array {
        $defaults = $this->get_database_settings_defaults()['cleanup_items'];
        $items = is_array($items) ? $items : [];

        foreach ($defaults as $key => $value) {
            $defaults[$key] = $this->normalize_boolean_setting($items[$key] ?? false);
        }

        return $defaults;
    }

    /**
     * Sanitize the database settings payload and drop unsupported legacy keys.
     *
     * @param array<string,mixed> $settings Incoming settings.
     * @return array<string,mixed>
     */
    private function sanitize_database_settings_payload(array $settings): array {
        $defaults = $this->get_database_settings_defaults();
        $allowed_cleanup_schedules = ['daily', 'weekly', 'monthly'];
        $allowed_heartbeat_frequencies = [15, 30, 60, 120, 300];

        $cleanup_schedule = sanitize_key((string) ($settings['cleanup_schedule'] ?? $defaults['cleanup_schedule']));
        if (!in_array($cleanup_schedule, $allowed_cleanup_schedules, true)) {
            $cleanup_schedule = $defaults['cleanup_schedule'];
        }

        $revision_limit = absint($settings['revision_limit'] ?? $defaults['revision_limit']);
        $trash_days = absint($settings['trash_days'] ?? $defaults['trash_days']);
        $action_scheduler_retention_days = absint($settings['action_scheduler_retention_days'] ?? $defaults['action_scheduler_retention_days']);
        $wpmailsmtp_log_retention_days = absint($settings['wpmailsmtp_log_retention_days'] ?? $defaults['wpmailsmtp_log_retention_days']);
        $heartbeat_frequency = (int) ($settings['heartbeat_frequency'] ?? $defaults['heartbeat_frequency']);
        if (!in_array($heartbeat_frequency, $allowed_heartbeat_frequencies, true)) {
            $heartbeat_frequency = $defaults['heartbeat_frequency'];
        }

        return [
            'auto_cleanup_enabled' => $this->normalize_boolean_setting($settings['auto_cleanup_enabled'] ?? false),
            'cleanup_schedule' => $cleanup_schedule,
            'cleanup_items' => $this->sanitize_database_cleanup_items($settings['cleanup_items'] ?? []),
            'revision_limit' => max(0, min(100, $revision_limit)),
            'trash_days' => max(0, min(365, $trash_days)),
            'action_scheduler_retention_days' => max(1, min(365, $action_scheduler_retention_days)),
            'wpmailsmtp_log_retention_days' => max(1, min(365, $wpmailsmtp_log_retention_days)),
            'heartbeat_control' => $this->normalize_boolean_setting($settings['heartbeat_control'] ?? false),
            'heartbeat_frequency' => $heartbeat_frequency,
        ];
    }

    /**
     * Get performance conflict safe mode status.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response
     */
    public function get_performance_conflict_safe_mode_status(WP_REST_Request $request): WP_REST_Response {
        $enabled = $this->is_performance_conflict_safe_mode_enabled();
        $conflicts = $this->get_active_performance_conflicts();

        return new WP_REST_Response([
            'success' => true,
            'safe_mode_enabled' => $enabled,
            'conflicts' => [
                'count' => count($conflicts),
                'plugins' => $conflicts,
            ],
            'should_apply' => $enabled && !empty($conflicts),
        ], 200);
    }

    /**
     * Apply performance conflict safe mode immediately.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response
     */
    public function apply_performance_conflict_safe_mode_now(WP_REST_Request $request): WP_REST_Response {
        if ($request->has_param('enabled')) {
            $this->set_performance_conflict_safe_mode((bool) $request->get_param('enabled'));
        }

        $result = $this->enforce_performance_conflict_safe_mode();

        return new WP_REST_Response([
            'success' => true,
            'safe_mode' => $result,
        ], 200);
    }

    /**
     * Apply a safe bloat-cleanup preset for performance hardening.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response
     */
    public function apply_bloat_cleanup_preset(WP_REST_Request $request): WP_REST_Response {
        // Advanced settings (safe, non-destructive)
        $advanced_defaults = [
            'debug_mode' => false,
            'disable_emojis' => false,
            'disable_gutenberg' => false,
            'disable_xmlrpc' => true,
            'disable_rest_api_public' => false,
            'remove_version_strings' => true,
            'disable_file_editing' => true,
            'structural_guard_enabled' => true,
            'performance_compatibility_mode' => true,
            'performance_auto_rollback' => true,
            'performance_profile' => 'safe',
            'api_timeout' => 30,
            'cache_duration' => 3600,
            'log_level' => 'error',
        ];
        $advanced = get_option('prorank_seo_advanced_settings', []);
        $advanced = wp_parse_args(is_array($advanced) ? $advanced : [], $advanced_defaults);
        $advanced['disable_emojis'] = true;
        $advanced['disable_xmlrpc'] = true;
        $advanced['remove_version_strings'] = true;
        update_option('prorank_seo_advanced_settings', $advanced);

        // Head cleanup settings (safe metadata cleanup)
        $head_defaults = [
            'remove_wp_generator' => false,
            'remove_rsd_link' => false,
            'remove_wlwmanifest_link' => false,
            'remove_emoji_scripts' => false,
            'remove_wp_shortlink' => false,
            'remove_feed_links' => false,
            'remove_feed_links_extra' => false,
            'remove_adjacent_posts_links' => false,
            'remove_rest_api_links' => false,
            'remove_oembed_links' => false,
            'disable_xmlrpc' => false,
            'remove_dns_prefetch' => false,
            'remove_version_numbers' => false,
            'remove_jquery_migrate' => false,
            'disable_self_pingbacks' => false,
            'limit_post_revisions' => false,
            'revision_limit' => 5,
            'strip_category_base' => false,
            'remove_replytocom' => false,
            'redirect_attachments' => false,
            'attachment_redirect_type' => 'parent',
        ];
        $head_cleanup = get_option('prorank_seo_head_cleanup', []);
        $head_cleanup = wp_parse_args(is_array($head_cleanup) ? $head_cleanup : [], $head_defaults);
        $head_cleanup['remove_wp_generator'] = true;
        $head_cleanup['remove_rsd_link'] = true;
        $head_cleanup['remove_wlwmanifest_link'] = true;
        $head_cleanup['remove_emoji_scripts'] = true;
        $head_cleanup['remove_wp_shortlink'] = true;
        $head_cleanup['remove_oembed_links'] = true;
        $head_cleanup['remove_rest_api_links'] = true;
        $head_cleanup['disable_xmlrpc'] = true;
        $head_cleanup['remove_version_numbers'] = true;
        $head_cleanup['disable_self_pingbacks'] = true;
        update_option('prorank_seo_head_cleanup', $head_cleanup);

        // Database/server settings (safe runtime optimizations)
        $database = get_option('prorank_database_settings', []);
        $database = $this->sanitize_database_settings_payload(is_array($database) ? $database : []);
        $database['auto_cleanup_enabled'] = true;
        $database['cleanup_schedule'] = 'weekly';
        $database['cleanup_items']['revisions'] = true;
        $database['cleanup_items']['spam_comments'] = true;
        $database['cleanup_items']['expired_transients'] = true;
        $database['heartbeat_control'] = true;
        $database['heartbeat_frequency'] = 60;
        update_option('prorank_database_settings', $this->sanitize_database_settings_payload($database));

        delete_transient('prorank_dashboard_stats');

        return new WP_REST_Response([
            'success' => true,
            'message' => __('Bloat cleanup preset applied successfully.', 'prorank-seo'),
            'applied' => [
                'advanced' => [
                    'disable_emojis',
                    'disable_xmlrpc',
                    'remove_version_strings',
                ],
                'head_cleanup' => [
                    'remove_wp_generator',
                    'remove_rsd_link',
                    'remove_wlwmanifest_link',
                    'remove_emoji_scripts',
                    'remove_wp_shortlink',
                    'remove_oembed_links',
                    'remove_rest_api_links',
                    'disable_xmlrpc',
                    'remove_version_numbers',
                    'disable_self_pingbacks',
                ],
                'database' => [
                    'auto_cleanup_enabled',
                    'cleanup_schedule',
                    'cleanup_items.revisions',
                    'cleanup_items.spam_comments',
                    'cleanup_items.expired_transients',
                    'heartbeat_control',
                    'heartbeat_frequency',
                ],
            ],
        ], 200);
    }

    /**
     * Get image optimization statistics
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function get_image_stats(WP_REST_Request $request): WP_REST_Response {
        // Use module stats as single source of truth
        $module_enabled = false;
        $stats = [
            'total_images' => 0,
            'optimized_images' => 0,
            'saved_bytes' => 0,
            'optimization_percentage' => 0,
            'backup_size' => 0,
            'backup_size_formatted' => '',
            'already_modern_images' => 0,
            'unsupported_images' => 0,
            'source_images' => 0,
            'pending' => 0,
            'unresolved_images' => 0,
            'failed_images' => 0,
            'retryable_failed_images' => 0,
            'exhausted_failed_images' => 0,
            'no_gain_images' => 0,
            'bulk_retry_limit' => 0,
        ];

        $plugin = \ProRank\SEO\Plugin::get_instance();
        if ($plugin && $plugin->is_initialized()) {
            $module = $plugin->modules()->get_module('image_optimization');
            if ($module && method_exists($module, 'get_stats')) {
                $module_enabled = method_exists($module, 'is_enabled')
                    ? (bool) $module->is_enabled()
                    : false;
                $module_stats = $module->get_stats();
                $stats['total_images'] = (int) ($module_stats['total'] ?? 0);
                $stats['optimized_images'] = (int) ($module_stats['optimized'] ?? 0);
                $stats['saved_bytes'] = (int) ($module_stats['total_savings'] ?? $module_stats['bytes_saved'] ?? 0);
                $stats['optimization_percentage'] = $stats['total_images'] > 0
                    ? round(($stats['optimized_images'] / $stats['total_images']) * 100, 2)
                    : 0;
                $stats['already_modern_images'] = (int) ($module_stats['already_modern_images'] ?? 0);
                $stats['unsupported_images'] = (int) ($module_stats['unsupported_images'] ?? 0);
                $stats['source_images'] = (int) ($module_stats['source_images'] ?? 0);
                $stats['pending'] = (int) ($module_stats['pending'] ?? 0);
                $stats['unresolved_images'] = (int) ($module_stats['unresolved_images'] ?? 0);
                $stats['failed_images'] = (int) ($module_stats['failed_images'] ?? 0);
                $stats['retryable_failed_images'] = (int) ($module_stats['retryable_failed_images'] ?? 0);
                $stats['exhausted_failed_images'] = (int) ($module_stats['exhausted_failed_images'] ?? 0);
                $stats['no_gain_images'] = (int) ($module_stats['no_gain_images'] ?? 0);
                $stats['bulk_retry_limit'] = (int) ($module_stats['bulk_retry_limit'] ?? 0);
                if (!empty($module_stats['backup_size'])) {
                    $stats['backup_size'] = (int) $module_stats['backup_size'];
                    $stats['backup_size_formatted'] = $module_stats['backup_size_formatted'] ?? '';
                }
            }
        }

        return new WP_REST_Response([
            'module_enabled' => $module_enabled,
            'stats' => $stats,
        ], 200);
    }

    /**
     * Ingest lightweight RUM metrics (INP/CLS/LCP)
     */
    public function ingest_rum(WP_REST_Request $request): WP_REST_Response {
        $data = $request->get_json_params();
        $sample = [
            'url' => isset($data['url']) ? esc_url_raw($data['url']) : '',
            'lcp' => isset($data['lcp']) ? (int) $data['lcp'] : 0,
            'cls' => isset($data['cls']) ? (float) $data['cls'] : 0,
            'inp' => isset($data['inp']) ? (int) $data['inp'] : 0,
            'ts'  => time(),
        ];

        // Basic rate limit (per IP, per minute)
        $ip = prorank_get_server_var('REMOTE_ADDR');
        if (!empty($ip)) {
            $rl_key = 'prorank_rum_rl_' . md5($ip);
            $count = (int) get_transient($rl_key);
            if ($count >= 60) {
                return new WP_REST_Response(['success' => false, 'message' => 'Rate limited'], 429);
            }
            set_transient($rl_key, $count + 1, MINUTE_IN_SECONDS);
        }

        $sample['url'] = $this->normalize_site_url($sample['url']);

        if (empty($sample['url'])) {
            return new WP_REST_Response(['success' => false, 'message' => 'Invalid'], 400);
        }

        // Clamp values to reasonable bounds (helps avoid poisoning / huge payloads)
        $sample['lcp'] = max(0, min(60000, (int) $sample['lcp']));
        $sample['inp'] = max(0, min(60000, (int) $sample['inp']));
        $sample['cls'] = max(0.0, min(10.0, (float) $sample['cls']));

        $buffer = get_option('prorank_rum_buffer', []);
        if (!is_array($buffer)) {
            $buffer = [];
        }
        array_unshift($buffer, $sample);
        $buffer = array_slice($buffer, 0, 50); // keep last 50 samples
        update_option('prorank_rum_buffer', $buffer, false);

        return new WP_REST_Response(['success' => true], 200);
    }

    /**
     * Normalize and scope a URL to this site (prevents storing arbitrary external URLs)
     */
    private function normalize_site_url(string $url): string {
        $url = trim($url);
        if ($url === '') {
            return '';
        }

        $parsed = wp_parse_url($url);
        if ($parsed === false) {
            return '';
        }

        $site_host = wp_parse_url(home_url(), PHP_URL_HOST);
        $host = $parsed['host'] ?? '';

        if (!empty($host) && !empty($site_host) && strtolower($host) !== strtolower($site_host)) {
            return '';
        }

        $path = $parsed['path'] ?? '/';
        return $path !== '' ? $path : '/';
    }

    /**
     * Start bulk image optimization
     *
     * FREE VERSION: Bulk optimization is LOCAL ONLY to prevent cloud cost spikes.
     * Cloud credits are reserved for single image optimization.
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function bulk_optimize_images(WP_REST_Request $request): WP_REST_Response {
        // FREE VERSION: Force local-only bulk optimization
        // This prevents cloud cost spikes and abuse. Cloud credits are for single image ops.
        $settings = get_option('prorank_image_optimization_settings', []);
        $current_method = isset($settings['optimization_method']) ? (string) $settings['optimization_method'] : 'local';
        $previous_method = get_option('prorank_bulk_prev_optimization_method', '');

        if ($current_method !== 'local' && $previous_method === '') {
            update_option('prorank_bulk_prev_optimization_method', $current_method, false);
        }

        $settings['optimization_method'] = 'local';
        update_option('prorank_image_optimization_settings', $settings, false);

        // Schedule bulk optimization
        if (!wp_next_scheduled('prorank_bulk_image_optimization')) {
            wp_schedule_single_event(time() + 10, 'prorank_bulk_image_optimization');
        }

        return new WP_REST_Response([
            'success' => true,
            'message' => __('Bulk optimization started in background (local processing)', 'prorank-seo'),
        ], 200);
    }

    /**
     * Run the selected database cleanup tasks.
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function optimize_database(WP_REST_Request $request): WP_REST_Response {
        $cleanup_items = $request->get_param('items');
        if (!is_array($cleanup_items)) {
            $cleanup_items = [];
        }

        $module = $this->get_database_optimization_module();
        if (!$module) {
            return new WP_REST_Response([
                'error' => __('Database cleanup module not found.', 'prorank-seo'),
            ], 400);
        }

        $cleanup_types = $module->get_cleanup_types_from_items($cleanup_items);
        if (empty($cleanup_types)) {
            return new WP_REST_Response([
                'success' => false,
                'message' => __('Select at least one cleanup item to run.', 'prorank-seo'),
                'error' => __('Select at least one cleanup item to run.', 'prorank-seo'),
            ], 200);
        }

        $cleanup_total = 0;
        $cleanup_results = [];
        $optimized_tables = [];

        foreach ($cleanup_types as $cleanup_type) {
            $result = $module->cleanup_database_type($cleanup_type);
            $cleanup_results[$cleanup_type] = $result;

            if (empty($result['success']) && empty($result['partial_success'])) {
                continue;
            }

            $cleanup_total += (int) ($result['deleted'] ?? 0);

            if (!empty($result['optimized_tables']) && is_array($result['optimized_tables'])) {
                foreach ($result['optimized_tables'] as $optimized_table) {
                    if (is_string($optimized_table) && $optimized_table !== '') {
                        $optimized_tables[$optimized_table] = true;
                    }
                }
            } elseif (!empty($result['optimized']) && !empty($result['table']) && is_string($result['table'])) {
                $optimized_tables[$result['table']] = true;
            }
        }

        $optimized_count = count($optimized_tables);
        $message = $cleanup_total > 0
            ? sprintf(
                __('Deleted %1$d items across %2$d cleanup tasks and optimized %3$d affected tables.', 'prorank-seo'),
                $cleanup_total,
                count($cleanup_types),
                $optimized_count
            )
            : __('Selected cleanup completed. No matching records were found.', 'prorank-seo');

        return new WP_REST_Response([
            'success' => true,
            'tables_optimized' => $optimized_count,
            'cleaned' => $cleanup_total,
            'cleanup_results' => $cleanup_results,
            'tasks_run' => count($cleanup_types),
            'message' => $message,
        ], 200);
    }

    /**
     * Resolve the database optimization module instance.
     *
     * @return \ProRank\SEO\Modules\Performance\DatabaseOptimizationModule|null
     */
    private function get_database_optimization_module(): ?\ProRank\SEO\Modules\Performance\DatabaseOptimizationModule {
        $plugin = \ProRank\SEO\Plugin::get_instance();
        if (!$plugin || !$plugin->is_initialized()) {
            return null;
        }

        $module_manager = $plugin->modules();
        if (!$module_manager) {
            return null;
        }

        $module = $module_manager->get_module('database_optimization');
        return $module instanceof \ProRank\SEO\Modules\Performance\DatabaseOptimizationModule ? $module : null;
    }

    /**
     * Get database statistics
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function get_database_stats(WP_REST_Request $request): WP_REST_Response {
        global $wpdb;

        // Get table count and size
        $tables = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT
                    COUNT(*) as count,
                    COALESCE(SUM(data_length + index_length), 0) as size
                FROM information_schema.TABLES
                WHERE table_schema = %s",
                $wpdb->dbname
            )
        );

        $transient_prefix = $wpdb->esc_like('_transient_') . '%';
        $site_transient_prefix = $wpdb->esc_like('_site_transient_') . '%';

        // Get revision count
        $revisions_count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM %i
                 WHERE post_type = %s",
                $wpdb->posts,
                'revision'
            )
        );

        // Get trash counts
        $trashed_posts_count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM %i
                 WHERE post_status = %s",
                $wpdb->posts,
                'trash'
            )
        );

        $trashed_comments_count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM %i
                 WHERE comment_approved = %s",
                $wpdb->comments,
                'trash'
            )
        );

        $trash_count = (int) $trashed_posts_count + (int) $trashed_comments_count;

        // Get transient count
        $transients_count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM %i
                 WHERE option_name LIKE %s
                    OR option_name LIKE %s",
                $wpdb->options,
                $transient_prefix,
                $site_transient_prefix
            )
        );

        $pending_comments_count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM %i
                 WHERE comment_approved = %s",
                $wpdb->comments,
                '0'
            )
        );

        $spam_comments_count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM %i
                 WHERE comment_approved = %s",
                $wpdb->comments,
                'spam'
            )
        );

        // Get orphaned metadata count
        $orphaned_postmeta = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM %i pm
                 LEFT JOIN %i p ON pm.post_id = p.ID
                 WHERE p.ID IS NULL",
                $wpdb->postmeta,
                $wpdb->posts
            )
        );
        $orphaned_commentmeta = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM %i cm
                 LEFT JOIN %i c ON cm.comment_id = c.comment_ID
                 WHERE c.comment_ID IS NULL",
                $wpdb->commentmeta,
                $wpdb->comments
            )
        );
        $orphaned_usermeta = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM %i um
                 LEFT JOIN %i u ON um.user_id = u.ID
                 WHERE u.ID IS NULL",
                $wpdb->usermeta,
                $wpdb->users
            )
        );
        $orphaned_termmeta = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM %i tm
                 LEFT JOIN %i t ON tm.term_id = t.term_id
                 WHERE t.term_id IS NULL",
                $wpdb->termmeta,
                $wpdb->terms
            )
        );
        $orphaned_relationships = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM %i tr
                 LEFT JOIN %i p ON tr.object_id = p.ID
                 WHERE p.ID IS NULL",
                $wpdb->term_relationships,
                $wpdb->posts
            )
        );
        $orphaned_meta = $orphaned_postmeta + $orphaned_commentmeta + $orphaned_usermeta + $orphaned_termmeta + $orphaned_relationships;

        return new WP_REST_Response([
            'stats' => [
                'table_count' => (int) ($tables->count ?? 0),
                'total_size' => $this->format_bytes((int) ($tables->size ?? 0)),
                'revisions_count' => (int) $revisions_count,
                'trash_count' => (int) $trash_count,
                'trash_posts_count' => (int) $trashed_posts_count,
                'trash_comments_count' => (int) $trashed_comments_count,
                'pending_comments_count' => (int) $pending_comments_count,
                'spam_comments_count' => (int) $spam_comments_count,
                'transients_count' => (int) $transients_count,
                'orphaned_meta' => (int) $orphaned_meta,
            ],
        ], 200);
    }

    /**
     * Format bytes to human readable
     *
     * @param int $bytes Size in bytes
     * @return string Formatted size
     */
    /**
     * Get server information
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function get_server_info(WP_REST_Request $request): WP_REST_Response {
        global $wpdb;

        // PHP info
        $php_version = phpversion();
        $memory_limit = ini_get('memory_limit');
        $max_execution_time = ini_get('max_execution_time');
        $upload_max_filesize = ini_get('upload_max_filesize');
        $post_max_size = ini_get('post_max_size');

        // MySQL/MariaDB info
        $mysql_version = $wpdb->get_var('SELECT VERSION()');
        $mysql_type = (stripos($mysql_version, 'mariadb') !== false) ? 'MariaDB' : 'MySQL';

        // OPcache status
        $opcache_enabled = function_exists('opcache_get_status') && @opcache_get_status() !== false;
        $opcache_info = [];
        if ($opcache_enabled) {
            $status = @opcache_get_status();
            if ($status) {
                $opcache_info = [
                    'enabled' => true,
                    'memory_usage' => isset($status['memory_usage']['used_memory']) 
                        ? round($status['memory_usage']['used_memory'] / 1048576, 2) . ' MB' 
                        : 'N/A',
                    'hit_rate' => isset($status['opcache_statistics']['opcache_hit_rate'])
                        ? round($status['opcache_statistics']['opcache_hit_rate'], 2) . '%'
                        : 'N/A',
                ];
            }
        }

        // Object cache status
        $object_cache_type = 'none';
        $object_cache_info = [];
        $object_cache_available_backends = [];
        $object_cache_dropin_exists = file_exists(WP_CONTENT_DIR . '/object-cache.php');

        if (class_exists('Redis') || class_exists('Predis\\Client')) {
            $object_cache_available_backends[] = 'redis';
        }
        if (class_exists('Memcached')) {
            $object_cache_available_backends[] = 'memcached';
        }
        if (class_exists('Memcache')) {
            $object_cache_available_backends[] = 'memcache';
        }
        if (function_exists('apcu_fetch')) {
            $object_cache_available_backends[] = 'apcu';
        }

        if (wp_using_ext_object_cache()) {
            global $wp_object_cache;
            if (class_exists('Redis') && isset($wp_object_cache) && method_exists($wp_object_cache, 'redis_instance')) {
                $object_cache_type = 'redis';
                try {
                    $redis = $wp_object_cache->redis_instance();
                    if ($redis && method_exists($redis, 'info')) {
                        $info = $redis->info();
                        $object_cache_info = [
                            'version' => $info['redis_version'] ?? 'N/A',
                            'memory' => isset($info['used_memory_human']) ? $info['used_memory_human'] : 'N/A',
                        ];
                    }
                } catch (\Exception $e) {
                    // Redis connection failed
                }
            } elseif (class_exists('Memcached')) {
                $object_cache_type = 'memcached';
            } elseif (class_exists('Memcache')) {
                $object_cache_type = 'memcache';
            } else {
                $object_cache_type = 'external';
            }
        }

        // Web server info
        $server_software = prorank_get_server_var('SERVER_SOFTWARE');
        if ($server_software === '') {
            $server_software = 'Unknown';
        }

        return new WP_REST_Response([
            'info' => [
                'php_version' => $php_version,
                'memory_limit' => $memory_limit,
                'max_execution_time' => $max_execution_time . 's',
                'upload_max_filesize' => $upload_max_filesize,
                'post_max_size' => $post_max_size,
                'mysql_version' => $mysql_version,
                'mysql_type' => $mysql_type,
                'server_software' => $server_software,
                'opcache_enabled' => $opcache_enabled,
                'opcache_info' => $opcache_info,
                'object_cache_enabled' => wp_using_ext_object_cache(),
                'object_cache_type' => $object_cache_type,
                'object_cache_info' => $object_cache_info,
                'object_cache_dropin_exists' => $object_cache_dropin_exists,
                'object_cache_available_backends' => $object_cache_available_backends,
            ],
        ], 200);
    }

    /**
     * Format bytes to human readable
     *
     * @param int $bytes Size in bytes
     * @return string Formatted size
     */
    private function format_bytes(int $bytes): string {
        if ($bytes < 1024) {
            return $bytes . ' B';
        } elseif ($bytes < 1048576) {
            return round($bytes / 1024, 2) . ' KB';
        } elseif ($bytes < 1073741824) {
            return round($bytes / 1048576, 2) . ' MB';
        } else {
            return round($bytes / 1073741824, 2) . ' GB';
        }
    }

    /**
     * Apply unused CSS optimizations by generating purged CSS files
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function apply_unused_css_optimizations(WP_REST_Request $request): WP_REST_Response {
        $generated_files = [];
        $generated_file_map = [];

        try {
            // Get scan results
            $results = get_option('prorank_unused_css_results', null);
            $stored_safelist = get_option('prorank_unused_css_safelist', '');

            if (!$results || empty($results['stylesheets'])) {
                return new WP_REST_Response([
                    'success' => false,
                    'message' => __('No scan results found. Please run a CSS scan first.', 'prorank-seo'),
                ], 400);
            }

            $quality_gate = $this->ensure_ucss_scan_quality($results, is_string($stored_safelist) ? $stored_safelist : '');
            if (empty($quality_gate['ok'])) {
                return new WP_REST_Response([
                    'success' => false,
                    'message' => (string) ($quality_gate['message'] ?? __('Unused CSS scan quality is insufficient. Please re-run scan.', 'prorank-seo')),
                    'reason' => (string) ($quality_gate['reason'] ?? 'scan_quality_failed'),
                    'quality_gate' => [
                        'refreshed' => !empty($quality_gate['refreshed']),
                        'issues_before' => $quality_gate['issues_before'] ?? [],
                        'issues_after' => $quality_gate['issues_after'] ?? [],
                    ],
                ], 422);
            }

            $results = $quality_gate['results'] ?? $results;
            $used_selectors = $results['used_selectors'] ?? [];
            if (!is_array($used_selectors) || empty($used_selectors)) {
                return new WP_REST_Response([
                    'success' => false,
                    'message' => __('Scan results are missing used selectors. Please re-run the scan.', 'prorank-seo'),
                ], 400);
            }

            // Create cache directory — must match UnusedCssModule::get_optimized_css_url()
            // which looks for files in WP_CONTENT_DIR/cache/prorank-seo/css/
            $cache_dir = WP_CONTENT_DIR . '/cache/prorank-seo/css';
            if (!is_dir($cache_dir)) {
                wp_mkdir_p($cache_dir);
            }
            if (!is_dir($cache_dir) || !wp_is_writable($cache_dir)) {
                return new WP_REST_Response([
                    'success' => false,
                    'message' => __('CSS cache directory is not writable. Please check file permissions for wp-content/uploads/prorank-cache/css.', 'prorank-seo'),
                ], 500);
            }

            $files_generated = 0;
            $total_saved = 0;
            $safelist = $this->get_safelist_patterns();
            if (!empty($results['safelist_applied']) && is_array($results['safelist_applied'])) {
                $safelist = array_merge($safelist, $results['safelist_applied']);
            }

            if (is_string($stored_safelist) && $stored_safelist !== '') {
                $lines = array_filter(array_map('trim', preg_split('/[\r\n]+/', $stored_safelist)));
                if (!empty($lines)) {
                    $safelist = array_merge($safelist, $lines);
                }
            }
            $site_host = wp_parse_url(home_url('/'), PHP_URL_HOST);

            $used_selectors_by_context = $results['used_selectors_by_context'] ?? [];
            $has_context_sets = is_array($used_selectors_by_context) && !empty($used_selectors_by_context);

            $context_files_generated = 0;
            $context_bytes_saved = 0;
            $safety_skipped_files = 0;
            $front_page_html_desktop = $this->fetch_html_document(home_url('/'), 'ProRank CSS Optimizer/1.0', true);
            $front_page_html_mobile = $this->fetch_html_document(
                home_url('/'),
                'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36',
                true
            );
            $front_page_html_for_generation = is_string($front_page_html_desktop) && $front_page_html_desktop !== ''
                ? $front_page_html_desktop
                : (is_string($front_page_html_mobile) ? $front_page_html_mobile : '');
            $front_page_runtime_selectors = $this->build_runtime_used_selectors_from_html($front_page_html_for_generation);
            $global_used_selectors = !empty($front_page_runtime_selectors)
                ? array_values(array_unique(array_merge($used_selectors, $front_page_runtime_selectors)))
                : $used_selectors;

            // Cache fetched stylesheets to avoid repeated HTTP requests.
            $original_css_cache = [];

            $stylesheets_by_context = $results['stylesheets_by_context'] ?? [];
            $has_stylesheets_by_context = is_array($stylesheets_by_context) && !empty($stylesheets_by_context);

            // Build the list of stylesheet URLs to optimize.
            $stylesheets_srcs = [];
            if ($has_stylesheets_by_context) {
                foreach ($stylesheets_by_context as $context => $sheet_srcs) {
                    if (!is_array($sheet_srcs)) {
                        continue;
                    }
                    foreach ($sheet_srcs as $src) {
                        if (is_string($src) && $src !== '' && strpos($src, 'inline-') !== 0 && !$this->is_generated_ucss_stylesheet($src)) {
                            $stylesheets_srcs[$src] = true;
                        }
                    }
                }
            } else {
                foreach ($results['stylesheets'] as $stylesheet) {
                    $src = $stylesheet['src'] ?? '';
                    if (is_string($src) && $src !== '' && strpos($src, 'inline-') !== 0 && !$this->is_generated_ucss_stylesheet($src)) {
                        $stylesheets_srcs[$src] = true;
                    }
                }
            }

            // Always merge live front-page stylesheets so hash-rotated combined CSS
            // files are included even when scan data is stale.
            if (is_string($front_page_html_for_generation) && $front_page_html_for_generation !== '') {
                $live_front_stylesheets = $this->extract_stylesheet_hrefs_from_html($front_page_html_for_generation);
                if (is_array($live_front_stylesheets) && !empty($live_front_stylesheets)) {
                    foreach ($live_front_stylesheets as $src) {
                        if (is_string($src) && $src !== '' && strpos($src, 'inline-') !== 0 && !$this->is_generated_ucss_stylesheet($src)) {
                            $stylesheets_srcs[$src] = true;
                        }
                    }
                }
            }

            $front_page_stylesheet_lookup = [];
            if (!empty($live_front_stylesheets) && is_array($live_front_stylesheets)) {
                foreach ($live_front_stylesheets as $front_src) {
                    if (!is_string($front_src) || $front_src === '') {
                        continue;
                    }
                    if (strpos($front_src, '//') === 0) {
                        $front_src = 'https:' . $front_src;
                    } elseif (strpos($front_src, '/') === 0) {
                        $front_src = home_url($front_src);
                    }
                    $front_key = (string) preg_replace('/[#?].*$/', '', $front_src);
                    if ($front_key !== '') {
                        $front_page_stylesheet_lookup[$front_key] = true;
                    }
                }
            }

            // 1) Generate global UCSS files (apply everywhere): md5($src).css
            foreach (array_keys($stylesheets_srcs) as $src) {

                // Convert relative URLs to absolute for requests + hashing consistency.
                if (strpos($src, '//') === 0) {
                    $src = 'https:' . $src;
                } elseif (strpos($src, '/') === 0) {
                    $src = home_url($src);
                }

                // Skip Google Fonts CSS from UCSS generation
                if (strpos($src, 'fonts.googleapis.com') !== false ||
                    strpos($src, 'fonts.gstatic.com') !== false) {
                    continue;
                }

                // Skip external stylesheets (different host).
                $src_host = wp_parse_url($src, PHP_URL_HOST);
                if (!empty($src_host) && !empty($site_host) && $src_host !== $site_host) {
                    continue;
                }

                $original_css = $this->fetch_stylesheet_content($src, $original_css_cache);
                if (empty($original_css)) {
                    continue;
                }

                // Save to cache
                $original_size = strlen($original_css);
                $src_lookup_key = (string) preg_replace('/[#?].*$/', '', $src);
                $is_front_page_source = isset($front_page_stylesheet_lookup[$src_lookup_key]);
                $allow_passthrough_candidate = false;

                // 1) Global UCSS file (applies everywhere): ucss-{md5($src)}.css
                $optimized_css = $this->purge_unused_css($original_css, $global_used_selectors, $safelist);
                $optimized_size = strlen($optimized_css);
                $saved = ($original_size - $optimized_size);

                if ($saved <= 0 && $is_front_page_source && $this->is_prorank_combined_stylesheet($src)) {
                    // Ensure runtime guard has at least one front-page candidate file.
                    // This passthrough candidate keeps the page stable even when purge
                    // yields no safe byte savings for the combined stylesheet.
                    $optimized_css = $original_css;
                    $optimized_size = $original_size;
                    $saved = 0;
                    $allow_passthrough_candidate = true;
                }

                if ($saved > 0 || $allow_passthrough_candidate) {
                    $safety = $this->evaluate_ucss_safety(
                        $original_css,
                        $optimized_css,
                        $front_page_html_for_generation
                    );
                    if (!$safety['safe']) {
                        $safety_skipped_files++;
                        continue;
                    }

                    $cache_key = md5($src);
                    $cache_file = $cache_dir . '/' . $cache_key . '.css';
                    $written = file_put_contents($cache_file, $optimized_css);
                    if ($written !== false) {
                        $gzipped = gzencode($optimized_css, 9);
                        if ($gzipped !== false) {
                            @file_put_contents($cache_file . '.gz', $gzipped);
                        }
                        $files_generated++;
                        if ($saved > 0) {
                            $total_saved += $saved;
                        }
                        $generated_files[] = $cache_file;
                        $this->register_generated_ucss_file_entry($generated_file_map, $src, '', $cache_file);
                        $this->write_normalized_ucss_alias($src, '', $optimized_css, $cache_dir, $generated_files);
                    }
                }
            }

            // 2) Generate context-specific UCSS files for ProRank combined CSS:
            // ucss-{md5($src . '|' . $context)}.css (frontend prefers these when present)
            if ($has_context_sets) {
                foreach ($used_selectors_by_context as $context => $context_selectors) {
                    if (!is_string($context) || $context === '' || !is_array($context_selectors) || empty($context_selectors)) {
                        continue;
                    }

                    // Prefer live HTML extraction for the current context so we don't optimize stale combined files.
                    $context_url = $this->get_sample_url_for_context($context);
                    $context_sheets = [];
                    $context_html = '';

                    if (is_string($context_url) && $context_url !== '') {
                        $html = $this->fetch_html_document($context_url, 'ProRank CSS Optimizer/1.0', true);
                        if (is_string($html) && $html !== '') {
                            $context_html = $html;
                            $context_sheets = $this->extract_stylesheet_hrefs_from_html($html);
                        }
                    }

                    // Fallback to scan result mapping if HTML fetch failed.
                    if (empty($context_sheets) && $has_stylesheets_by_context) {
                        $context_sheets = $stylesheets_by_context[$context] ?? [];
                    }

                    if ($context === 'front_page' && $context_html === '' && $front_page_html_for_generation !== '') {
                        $context_html = $front_page_html_for_generation;
                    }

                    if (!is_array($context_sheets) || empty($context_sheets)) {
                        continue;
                    }

                    foreach ($context_sheets as $src) {
                        if (!is_string($src) || $src === '' || !$this->is_prorank_combined_stylesheet($src)) {
                            continue;
                        }

                        // Normalize URL to match global processing and cache keys.
                        if (strpos($src, '//') === 0) {
                            $src = 'https:' . $src;
                        } elseif (strpos($src, '/') === 0) {
                            $src = home_url($src);
                        }

                        // Skip Google Fonts CSS from UCSS generation
                        if (strpos($src, 'fonts.googleapis.com') !== false ||
                            strpos($src, 'fonts.gstatic.com') !== false) {
                            continue;
                        }

                        // Skip external stylesheets (different host).
                        $src_host = wp_parse_url($src, PHP_URL_HOST);
                        if (!empty($src_host) && !empty($site_host) && $src_host !== $site_host) {
                            continue;
                        }

                        $original_css = $this->fetch_stylesheet_content($src, $original_css_cache);
                        if (empty($original_css)) {
                            continue;
                        }

                        $original_size = strlen($original_css);
                        $context_runtime_selectors = $this->build_runtime_used_selectors_from_html($context_html);
                        $effective_context_selectors = !empty($context_runtime_selectors)
                            ? array_values(array_unique(array_merge($context_selectors, $context_runtime_selectors)))
                            : $context_selectors;

                        $context_css = $this->purge_unused_css($original_css, $effective_context_selectors, $safelist);
                        $context_size = strlen($context_css);
                        $context_saved = ($original_size - $context_size);

                        if ($context_saved <= 0) {
                            continue;
                        }

                        $safety = $this->evaluate_ucss_safety($original_css, $context_css, $context_html);
                        if (!$safety['safe']) {
                            $safety_skipped_files++;
                            continue;
                        }

                        $context_key = md5($src . '|' . $context);
                        $context_file = $cache_dir . '/' . $context_key . '.css';
                        $written = file_put_contents($context_file, $context_css);
                        if ($written === false) {
                            continue;
                        }

                        $gzipped = gzencode($context_css, 9);
                        if ($gzipped !== false) {
                            @file_put_contents($context_file . '.gz', $gzipped);
                        }

                        $context_files_generated++;
                        $context_bytes_saved += $context_saved;
                        $generated_files[] = $context_file;
                        $this->register_generated_ucss_file_entry($generated_file_map, $src, $context, $context_file);
                        $this->write_normalized_ucss_alias($src, $context, $context_css, $cache_dir, $generated_files);
                    }
                }
            }

            if ($files_generated === 0 && $context_files_generated === 0) {
                $rolled_back_files = $this->rollback_generated_ucss_files($generated_files);
                return new WP_REST_Response([
                    'success' => false,
                    'message' => __('No safe optimized CSS could be generated. UCSS was not enabled to prevent layout regressions.', 'prorank-seo'),
                    'safety_skipped_files' => $safety_skipped_files,
                    'rolled_back_files' => $rolled_back_files,
                ], 422);
            }

            // Runtime verification against live front page before enabling UCSS globally.
            $runtime_safety_desktop = $this->validate_front_page_ucss_runtime_safety(
                $generated_file_map,
                $original_css_cache,
                $front_page_html_desktop,
                'desktop'
            );
            $runtime_safety_mobile = $this->validate_front_page_ucss_runtime_safety(
                $generated_file_map,
                $original_css_cache,
                $front_page_html_mobile,
                'mobile'
            );
            $runtime_safety = !$runtime_safety_desktop['safe'] ? $runtime_safety_desktop : $runtime_safety_mobile;

            if (!$runtime_safety['safe']) {
                $rolled_back_files = $this->rollback_generated_ucss_files($generated_files);
                delete_option('prorank_unused_css_applied');
                $settings = get_option('prorank_asset_optimization_settings', []);
                $settings['unused_css_enabled'] = false;
                update_option('prorank_asset_optimization_settings', $settings);

                return new WP_REST_Response([
                    'success' => false,
                    'message' => __('UCSS failed runtime safety verification and was rolled back automatically.', 'prorank-seo'),
                    'reason' => $runtime_safety['reason'] ?? 'runtime_guard_failed',
                    'source' => $runtime_safety['source'] ?? '',
                    'viewport' => $runtime_safety['viewport'] ?? '',
                    'rolled_back_files' => $rolled_back_files,
                    'safety_skipped_files' => $safety_skipped_files,
                ], 422);
            }

            // Mark optimizations as applied
            update_option('prorank_unused_css_applied', [
                'applied_at' => current_time('mysql'),
                'files_generated' => $files_generated,
                'bytes_saved' => $total_saved,
                'context_files_generated' => $context_files_generated,
                'context_bytes_saved' => $context_bytes_saved,
                'safety_skipped_files' => $safety_skipped_files,
                'runtime_guard' => 'passed',
            ]);

            // Enable the optimization in settings
            $settings = get_option('prorank_asset_optimization_settings', []);
            $settings['unused_css_enabled'] = true;
            update_option('prorank_asset_optimization_settings', $settings);

            // ProRank page cache serves static HTML; purge it so new HTML can include swapped CSS URLs.
            $purged_files = $this->purge_prorank_html_cache_for_current_host();

            return new WP_REST_Response([
                'success' => true,
                'message' => sprintf(
/* translators: %1$: value, %2$: value, %3$: value */
                    __('Generated %1$d optimized CSS files (+%2$d context files). Saving %3$s.', 'prorank-seo'),
                    $files_generated,
                    $context_files_generated,
                    $this->format_bytes($total_saved + $context_bytes_saved)
                ),
                'files_generated' => $files_generated,
                'bytes_saved' => $total_saved,
                'context_files_generated' => $context_files_generated,
                'context_bytes_saved' => $context_bytes_saved,
                'safety_skipped_files' => $safety_skipped_files,
                'runtime_guard' => 'passed',
                'html_cache_purged_files' => $purged_files,
                'quality_gate' => [
                    'refreshed' => !empty($quality_gate['refreshed']),
                    'issues_before' => $quality_gate['issues_before'] ?? [],
                    'issues_after' => $quality_gate['issues_after'] ?? [],
                ],
            ], 200);

        } catch (\Throwable $e) {
            $rolled_back_files = $this->rollback_generated_ucss_files($generated_files);
            return new WP_REST_Response([
                'success' => false,
                'message' => $e->getMessage(),
                'rolled_back_files' => $rolled_back_files,
            ], 500);
        }
    }

    /**
     * Purge ProRank HTML page cache for the current site host.
     *
     * Only clears the HTML page cache. CSS assets are stored under `wp-content/uploads/prorank-cache/`.
     *
     * @return int Number of files deleted
     */
    private function purge_prorank_html_cache_for_current_host(): int {
        $host = wp_parse_url(home_url('/'), PHP_URL_HOST);
        if (empty($host)) {
            return 0;
        }

        $upload_dir = wp_upload_dir();
        $upload_basedir = is_array($upload_dir) && !empty($upload_dir['basedir'])
            ? rtrim((string) $upload_dir['basedir'], '/\\')
            : '';

        $cache_dirs = [
            WP_CONTENT_DIR . '/cache/prorank-seo/' . $host,
            WP_CONTENT_DIR . '/uploads/prorank-cache/html/' . $host,
        ];

        if ($upload_basedir !== '') {
            $cache_dirs[] = $upload_basedir . '/prorank-cache/html/' . $host;
            $cache_dirs[] = $upload_basedir . '/prorank-seo/page-cache/' . $host;
        }

        $cache_dirs = array_values(array_unique(array_filter($cache_dirs, static function ($dir): bool {
            return is_string($dir) && $dir !== '';
        })));

        $deleted = 0;
        foreach ($cache_dirs as $cache_dir) {
            if (!is_dir($cache_dir)) {
                continue;
            }

            $iterator = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($cache_dir, \RecursiveDirectoryIterator::SKIP_DOTS),
                \RecursiveIteratorIterator::CHILD_FIRST
            );

            foreach ($iterator as $file) {
                if ($file->isDir()) {
                    \ProRank\SEO\Core\FilesystemHelper::delete_dir($file->getPathname());
                    continue;
                }

                if (\ProRank\SEO\Core\FilesystemHelper::delete_file($file->getPathname())) {
                    $deleted++;
                }
            }

            \ProRank\SEO\Core\FilesystemHelper::delete_dir($cache_dir);
        }

        return $deleted;
    }

    /**
     * Check if a stylesheet is ProRank's combined CSS output.
     *
     * @param string $src Stylesheet URL
     * @return bool
     */
    private function is_prorank_combined_stylesheet(string $src): bool {
        return strpos($src, '/prorank-cache/css/combined-') !== false
            || strpos($src, '/wp-content/uploads/prorank-cache/css/combined-') !== false;
    }

    /**
     * Check if a stylesheet is an already-generated UCSS file.
     *
     * These are outputs, not safe scan/apply inputs.
     *
     * @param string $src Stylesheet URL
     * @return bool
     */
    private function is_generated_ucss_stylesheet(string $src): bool {
        return strpos($src, '/cache/prorank-seo/css/') !== false
            || strpos($src, '/wp-content/cache/prorank-seo/css/') !== false;
    }

    /**
     * Normalize stylesheet URLs for stable UCSS keying across rotating combined hashes.
     *
     * @param string $src Stylesheet URL
     * @return string
     */
    private function normalize_ucss_source_url(string $src): string {
        $src = trim(html_entity_decode($src, ENT_QUOTES));
        if ($src === '') {
            return '';
        }

        $without_fragment = (string) preg_replace('/#.*/', '', $src);
        $without_query = (string) preg_replace('/\?.*/', '', $without_fragment);

        if ($this->is_prorank_combined_stylesheet($without_query)) {
            return (string) preg_replace(
                '#/combined-[a-f0-9]{32}(?:\.min)?\.css$#i',
                '/combined.min.css',
                $without_query
            );
        }

        return $without_fragment;
    }

    /**
     * Write alias UCSS files for normalized source URLs.
     *
     * This keeps UCSS hits stable when combined CSS hash filenames rotate.
     *
     * @param string $src Original source URL
     * @param string $context Context key
     * @param string $css Optimized CSS body
     * @param string $cache_dir UCSS cache directory
     * @param array<int,string> $generated_files List of generated files (by reference)
     * @return void
     */
    private function write_normalized_ucss_alias(string $src, string $context, string $css, string $cache_dir, array &$generated_files): void {
        $normalized_src = $this->normalize_ucss_source_url($src);
        if ($normalized_src === '' || $normalized_src === $src) {
            return;
        }

        $alias_key = $context !== ''
            ? md5($normalized_src . '|' . $context)
            : md5($normalized_src);
        $alias_file = $cache_dir . '/' . $alias_key . '.css';

        if (file_exists($alias_file)) {
            return;
        }

        $written = file_put_contents($alias_file, $css);
        if ($written === false) {
            return;
        }

        $gzipped = gzencode($css, 9);
        if ($gzipped !== false) {
            @file_put_contents($alias_file . '.gz', $gzipped);
        }

        $generated_files[] = $alias_file;
    }

    /**
     * Get a representative URL for a scan context.
     *
     * Used to refresh context stylesheets (especially ProRank combined CSS) at apply-time so
     * we don't generate optimizations for stale cache filenames.
     *
     * @param string $context Context identifier (e.g. front_page, single_post)
     * @return string|null URL or null when not available
     */
    private function get_sample_url_for_context(string $context): ?string {
        switch ($context) {
            case 'front_page':
                return home_url('/');

            case 'single_page':
                $front_id = (int) get_option('page_on_front');
                $pages = get_posts([
                    'post_type' => 'page',
                    'post_status' => 'publish',
                    'posts_per_page' => 1,
                    'post__not_in' => $front_id ? [$front_id] : [],
                    'orderby' => 'date',
                    'order' => 'DESC',
                ]);
                if (!empty($pages)) {
                    $url = get_permalink($pages[0]);
                    return is_string($url) && $url !== '' ? $url : home_url('/');
                }
                return home_url('/');

            case 'single_post':
                $posts = get_posts([
                    'post_type' => 'post',
                    'post_status' => 'publish',
                    'posts_per_page' => 1,
                    'orderby' => 'date',
                    'order' => 'DESC',
                ]);
                if (!empty($posts)) {
                    $url = get_permalink($posts[0]);
                    return is_string($url) && $url !== '' ? $url : null;
                }
                return null;

            case 'archive_category':
                $cats = get_categories([
                    'hide_empty' => false,
                    'number' => 1,
                ]);
                if (!empty($cats)) {
                    $url = get_category_link($cats[0]);
                    return is_string($url) && $url !== '' ? $url : null;
                }
                return null;

            case 'archive':
                // Try the posts page when "Your homepage displays" is set to a static page.
                $page_for_posts = (int) get_option('page_for_posts');
                if ($page_for_posts) {
                    $url = get_permalink($page_for_posts);
                    return is_string($url) && $url !== '' ? $url : home_url('/');
                }
                return home_url('/');

            default:
                // Best-effort fallback.
                return home_url('/');
        }
    }

    /**
     * Extract stylesheet hrefs from HTML.
     *
     * @param string $html Raw HTML
     * @return array<int,string> Unique hrefs
     */
    private function extract_stylesheet_hrefs_from_html(string $html): array {
        $hrefs = [];

        if (preg_match_all("#<link[^>]+rel=[\"']stylesheet[\"'][^>]*href=[\"']([^\"']+)[\"']#i", $html, $matches)) {
            foreach (($matches[1] ?? []) as $href) {
                if (!is_string($href) || $href === '') {
                    continue;
                }
                $href = html_entity_decode($href, ENT_QUOTES);
                $hrefs[$href] = true;
            }
        }

        return array_keys($hrefs);
    }

    /**
     * Fetch HTML document for a URL.
     *
     * @param string $url URL to fetch
     * @param string $user_agent Optional user-agent string
     * @param bool $bypass_cache Append probe query and no-cache headers
     * @return string|null
     */
    private function fetch_html_document(string $url, string $user_agent = 'ProRank CSS Optimizer/1.0', bool $bypass_cache = false): ?string {
        if (!is_string($url) || $url === '') {
            return null;
        }

        if ($bypass_cache) {
            $url = add_query_arg([
                'prorank_ucss_probe' => (string) (time() . wp_rand(1000, 9999)),
            ], $url);
        }

        $request_args = [
            'timeout' => 30,
            'user-agent' => $user_agent !== '' ? $user_agent : 'ProRank CSS Optimizer/1.0',
        ];

        if ($bypass_cache) {
            $request_args['headers'] = [
                'Cache-Control' => 'no-cache, no-store, must-revalidate',
                'Pragma' => 'no-cache',
                'Expires' => '0',
            ];
        }

        $response = wp_remote_get($url, $request_args);

        if (is_wp_error($response)) {
            return null;
        }

        if ((int) wp_remote_retrieve_response_code($response) !== 200) {
            return null;
        }

        $body = wp_remote_retrieve_body($response);
        return is_string($body) && $body !== '' ? $body : null;
    }

    /**
     * Ensure unused-CSS scan data is fresh and representative before apply.
     *
     * Quality gate strategy:
     * 1) Validate existing scan data freshness + front-page stylesheet coverage.
     * 2) Auto-refresh by running scanner when quality is insufficient.
     * 3) Merge live front-page selectors/stylesheets as a final safety signal.
     *
     * @param array $results Existing scan results
     * @param string $stored_safelist Safelist string from settings
     * @return array{ok:bool,results:array,refreshed:bool,issues_before:array,issues_after:array,reason?:string,message?:string}
     */
    private function ensure_ucss_scan_quality(array $results, string $stored_safelist = ''): array {
        $front_page_html = $this->fetch_html_document(home_url('/'), 'ProRank CSS Optimizer/1.0', true);
        $live_stylesheets = [];
        if (is_string($front_page_html) && $front_page_html !== '') {
            foreach ($this->extract_stylesheet_hrefs_from_html($front_page_html) as $src) {
                if (is_string($src) && $src !== '' && !$this->is_generated_ucss_stylesheet($src)) {
                    $live_stylesheets[] = $src;
                }
            }
        }

        $issues_before = $this->assess_ucss_scan_quality_issues($results, $live_stylesheets);
        $refreshed = false;

        if (!empty($issues_before)) {
            $refreshed_results = $this->refresh_ucss_scan_results($stored_safelist);
            if (is_array($refreshed_results) && !empty($refreshed_results)) {
                $results = $refreshed_results;
                $refreshed = true;
            }

            if (is_string($front_page_html) && $front_page_html !== '') {
                $results = $this->merge_live_front_page_scan_signals($results, $front_page_html, $live_stylesheets);
            }

            update_option('prorank_unused_css_results', $results);
        }

        $issues_after = $this->assess_ucss_scan_quality_issues($results, $live_stylesheets);
        if (!empty($issues_after)) {
            return [
                'ok' => false,
                'results' => $results,
                'refreshed' => $refreshed,
                'issues_before' => $issues_before,
                'issues_after' => $issues_after,
                'reason' => 'scan_quality_failed',
                'message' => __('Unused CSS scan quality is insufficient after auto-refresh. Please run a fresh scan from Performance > Asset Optimization.', 'prorank-seo'),
            ];
        }

        return [
            'ok' => true,
            'results' => $results,
            'refreshed' => $refreshed,
            'issues_before' => $issues_before,
            'issues_after' => $issues_after,
        ];
    }

    /**
     * Assess scan-quality issues that can cause unsafe UCSS output.
     *
     * @param array $results Scan results
     * @param array<int,string> $live_stylesheets Live front-page stylesheet hrefs
     * @return array<int,string>
     */
    private function assess_ucss_scan_quality_issues(array $results, array $live_stylesheets): array {
        $issues = [];

        $used_selectors = $results['used_selectors'] ?? [];
        if (!is_array($used_selectors) || empty($used_selectors)) {
            $issues[] = 'missing_used_selectors';
        }

        $min_selector_count = (int) apply_filters('prorank_ucss_quality_gate_min_used_selectors', 120);
        if (is_array($used_selectors) && count($used_selectors) < $min_selector_count) {
            $issues[] = 'low_used_selector_count';
        }

        $scanned_at_raw = isset($results['scanned_at']) && is_string($results['scanned_at'])
            ? trim($results['scanned_at'])
            : '';
        if ($scanned_at_raw === '') {
            $issues[] = 'missing_scanned_at';
        } else {
            $scan_timestamp = strtotime($scanned_at_raw);
            $max_scan_age = (int) apply_filters('prorank_ucss_quality_gate_max_scan_age_seconds', 6 * HOUR_IN_SECONDS);
            if ($scan_timestamp === false) {
                $issues[] = 'invalid_scanned_at';
            } elseif ($max_scan_age > 0 && (time() - $scan_timestamp) > $max_scan_age) {
                $issues[] = 'stale_scan';
            }
        }

        $front_context_selectors = $results['used_selectors_by_context']['front_page'] ?? [];
        $min_front_context_selectors = (int) apply_filters('prorank_ucss_quality_gate_min_front_page_selectors', 80);
        if (!is_array($front_context_selectors) || count($front_context_selectors) < $min_front_context_selectors) {
            $issues[] = 'weak_front_page_context';
        }

        $scan_stylesheets = $this->collect_scan_stylesheet_sources($results);
        if (empty($scan_stylesheets)) {
            $issues[] = 'missing_scan_stylesheets';
        }

        $site_host = wp_parse_url(home_url('/'), PHP_URL_HOST);
        $live_internal = [];
        foreach ($live_stylesheets as $src) {
            $canonical = $this->canonicalize_scan_stylesheet_source((string) $src);
            if ($canonical === '') {
                continue;
            }

            $host = wp_parse_url($canonical, PHP_URL_HOST);
            if (!empty($host) && !empty($site_host) && $host !== $site_host) {
                continue;
            }

            $live_internal[$canonical] = true;
        }

        if (!empty($live_internal) && !empty($scan_stylesheets)) {
            $missing_live = 0;
            foreach (array_keys($live_internal) as $live_src) {
                if (!isset($scan_stylesheets[$live_src])) {
                    $missing_live++;
                }
            }

            $max_missing_live = (int) apply_filters('prorank_ucss_quality_gate_max_missing_live_stylesheets', 0);
            if ($missing_live > $max_missing_live) {
                $issues[] = 'missing_live_stylesheets';
            }
        }

        return array_values(array_unique($issues));
    }

    /**
     * Attempt automatic scanner refresh for quality-gate recovery.
     *
     * @param string $stored_safelist Safelist rules
     * @return array|null
     */
    private function refresh_ucss_scan_results(string $stored_safelist): ?array {
        unset($stored_safelist);
        return null;
    }

    /**
     * Merge live front-page selectors/stylesheets into scan results.
     *
     * @param array $results Scan results
     * @param string $front_page_html Front-page HTML
     * @param array<int,string> $live_stylesheets Optional pre-extracted stylesheet hrefs
     * @return array
     */
    private function merge_live_front_page_scan_signals(array $results, string $front_page_html, array $live_stylesheets = []): array {
        if ($front_page_html === '') {
            return $results;
        }

        $runtime_selectors = $this->build_runtime_used_selectors_from_html($front_page_html);
        if (!isset($results['used_selectors']) || !is_array($results['used_selectors'])) {
            $results['used_selectors'] = [];
        }
        $results['used_selectors'] = array_values(array_unique(array_merge($results['used_selectors'], $runtime_selectors)));

        if (!isset($results['used_selectors_by_context']) || !is_array($results['used_selectors_by_context'])) {
            $results['used_selectors_by_context'] = [];
        }
        $front_context_selectors = $results['used_selectors_by_context']['front_page'] ?? [];
        if (!is_array($front_context_selectors)) {
            $front_context_selectors = [];
        }
        $results['used_selectors_by_context']['front_page'] = array_values(array_unique(array_merge($front_context_selectors, $runtime_selectors)));

        if (empty($live_stylesheets)) {
            $live_stylesheets = $this->extract_stylesheet_hrefs_from_html($front_page_html);
        }

        if (!empty($live_stylesheets)) {
            $live_stylesheets = array_values(array_filter($live_stylesheets, function ($src): bool {
                return is_string($src) && $src !== '' && !$this->is_generated_ucss_stylesheet($src);
            }));
        }

        if (!isset($results['stylesheets_by_context']) || !is_array($results['stylesheets_by_context'])) {
            $results['stylesheets_by_context'] = [];
        }
        $front_context_stylesheets = $results['stylesheets_by_context']['front_page'] ?? [];
        if (!is_array($front_context_stylesheets)) {
            $front_context_stylesheets = [];
        }
        $results['stylesheets_by_context']['front_page'] = array_values(array_unique(array_merge($front_context_stylesheets, $live_stylesheets)));

        $stylesheets = [];
        if (!empty($results['stylesheets']) && is_array($results['stylesheets'])) {
            foreach ($results['stylesheets'] as $stylesheet) {
                if (!is_array($stylesheet)) {
                    continue;
                }
                $src = isset($stylesheet['src']) && is_string($stylesheet['src']) ? $stylesheet['src'] : '';
                if ($src === '') {
                    continue;
                }
                $stylesheets[$src] = $stylesheet;
            }
        }

        foreach ($live_stylesheets as $src) {
            if (!is_string($src) || $src === '') {
                continue;
            }
            if (!isset($stylesheets[$src])) {
                $stylesheets[$src] = [
                    'src' => $src,
                    'size' => 0,
                    'unused_size' => 0,
                    'unused_percentage' => 0,
                    'unused_count' => 0,
                    'total_selectors' => 0,
                ];
            }
        }

        $results['stylesheets'] = array_values($stylesheets);
        $results['scanned_at'] = current_time('mysql');
        $results['quality_gate'] = [
            'front_page_selector_count' => count($runtime_selectors),
            'front_page_stylesheet_count' => count(array_unique($live_stylesheets)),
            'refreshed_at' => current_time('mysql'),
        ];

        return $results;
    }

    /**
     * Collect canonicalized stylesheet sources from scan results.
     *
     * @param array $results Scan results
     * @return array<string,true>
     */
    private function collect_scan_stylesheet_sources(array $results): array {
        $sources = [];

        $stylesheets = $results['stylesheets'] ?? [];
        if (is_array($stylesheets)) {
            foreach ($stylesheets as $stylesheet) {
                if (!is_array($stylesheet)) {
                    continue;
                }
                $src = isset($stylesheet['src']) && is_string($stylesheet['src']) ? $stylesheet['src'] : '';
                $canonical = $this->canonicalize_scan_stylesheet_source($src);
                if ($canonical !== '') {
                    $sources[$canonical] = true;
                }
            }
        }

        $by_context = $results['stylesheets_by_context'] ?? [];
        if (is_array($by_context)) {
            foreach ($by_context as $context_sources) {
                if (!is_array($context_sources)) {
                    continue;
                }
                foreach ($context_sources as $src) {
                    if (!is_string($src)) {
                        continue;
                    }
                    $canonical = $this->canonicalize_scan_stylesheet_source($src);
                    if ($canonical !== '') {
                        $sources[$canonical] = true;
                    }
                }
            }
        }

        return $sources;
    }

    /**
     * Canonicalize stylesheet source URL for scan-quality comparisons.
     *
     * @param string $src Stylesheet source
     * @return string
     */
    private function canonicalize_scan_stylesheet_source(string $src): string {
        $src = trim(html_entity_decode($src, ENT_QUOTES));
        if ($src === '') {
            return '';
        }

        if (strpos($src, '//') === 0) {
            $src = 'https:' . $src;
        } elseif (strpos($src, '/') === 0) {
            $src = home_url($src);
        }

        $normalized = $this->normalize_ucss_source_url($src);
        return (string) preg_replace('/[#?].*$/', '', $normalized);
    }

    /**
     * Fetch stylesheet content with cache.
     *
     * @param string $src Stylesheet URL
     * @param array<string,string|null> $cache Cached content by URL
     * @return string|null
     */
    private function fetch_stylesheet_content(string $src, array &$cache): ?string {
        if (!array_key_exists($src, $cache)) {
            $response = wp_remote_get($src, [
                'timeout' => 30,
                'user-agent' => 'ProRank CSS Optimizer/1.0',
            ]);

            if (is_wp_error($response)) {
                $cache[$src] = null;
            } else {
                $code = (int) wp_remote_retrieve_response_code($response);
                $content_type = (string) wp_remote_retrieve_header($response, 'content-type');
                $body = wp_remote_retrieve_body($response);

                if ($code !== 200) {
                    $cache[$src] = null;
                } elseif ($content_type !== '' && stripos($content_type, 'text/css') === false) {
                    $cache[$src] = null;
                } else {
                    $cache[$src] = is_string($body) ? $body : null;
                }
            }
        }

        $content = $cache[$src] ?? null;
        return is_string($content) && $content !== '' ? $content : null;
    }

    /**
     * Remove generated UCSS files (and gzip variants) during rollback.
     *
     * @param array<int,string> $generated_files Generated CSS files
     * @return int Number of deleted files
     */
    private function rollback_generated_ucss_files(array $generated_files): int {
        $deleted = 0;
        $unique_files = array_values(array_unique(array_filter($generated_files, static function ($file): bool {
            return is_string($file) && $file !== '';
        })));

        foreach ($unique_files as $file) {
            if (\ProRank\SEO\Core\FilesystemHelper::delete_file($file)) {
                $deleted++;
            }
            if (\ProRank\SEO\Core\FilesystemHelper::delete_file($file . '.gz')) {
                $deleted++;
            }
        }

        return $deleted;
    }

    /**
     * Final runtime safety check against the live front page.
     *
     * @param array<string,array<string,mixed>> $generated_file_map Source-to-generated file map
     * @param array<string,string|null> $original_css_cache Original CSS cache
     * @param string|null $front_page_html Front page HTML
     * @param string $viewport Viewport label (desktop/mobile)
     * @return array{safe:bool,reason:string,source:string,viewport:string}
     */
    private function validate_front_page_ucss_runtime_safety(
        array $generated_file_map,
        array &$original_css_cache,
        ?string $front_page_html,
        string $viewport = 'desktop'
    ): array {
        if (!is_string($front_page_html) || trim($front_page_html) === '') {
            $allow_skip = (bool) apply_filters('prorank_ucss_allow_guard_skip_when_html_missing', false, $viewport);
            if ($allow_skip) {
                return ['safe' => true, 'reason' => 'front_page_unavailable', 'source' => '', 'viewport' => $viewport];
            }
            return ['safe' => false, 'reason' => 'front_page_unavailable', 'source' => '', 'viewport' => $viewport];
        }

        $raw_stylesheets = $this->extract_stylesheet_hrefs_from_html($front_page_html);
        if (empty($raw_stylesheets)) {
            $allow_skip = (bool) apply_filters('prorank_ucss_allow_guard_skip_when_no_stylesheets', false, $viewport);
            if ($allow_skip) {
                return ['safe' => true, 'reason' => 'front_page_no_stylesheets', 'source' => '', 'viewport' => $viewport];
            }
            return ['safe' => false, 'reason' => 'front_page_no_stylesheets', 'source' => '', 'viewport' => $viewport];
        }

        $stylesheets = [];
        $had_generated_ucss_stylesheet = false;
        foreach ($raw_stylesheets as $src) {
            if (!is_string($src) || $src === '') {
                continue;
            }
            if ($this->is_generated_ucss_stylesheet($src)) {
                $had_generated_ucss_stylesheet = true;
                continue;
            }
            $stylesheets[] = $src;
        }

        if (empty($stylesheets) && $had_generated_ucss_stylesheet && !empty($generated_file_map)) {
            // Stale page-cache HTML can still reference previously generated UCSS paths.
            // Skip hard-fail to avoid false negatives; apply() purges HTML cache afterward.
            return ['safe' => true, 'reason' => 'front_page_generated_ucss_only', 'source' => '', 'viewport' => $viewport];
        }

        $validated_stylesheets = 0;
        foreach ($stylesheets as $src) {
            if (!is_string($src) || $src === '') {
                continue;
            }

            if (strpos($src, '//') === 0) {
                $src = 'https:' . $src;
            } elseif (strpos($src, '/') === 0) {
                $src = home_url($src);
            }

            $entry = $this->resolve_generated_ucss_entry($generated_file_map, $src);
            if (!is_array($entry)) {
                continue;
            }

            $candidate_file = '';
            $entry_contexts = (!empty($entry['contexts']) && is_array($entry['contexts']))
                ? $entry['contexts']
                : [];
            if (!empty($entry_contexts)) {
                foreach ($this->get_ucss_context_lookup_order('front_page') as $runtime_context) {
                    if (!empty($entry_contexts[$runtime_context]) && is_string($entry_contexts[$runtime_context])) {
                        $candidate_file = (string) $entry_contexts[$runtime_context];
                        break;
                    }
                }
            }
            if ($candidate_file === '' && !empty($entry['global'])) {
                $candidate_file = (string) $entry['global'];
            }

            if ($candidate_file === '' || !file_exists($candidate_file)) {
                continue;
            }

            $original_css = $this->fetch_stylesheet_content($src, $original_css_cache);
            if (!is_string($original_css) || $original_css === '') {
                continue;
            }

            $optimized_css = file_get_contents($candidate_file);
            if (!is_string($optimized_css) || $optimized_css === '') {
                return ['safe' => false, 'reason' => 'optimized_css_missing', 'source' => $src, 'viewport' => $viewport];
            }

            $validated_stylesheets++;
            $safety = $this->evaluate_ucss_safety($original_css, $optimized_css, $front_page_html);
            if (!$safety['safe']) {
                return [
                    'safe' => false,
                    'reason' => (string) ($safety['reason'] ?? 'runtime_guard_failed'),
                    'source' => $src,
                    'viewport' => $viewport,
                ];
            }
        }

        if ($validated_stylesheets === 0) {
            if ($had_generated_ucss_stylesheet && !empty($generated_file_map)) {
                // Runtime page HTML and current generated map are out of sync (cache lag).
                // Treat as safe partial pass; cache purge will reconcile on next render.
                return ['safe' => true, 'reason' => 'runtime_cache_mismatch', 'source' => '', 'viewport' => $viewport];
            }

            if (!empty($generated_file_map)) {
                // No front-page stylesheet matched the current generated map. This can happen
                // on hash rotations or when only context-specific files were generated for
                // non-front-page templates. Hard-failing here blocks safe partial rollouts.
                $allow_partial = (bool) apply_filters(
                    'prorank_ucss_allow_partial_when_no_runtime_candidates',
                    true,
                    $viewport,
                    $stylesheets,
                    $generated_file_map
                );
                if ($allow_partial) {
                    return ['safe' => true, 'reason' => 'no_runtime_candidates_partial', 'source' => '', 'viewport' => $viewport];
                }
            }

            $allow_skip = (bool) apply_filters('prorank_ucss_allow_guard_skip_when_no_candidates', false, $viewport);
            if ($allow_skip) {
                return ['safe' => true, 'reason' => 'no_runtime_candidates', 'source' => '', 'viewport' => $viewport];
            }
            return ['safe' => false, 'reason' => 'no_runtime_candidates', 'source' => '', 'viewport' => $viewport];
        }

        return ['safe' => true, 'reason' => 'ok', 'source' => '', 'viewport' => $viewport];
    }

    /**
     * Register generated UCSS file in the source map using raw + normalized keys.
     *
     * Combined CSS filenames rotate by hash, so storing both raw and normalized keys
     * keeps runtime guard and frontend lookup stable across cache rotations.
     *
     * @param array<string,array<string,mixed>> $generated_file_map Source map
     * @param string $src Stylesheet source URL
     * @param string $context Context key (empty for global file)
     * @param string $file Generated CSS file path
     * @return void
     */
    private function register_generated_ucss_file_entry(array &$generated_file_map, string $src, string $context, string $file): void {
        $keys = [];

        $raw_key = (string) preg_replace('/[#?].*$/', '', $src);
        if ($raw_key !== '') {
            $keys[$raw_key] = true;
        }

        $normalized_src = $this->normalize_ucss_source_url($src);
        $normalized_key = (string) preg_replace('/[#?].*$/', '', $normalized_src);
        if ($normalized_key !== '') {
            $keys[$normalized_key] = true;
        }

        foreach (array_keys($keys) as $key) {
            if (!isset($generated_file_map[$key]) || !is_array($generated_file_map[$key])) {
                $generated_file_map[$key] = [];
            }

            if ($context === '') {
                $generated_file_map[$key]['global'] = $file;
                continue;
            }

            if (!isset($generated_file_map[$key]['contexts']) || !is_array($generated_file_map[$key]['contexts'])) {
                $generated_file_map[$key]['contexts'] = [];
            }
            $generated_file_map[$key]['contexts'][$context] = $file;
        }
    }

    /**
     * Resolve generated UCSS map entry for a runtime stylesheet URL.
     *
     * Falls back to normalized-hash alias filenames when the in-memory map misses
     * due to cache hash rotation between scan/apply/runtime.
     *
     * @param array<string,array<string,mixed>> $generated_file_map Source map
     * @param string $src Runtime stylesheet URL
     * @return array<string,mixed>|null
     */
    private function resolve_generated_ucss_entry(array $generated_file_map, string $src): ?array {
        $candidates = [];

        $raw_key = (string) preg_replace('/[#?].*$/', '', $src);
        if ($raw_key !== '') {
            $candidates[] = $raw_key;
        }

        $normalized_key = (string) preg_replace('/[#?].*$/', '', $this->normalize_ucss_source_url($src));
        if ($normalized_key !== '' && $normalized_key !== $raw_key) {
            $candidates[] = $normalized_key;
        }

        foreach ($candidates as $key) {
            $entry = $generated_file_map[$key] ?? null;
            if (is_array($entry)) {
                return $entry;
            }
        }

        if ($normalized_key === '') {
            return null;
        }

        $cache_dir = WP_CONTENT_DIR . '/cache/prorank-seo/css';
        $entry = [];

        $global_file = $cache_dir . '/' . md5($normalized_key) . '.css';
        if (file_exists($global_file)) {
            $entry['global'] = $global_file;
        }

        $context_files = [];
        foreach ($this->get_ucss_context_lookup_order('front_page') as $context) {
            $context_file = $cache_dir . '/' . md5($normalized_key . '|' . $context) . '.css';
            if (file_exists($context_file)) {
                $context_files[$context] = $context_file;
            }
        }
        if (!empty($context_files)) {
            $entry['contexts'] = $context_files;
        }

        return !empty($entry) ? $entry : null;
    }

    /**
     * Build UCSS context lookup order for equivalent templates.
     *
     * @param string $context Runtime context key
     * @return array<int,string>
     */
    private function get_ucss_context_lookup_order(string $context): array {
        $order = [$context];

        if ($context === 'front_page') {
            $order[] = 'single_page';
            $order[] = 'home';
        } elseif ($context === 'single_page') {
            $order[] = 'front_page';
            $order[] = 'home';
        } elseif ($context === 'home') {
            $order[] = 'front_page';
            $order[] = 'single_page';
        }

        if ($context !== 'default') {
            $order[] = 'default';
        }

        $order = array_values(array_filter(array_unique($order), static function ($value): bool {
            return is_string($value) && $value !== '';
        }));

        return !empty($order) ? $order : ['default'];
    }

    /**
     * Clear unused CSS optimizations
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function clear_unused_css_optimizations(WP_REST_Request $request): WP_REST_Response {
        try {
            // Clear cache directory
            $upload_dir = wp_upload_dir();
            $cache_dirs = [
                WP_CONTENT_DIR . '/cache/prorank-seo/css',
                trailingslashit($upload_dir['basedir']) . 'prorank-cache/css',
            ];

            foreach ($cache_dirs as $cache_dir) {
                if (!is_dir($cache_dir)) {
                    continue;
                }

                $files = array_merge(
                    glob($cache_dir . '/[a-f0-9][a-f0-9]*.css') ?: [],
                    glob($cache_dir . '/[a-f0-9][a-f0-9]*.css.gz') ?: [],
                    glob($cache_dir . '/ucss-*.css') ?: [],
                    glob($cache_dir . '/ucss-*.css.gz') ?: []
                );

                foreach ($files as $file) {
                    \ProRank\SEO\Core\FilesystemHelper::delete_file($file);
                }
            }

            // Clear applied status
            delete_option('prorank_unused_css_applied');
            delete_option('prorank_unused_css_results');

            // Disable in settings
            $settings = get_option('prorank_asset_optimization_settings', []);
            $settings['unused_css_enabled'] = false;
            update_option('prorank_asset_optimization_settings', $settings);

            // Clear page cache
            if (function_exists('rocket_clean_domain')) {
                rocket_clean_domain();
            }
            wp_cache_flush();
            $purged_files = $this->purge_prorank_html_cache_for_current_host();

            return new WP_REST_Response([
                'success' => true,
                'message' => __('Optimizations cleared. Original CSS files restored.', 'prorank-seo'),
                'html_cache_purged_files' => $purged_files,
            ], 200);

        } catch (\Throwable $e) {
            return new WP_REST_Response([
                'success' => false,
                'message' => $e->getMessage(),
            ], 500);
        }
    }

    /**
     * Purge unused CSS from stylesheet
     *
     * @param string $css Original CSS
     * @param array $used_selectors Used selectors
     * @param array $safelist Safelist patterns
     * @return string Purged CSS
     */
    private function purge_unused_css(string $css, array $used_selectors, array $safelist): string {
        $used = $this->build_used_token_sets($used_selectors);
        $safelist = $this->normalize_safelist_patterns($safelist);

        // Remove comments
        $css = preg_replace('/\/\*.*?\*\//s', '', $css);

        $purged = '';

        // Keep @charset/@import statements (order-sensitive).
        $css = preg_replace_callback('/@(charset|import)[^;]*;/i', function($match) use (&$purged) {
            $purged .= $match[0] . "\n";
            return '';
        }, $css);

        // Keep @font-face blocks.
        $css = preg_replace_callback('/@font-face\\s*\\{[^}]*\\}/i', function($match) use (&$purged) {
            $purged .= $match[0] . "\n";
            return '';
        }, $css);

        // Keep @keyframes blocks (incl. vendor prefixes).
        $css = preg_replace_callback('/@(?:-webkit-|-moz-|-o-)?keyframes\\s+[^\\{]+\\{(?:[^{}]+|\\{[^{}]*\\})*\\}/i', function($match) use (&$purged) {
            $purged .= $match[0] . "\n";
            return '';
        }, $css);

        // Process @media rules
        $css = preg_replace_callback('/@media[^{]*\{((?:[^{}]+|\{[^{}]*\})*)\}/s', function($match) use ($used, $safelist, &$purged) {
            $media_query = $match[0];
            $inner_css = $match[1];

            // Purge inner rules
            $inner_purged = $this->purge_rules($inner_css, $used, $safelist);

            if (!empty(trim($inner_purged))) {
                $purged .= str_replace($match[1], $inner_purged, $media_query) . "\n";
            }
            return '';
        }, $css);

        // Process @supports/@container/@layer similarly to @media.
        $css = preg_replace_callback('/@(supports|container|layer)[^{]*\{((?:[^{}]+|\\{[^{}]*\\})*)\\}/s', function($match) use ($used, $safelist, &$purged) {
            $at_rule = $match[0];
            $inner_css = $match[2] ?? '';

            $inner_purged = $this->purge_rules($inner_css, $used, $safelist);
            if (!empty(trim($inner_purged))) {
                $purged .= str_replace($inner_css, $inner_purged, $at_rule) . "\n";
            }
            return '';
        }, $css);

        // Process remaining rules
        $purged .= $this->purge_rules($css, $used, $safelist);

        // Minify
        $purged = preg_replace('/\s+/', ' ', $purged);
        $purged = preg_replace('/\s*([{}:;,])\s*/', '$1', $purged);
        $purged = str_replace(';}', '}', $purged);

        return trim($purged);
    }

    /**
     * Validate optimized CSS output before it can be applied on frontend.
     *
     * This prevents catastrophic over-purging that can unstyle public pages.
     *
     * @param string $original_css Original stylesheet
     * @param string $optimized_css Purged stylesheet candidate
     * @param string $context_html Optional HTML context for class coverage checks
     * @return array{safe: bool, reason: string}
     */
    private function evaluate_ucss_safety(string $original_css, string $optimized_css, string $context_html = ''): array {
        $original_size = strlen($original_css);
        $optimized_size = strlen($optimized_css);

        if ($optimized_size <= 0 || $original_size <= 0) {
            return ['safe' => false, 'reason' => 'empty_output'];
        }

        $min_retain_ratio = (float) apply_filters('prorank_ucss_min_retain_ratio', 0.24);
        $min_output_bytes = (int) apply_filters('prorank_ucss_min_output_bytes', 24 * 1024);
        $retain_ratio = $optimized_size / $original_size;

        // If output is both tiny and aggressively reduced, skip it.
        if ($retain_ratio < $min_retain_ratio && $optimized_size < $min_output_bytes) {
            return ['safe' => false, 'reason' => 'too_small'];
        }

        $original_rule_blocks = substr_count($original_css, '{');
        $optimized_rule_blocks = substr_count($optimized_css, '{');
        $min_rule_ratio = (float) apply_filters('prorank_ucss_min_rule_ratio', 0.12);

        if ($original_rule_blocks > 0 && (($optimized_rule_blocks / $original_rule_blocks) < $min_rule_ratio)) {
            return ['safe' => false, 'reason' => 'too_few_rules'];
        }

        // If a family exists in the original CSS, it must still exist after purge.
        $critical_families = [
            ['needle' => '.elementor', 'reason' => 'missing_elementor_family'],
            ['needle' => '.site-', 'reason' => 'missing_site_family'],
            ['needle' => '.menu', 'reason' => 'missing_menu_family'],
            ['needle' => '.wp-block-', 'reason' => 'missing_wp_block_family'],
        ];

        foreach ($critical_families as $family) {
            $needle = $family['needle'];
            if (strpos($original_css, $needle) !== false && strpos($optimized_css, $needle) === false) {
                return ['safe' => false, 'reason' => $family['reason']];
            }
        }

        // Optional coverage guard against the actual HTML context.
        if ($context_html !== '') {
            $classes = $this->extract_class_tokens_from_html($context_html);

            $min_sample_classes = (int) apply_filters('prorank_ucss_class_guard_min_classes', 20);
            if (count($classes) >= $min_sample_classes) {
                $original_coverage = $this->calculate_css_class_coverage($classes, $original_css);
                $optimized_coverage = $this->calculate_css_class_coverage($classes, $optimized_css);

                $min_original_ratio = (float) apply_filters('prorank_ucss_class_guard_min_original_ratio', 0.18);
                if ($original_coverage['ratio'] >= $min_original_ratio) {
                    $retain_ratio = (float) apply_filters('prorank_ucss_class_guard_retain_ratio', 0.80);
                    $absolute_min_ratio = (float) apply_filters('prorank_ucss_class_guard_min_ratio', 0.60);
                    $required_ratio = max($absolute_min_ratio, $original_coverage['ratio'] * $retain_ratio);

                    if ($optimized_coverage['ratio'] < $required_ratio) {
                        return ['safe' => false, 'reason' => 'low_class_coverage'];
                    }
                }

                // Extra protection for structural/theme classes that impact layout integrity.
                $structural_classes = $this->extract_structural_class_tokens($classes);
                $min_structural_classes = (int) apply_filters('prorank_ucss_structural_guard_min_classes', 8);

                if (count($structural_classes) >= $min_structural_classes) {
                    $original_structural_coverage = $this->calculate_css_class_coverage($structural_classes, $original_css);
                    $optimized_structural_coverage = $this->calculate_css_class_coverage($structural_classes, $optimized_css);

                    $min_structural_ratio = (float) apply_filters('prorank_ucss_structural_guard_min_ratio', 0.70);
                    $retain_structural_ratio = (float) apply_filters('prorank_ucss_structural_guard_retain_ratio', 0.90);
                    $required_structural_ratio = max(
                        $min_structural_ratio,
                        $original_structural_coverage['ratio'] * $retain_structural_ratio
                    );

                    if ($optimized_structural_coverage['ratio'] < $required_structural_ratio) {
                        return ['safe' => false, 'reason' => 'low_structural_class_coverage'];
                    }
                }
            }
        }

        return ['safe' => true, 'reason' => 'ok'];
    }

    /**
     * Build runtime selector set from live HTML for safer purge decisions.
     *
     * Adds class, id, and tag tokens in the same format produced by the scanner
     * (e.g. `.class`, `#id`, `tag`) so purge logic can keep layout-critical rules
     * when scan data is incomplete or stale.
     *
     * @param string $html HTML markup
     * @return array<int,string>
     */
    private function build_runtime_used_selectors_from_html(string $html): array {
        if ($html === '') {
            return [];
        }

        $selectors = [];

        foreach ($this->extract_class_tokens_from_html($html) as $class) {
            if (is_string($class) && $class !== '') {
                $selectors[] = '.' . $class;
            }
        }

        if (preg_match_all('/\sid=("|\')([^"\']+)("|\')/i', $html, $matches)) {
            foreach (($matches[2] ?? []) as $id) {
                $id = trim((string) $id);
                if ($id !== '') {
                    $selectors[] = '#' . $id;
                }
            }
        }

        if (preg_match_all('/<([a-z][a-z0-9-]*)\b/i', $html, $tag_matches)) {
            foreach (($tag_matches[1] ?? []) as $tag) {
                $tag = strtolower(trim((string) $tag));
                if ($tag !== '') {
                    $selectors[] = $tag;
                }
            }
        }

        return array_values(array_unique($selectors));
    }

    /**
     * Extract class tokens from HTML markup.
     *
     * @param string $html HTML markup
     * @return array<int,string>
     */
    private function extract_class_tokens_from_html(string $html): array {
        $classes = [];

        if (preg_match_all('/\sclass=("|\')([^"\']+)("|\')/i', $html, $matches)) {
            foreach (($matches[2] ?? []) as $class_list) {
                if (!is_string($class_list) || $class_list === '') {
                    continue;
                }

                $tokens = preg_split('/\s+/', trim($class_list)) ?: [];
                foreach ($tokens as $token) {
                    $token = trim((string) $token);
                    if ($token === '') {
                        continue;
                    }
                    $classes[$token] = true;
                }
            }
        }

        return array_keys($classes);
    }

    /**
     * Extract structural class tokens likely tied to layout/theme integrity.
     *
     * @param array<int,string> $classes Full class token set
     * @return array<int,string>
     */
    private function extract_structural_class_tokens(array $classes): array {
        $patterns = [
            '/^elementor/i',
            '/^site(?:-|_)/i',
            '/^(?:header|footer|content|entry|container|wrapper|layout)(?:-|_)/i',
            '/^(?:menu|nav|navbar|sub-menu|mega-menu)(?:-|_)?/i',
            '/^(?:mobile|desktop)(?:-|_)/i',
            '/^wp-block-/i',
        ];

        $structural = [];
        foreach ($classes as $class) {
            if (!is_string($class) || $class === '') {
                continue;
            }
            foreach ($patterns as $pattern) {
                if (preg_match($pattern, $class) === 1) {
                    $structural[$class] = true;
                    break;
                }
            }
        }

        return array_keys($structural);
    }

    /**
     * Calculate CSS class coverage ratio against extracted HTML classes.
     *
     * @param array<int,string> $classes HTML class tokens
     * @param string $css CSS stylesheet
     * @return array{matched:int,total:int,ratio:float}
     */
    private function calculate_css_class_coverage(array $classes, string $css): array {
        $total = 0;
        $matched = 0;

        foreach ($classes as $class) {
            if (!is_string($class) || $class === '') {
                continue;
            }

            // Skip classes that are usually JS-only runtime markers.
            if (strpos($class, 'js-') === 0 || strpos($class, 'is-') === 0 || strpos($class, 'has-') === 0) {
                continue;
            }

            $total++;
            $escaped = preg_quote($class, '/');
            $escaped = str_replace('\:', '(?:\\\\:|:)', $escaped);
            $pattern = '/\.' . $escaped . '(?![A-Za-z0-9_-])/';

            if (preg_match($pattern, $css) === 1) {
                $matched++;
            }
        }

        if ($total === 0) {
            return ['matched' => 0, 'total' => 0, 'ratio' => 1.0];
        }

        return [
            'matched' => $matched,
            'total' => $total,
            'ratio' => $matched / $total,
        ];
    }

    /**
     * Purge individual CSS rules
     *
     * @param string $css CSS rules
     * @param array $used_selectors Used selectors
     * @param array $safelist Safelist patterns
     * @return string Purged rules
     */
    private function purge_rules(string $css, array $used_selectors, array $safelist): string {
        $purged = '';

        preg_match_all('/([^{]+)\{([^}]+)\}/s', $css, $matches, PREG_SET_ORDER);

        foreach ($matches as $match) {
            $selector_group = trim($match[1]);
            $declarations = trim($match[2]);

            if (empty($selector_group) || empty($declarations)) {
                continue;
            }

            // Split selector groups and keep only the matching selectors.
            $selectors = array_filter(array_map('trim', explode(',', $selector_group)));
            if (empty($selectors)) {
                continue;
            }

            $kept = [];
            foreach ($selectors as $selector) {
                if ($this->should_keep_single_selector($selector, $used_selectors, $safelist)) {
                    $kept[] = $selector;
                }
            }

            if (!empty($kept)) {
                $purged .= implode(',', $kept) . '{' . $declarations . '}';
            }
        }

        return $purged;
    }

    /**
     * Build lookup sets for extracted selector tokens.
     *
     * @param array $used_selectors Used selectors from scan (classes/ids/tags/attrs)
     * @return array{classes: array<string,true>, ids: array<string,true>, tags: array<string,true>, attrs: array<string,true>}
     */
    private function build_used_token_sets(array $used_selectors): array {
        $used = [
            'classes' => [],
            'ids' => [],
            'tags' => [],
            'attrs' => [],
        ];

        foreach ($used_selectors as $selector) {
            if (!is_string($selector)) {
                continue;
            }

            $selector = trim($selector);
            if ($selector === '') {
                continue;
            }

            $first = $selector[0] ?? '';
            if ($first === '.') {
                $name = substr($selector, 1);
                if ($name !== '') {
                    $used['classes'][$name] = true;
                }
                continue;
            }

            if ($first === '#') {
                $name = substr($selector, 1);
                if ($name !== '') {
                    $used['ids'][$name] = true;
                }
                continue;
            }

            if ($first === '[' && substr($selector, -1) === ']') {
                $name = trim(substr($selector, 1, -1));
                if ($name !== '') {
                    $used['attrs'][$name] = true;
                }
                continue;
            }

            // Treat the remainder as a tag token (from HTML extraction).
            $tag = strtolower($selector);
            if (preg_match('/^[a-z][a-z0-9-]*$/', $tag)) {
                $used['tags'][$tag] = true;
            }
        }

        return $used;
    }

    /**
     * Normalize safelist patterns into token patterns (class/id/attribute names).
     *
     * @param array $patterns Patterns from UI/settings
     * @return array Normalized patterns
     */
    private function normalize_safelist_patterns(array $patterns): array {
        $normalized = [];

        foreach ($patterns as $pattern) {
            if (!is_string($pattern)) {
                continue;
            }

            $pattern = trim($pattern);
            if ($pattern === '') {
                continue;
            }

            // Convert selector-like patterns into token patterns.
            if ($pattern[0] === '.' || $pattern[0] === '#') {
                $pattern = substr($pattern, 1);
            } elseif ($pattern[0] === '[' && substr($pattern, -1) === ']') {
                $pattern = trim(substr($pattern, 1, -1));
            }

            $pattern = trim($pattern);
            if ($pattern === '') {
                continue;
            }

            $normalized[$pattern] = true;
        }

        return array_keys($normalized);
    }

    /**
     * Check whether a token matches the safelist.
     *
     * @param string $token Token to test (class/id/attr/tag name)
     * @param array $safelist Safelist token patterns (fnmatch)
     * @return bool
     */
    private function token_is_safelisted(string $token, array $safelist): bool {
        foreach ($safelist as $pattern) {
            if (!is_string($pattern) || $pattern === '') {
                continue;
            }
            if (fnmatch($pattern, $token)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check if a single (non-comma) selector should be kept.
     *
     * Uses a PurgeCSS-style heuristic: keep selector if all its tokens exist in
     * the extracted used tokens, with support for basic :is()/:where() groups.
     *
     * @param string $selector Single selector (no commas)
     * @param array $used_sets Used token sets from build_used_token_sets()
     * @param array $safelist Safelist token patterns
     * @return bool
     */
    private function should_keep_single_selector(string $selector, array $used_sets, array $safelist): bool {
        $selector = trim($selector);
        if ($selector === '') {
            return false;
        }

        // Always keep global/root selectors and common vendor pseudo selectors.
        if ($selector === ':root' || $selector === 'html' || $selector === 'body' || $selector === '*') {
            return true;
        }
        if (strpos($selector, '::-webkit-') !== false || strpos($selector, '::-moz-') !== false) {
            return true;
        }

        // Evaluate :is()/:where() groups (OR lists). Every group must have a match.
        if (preg_match_all('/:((?:is|where))\(([^()]*)\)/i', $selector, $groups, PREG_SET_ORDER)) {
            foreach ($groups as $group) {
                $content = $group[2] ?? '';
                $alternatives = array_filter(array_map('trim', explode(',', $content)));
                if (empty($alternatives)) {
                    continue;
                }

                $any = false;
                foreach ($alternatives as $alt) {
                    if ($this->should_keep_single_selector($alt, $used_sets, $safelist)) {
                        $any = true;
                        break;
                    }
                }

                if (!$any) {
                    return false;
                }
            }
        }

        // Remove :not(...) blocks (their contents are NOT required to exist).
        $normalized = preg_replace('/:not\([^)]*\)/i', '', $selector);

        // Remove :is(...) and :where(...) blocks from required token extraction (handled above).
        $normalized = preg_replace('/:((?:is|where))\([^)]*\)/i', '', $normalized);

        // Extract tokens.
        $classes = [];
        $ids = [];
        $attrs = [];
        $tags = [];

        if (preg_match_all('/\.([_a-zA-Z][\\w-]*)/', $normalized, $m)) {
            $classes = $m[1];
        }
        if (preg_match_all('/#([_a-zA-Z][\\w-]*)/', $normalized, $m)) {
            $ids = $m[1];
        }
        if (preg_match_all('/\\[([^\\s~|\\^\\$\\*=\\]]+)/', $normalized, $m)) {
            $attrs = $m[1];
        }

        $has_simple = !empty($classes) || !empty($ids) || !empty($attrs);
        if (!$has_simple) {
            // Only consider tag tokens when selector has no class/id/attr (e.g. "a", "h1").
            if (preg_match_all('/(?:^|[\\s>+~])([a-zA-Z][\\w-]*)/', $normalized, $m)) {
                $tags = array_map('strtolower', $m[1]);
            }
        }

        // Require all class tokens.
        foreach ($classes as $class) {
            if (isset($used_sets['classes'][$class])) {
                continue;
            }
            if ($this->token_is_safelisted($class, $safelist)) {
                continue;
            }
            return false;
        }

        // Require all ID tokens.
        foreach ($ids as $id) {
            if (isset($used_sets['ids'][$id])) {
                continue;
            }
            if ($this->token_is_safelisted($id, $safelist)) {
                continue;
            }
            return false;
        }

        // Require all attribute tokens.
        foreach ($attrs as $attr) {
            if (isset($used_sets['attrs'][$attr])) {
                continue;
            }
            if ($this->token_is_safelisted($attr, $safelist)) {
                continue;
            }
            return false;
        }

        // Require tag tokens only when we have no class/id/attr tokens. If we have no tag
        // tokens (e.g. empty selector after normalization), keep only if it’s a known safe case.
        if (!$has_simple) {
            if (empty($tags)) {
                return false;
            }

            // If the scan did not include tag extraction (e.g. cloud results), don’t drop
            // tag-only selectors.
            if (empty($used_sets['tags'])) {
                return true;
            }

            foreach ($tags as $tag) {
                if ($tag === '') {
                    continue;
                }
                if (isset($used_sets['tags'][$tag])) {
                    continue;
                }
                if ($this->token_is_safelisted($tag, $safelist)) {
                    continue;
                }
                return false;
            }
        }

        return true;
    }

    /**
     * Get safelist token patterns for CSS purging
     *
     * @return array
     */
    private function get_safelist_patterns(): array {
        return [
            // WordPress + block editor
            'wp-*',
            'wp-block-*',
            'screen-reader-*',
            'has-*',
            'is-*',
            'align*',
            'admin-bar',
            'logged-in',

            // Common JS/dynamic states
            'js-*',
            'no-js',
            'active',
            'open',
            'closed',
            'show',
            'hide',
            'hidden',
            'visible',
            'loading',
            'loaded',
            'disabled',
            'selected',
            'checked',
            'expanded',
            'collapsed',
            'current-*',
            'sticky*',
            'fixed*',
            'lazy*',
            'toggled',
            'animat*',
            'motion-*',

            // Structural classes (targeted, not broad wildcards)
            'site',
            'site-*',
            'content-area',
            'site-content',
            'site-main',
            'site-footer',
            'site-header',
            'entry-content',
            'entry-header',
            'entry-footer',
            'main-header-*',

            // Navigation/menu states
            'menu*',
            'nav*',
            'navbar*',
            'sub-menu*',
            'mega-menu*',
            'dropdown*',
            'toggle*',
            'hamburger*',
            'mobile-*',
            'desktop-*',
            'offcanvas*',
            'drawer*',

            // Popular plugins/themes
            'woocommerce*',
            'wc-*',
            'elementor*',
            'e-*',
            'edd-*',
            'wpcf7*',
            'wpforms*',
            'gform*',
            'rank-math*',
            'yoast*',

            // Common utility/UI libraries
            'swiper*',
            'slick*',
            'glide*',
            'splide*',
            'fancybox*',
            'lightbox*',
            'modal*',
            'popup*',

            // Attribute patterns
            'data-*',
            'aria-*',
            'role',
            'type',
        ];
    }

    /**
     * Check whether Performance Conflict Safe Mode is enabled.
     *
     * @return bool
     */
    private function is_performance_conflict_safe_mode_enabled(): bool {
        $settings = get_option('prorank_conflict_settings', []);
        if (is_array($settings) && array_key_exists('performance_safe_mode', $settings)) {
            return (bool) $settings['performance_safe_mode'];
        }

        return true;
    }

    /**
     * Persist Performance Conflict Safe Mode setting.
     *
     * @param bool $enabled Enabled state.
     * @return void
     */
    private function set_performance_conflict_safe_mode(bool $enabled): void {
        $settings = get_option('prorank_conflict_settings', []);
        if (!is_array($settings)) {
            $settings = [];
        }

        $settings['performance_safe_mode'] = $enabled;
        update_option('prorank_conflict_settings', $settings);
    }

    /**
     * Get active performance conflicts.
     *
     * @return array<int,string>
     */
    private function get_active_performance_conflicts(): array {
        $detector = new CompetitorDetector();
        return $detector->get_active_performance_competitors();
    }

    /**
     * Disable overlapping optimization toggles when safe mode is enabled.
     *
     * @return array<string,mixed>
     */
    private function enforce_performance_conflict_safe_mode(): array {
        $safe_mode_enabled = $this->is_performance_conflict_safe_mode_enabled();
        $conflicts = $this->get_active_performance_conflicts();

        $result = [
            'enabled' => $safe_mode_enabled,
            'conflicts' => $conflicts,
            'applied' => false,
            'disabled_settings' => [
                'cache' => [],
                'asset' => [],
                'image' => [],
            ],
            'message' => '',
        ];

        if (!$safe_mode_enabled) {
            $result['message'] = __('Performance Conflict Safe Mode is disabled.', 'prorank-seo');
            return $result;
        }

        if (empty($conflicts)) {
            $result['message'] = __('No active performance plugin conflicts detected.', 'prorank-seo');
            return $result;
        }

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

        foreach ([
            'cache_enabled',
            'preload_enabled',
            'link_preload_enabled',
            'prefetch_enabled',
            'prerender_enabled',
            'browser_cache_enabled',
            'js_defer',
            'js_delay',
            'css_minify',
            'css_combine',
            'css_async',
        ] as $key) {
            if (!empty($cache_settings[$key])) {
                $cache_settings[$key] = false;
                $result['disabled_settings']['cache'][] = $key;
            }
        }
        if (!empty($result['disabled_settings']['cache'])) {
            update_option('prorank_cache_settings', $cache_settings);
            $result['applied'] = true;
        }

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

        foreach ([
            'js_minify_enabled',
        ] as $key) {
            if (!empty($asset_settings[$key])) {
                $asset_settings[$key] = false;
                $result['disabled_settings']['asset'][] = $key;
            }
        }
        if (!empty($result['disabled_settings']['asset'])) {
            update_option('prorank_asset_optimization_settings', $asset_settings);
            $result['applied'] = true;
        }

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

        foreach ([
            'optimize_on_upload',
            'lazyload_images',
            'lazyload_iframes',
            'lazyload_videos',
        ] as $key) {
            if (!empty($image_settings[$key])) {
                $image_settings[$key] = false;
                $result['disabled_settings']['image'][] = $key;
            }
        }
        if (!empty($result['disabled_settings']['image'])) {
            update_option('prorank_image_optimization_settings', $image_settings);
            $result['applied'] = true;
        }

        if ($result['applied']) {
            $result['message'] = __(
                'Conflict Safe Mode disabled overlapping optimization features because other performance plugins are active.',
                'prorank-seo'
            );
        } else {
            $result['message'] = __(
                'Conflict Safe Mode is active. No overlapping optimization features needed changes.',
                'prorank-seo'
            );
        }

        return $result;
    }

    /**
     * Get font optimization stats
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function get_font_stats(WP_REST_Request $request): WP_REST_Response {
        $upload_dir = wp_upload_dir();
        $fonts_dir = $upload_dir['basedir'] . '/prorank-fonts';

        $stats = [
            'total_fonts' => 0,
            'total_size' => 0,
            'google_fonts_found' => 0,
        ];

        // Count local font files
        if (is_dir($fonts_dir)) {
            $iterator = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($fonts_dir, \RecursiveDirectoryIterator::SKIP_DOTS)
            );

            foreach ($iterator as $file) {
                if ($file->isFile() && $file->getFilename() !== 'index.php') {
                    $stats['total_fonts']++;
                    $stats['total_size'] += $file->getSize();
                }
            }
        }

        // Check for Google Fonts in registered styles
        global $wp_styles;
        if (!empty($wp_styles->registered)) {
            foreach ($wp_styles->registered as $style) {
                if (!empty($style->src) && is_string($style->src) && strpos($style->src, 'fonts.googleapis.com') !== false) {
                    $stats['google_fonts_found']++;
                }
            }
        }

        return new WP_REST_Response([
            'stats' => $stats,
        ], 200);
    }

    /**
     * Get htaccess cache serving status
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function get_htaccess_status(WP_REST_Request $request): WP_REST_Response {
        $plugin = \ProRank\SEO\Plugin::get_instance();
        $module = $plugin->modules()->get_module('modern_cache');

        if (!$module) {
            // Fallback: return basic htaccess info without the module
            $htaccess_path = prorank_home_file_path('.htaccess');
            $exists = file_exists($htaccess_path);
            $writable = $exists ? wp_is_writable($htaccess_path) : wp_is_writable(prorank_get_home_path());

            return new WP_REST_Response([
                'success' => true,
                'status' => [
                    'exists' => $exists,
                    'writable' => $writable,
                    'has_prorank_rules' => false,
                    'server' => prorank_get_server_var('SERVER_SOFTWARE') ?: 'unknown',
                ],
            ], 200);
        }

        $status = $module->get_htaccess_status();

        return new WP_REST_Response([
            'success' => true,
            'status' => $status,
        ], 200);
    }

    /**
     * Install htaccess cache serving rules
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function install_htaccess_rules(WP_REST_Request $request): WP_REST_Response {
        $plugin = \ProRank\SEO\Plugin::get_instance();
        $module = $plugin->modules()->get_module('modern_cache');

        if (!$module) {
            return new WP_REST_Response([
                'success' => false,
                'message' => __('Cache module not found', 'prorank-seo'),
            ], 404);
        }

        // Enable htaccess serving in settings
        $settings = get_option('prorank_cache_settings', []);
        $settings['htaccess_serving'] = true;
        update_option('prorank_cache_settings', $settings);

        $result = $module->install_htaccess_rules();

        return new WP_REST_Response([
            'success' => $result,
            'message' => $result
                ? __('htaccess rules installed successfully', 'prorank-seo')
                : __('Failed to install htaccess rules', 'prorank-seo'),
            'status' => $module->get_htaccess_status(),
        ], $result ? 200 : 500);
    }

    /**
     * Remove htaccess cache serving rules
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function remove_htaccess_rules(WP_REST_Request $request): WP_REST_Response {
        $plugin = \ProRank\SEO\Plugin::get_instance();
        $module = $plugin->modules()->get_module('modern_cache');

        if (!$module) {
            return new WP_REST_Response([
                'success' => false,
                'message' => __('Cache module not found', 'prorank-seo'),
            ], 404);
        }

        // Disable htaccess serving in settings
        $settings = get_option('prorank_cache_settings', []);
        $settings['htaccess_serving'] = false;
        update_option('prorank_cache_settings', $settings);

        $result = $module->remove_htaccess_rules();

        return new WP_REST_Response([
            'success' => $result,
            'message' => $result
                ? __('htaccess rules removed successfully', 'prorank-seo')
                : __('Failed to remove htaccess rules', 'prorank-seo'),
            'status' => $module->get_htaccess_status(),
        ], $result ? 200 : 500);
    }

    /**
     * Get nginx cache config snippet
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response
     */
    public function get_nginx_config(WP_REST_Request $request): WP_REST_Response {
        $cache_path = WP_CONTENT_DIR . '/cache/prorank-seo/page-cache';
        $cache_settings = get_option('prorank_cache_settings', []);
        if (!is_array($cache_settings)) {
            $cache_settings = [];
        }

        $to_nginx_expires = static function (int $seconds): string {
            if ($seconds <= 0) {
                return 'off';
            }
            if ($seconds % 31536000 === 0) {
                return (string) max(1, (int) ($seconds / 31536000)) . 'y';
            }
            if ($seconds % 2592000 === 0) {
                return (string) max(1, (int) ($seconds / 2592000)) . 'M';
            }
            if ($seconds % 86400 === 0) {
                return (string) max(1, (int) ($seconds / 86400)) . 'd';
            }
            if ($seconds % 3600 === 0) {
                return (string) max(1, (int) ($seconds / 3600)) . 'h';
            }
            if ($seconds % 60 === 0) {
                return (string) max(1, (int) ($seconds / 60)) . 'm';
            }
            return (string) $seconds . 's';
        };

        $images_ttl = max(0, (int) ($cache_settings['browser_cache_images'] ?? 31536000));
        $css_ttl = max(0, (int) ($cache_settings['browser_cache_css'] ?? 31536000));
        $js_ttl = max(0, (int) ($cache_settings['browser_cache_js'] ?? 31536000));
        $fonts_ttl = max(0, (int) ($cache_settings['browser_cache_fonts'] ?? 31536000));
        
        $config = "# ProRank SEO - Nginx Cache Configuration\n";
        $config .= "# Add this to your nginx server block\n\n";
        $config .= "location / {\n";
        $config .= "    # Try cached files first\n";
        $config .= "    set \$cache_file '" . $cache_path . "\$request_uri/index.html';\n";
        $config .= "    if (-f \$cache_file) {\n";
        $config .= "        rewrite ^(.*)\$ /wp-content/cache/prorank-seo/page-cache\$1/index.html break;\n";
        $config .= "    }\n";
        $config .= "    try_files \$uri \$uri/ /index.php?\$args;\n";
        $config .= "}\n";
        $config .= "\n";
        $config .= "# Browser cache (static assets) — includes AVIF/WebP/JXL\n";
        $config .= "location ~* \\.(?:jpg|jpeg|png|gif|svg|ico|webp|avif|jxl|bmp|tiff)$ {\n";
        $config .= "    expires " . $to_nginx_expires($images_ttl) . ";\n";
        $config .= "    add_header Cache-Control \"public, immutable\";\n";
        $config .= "    access_log off;\n";
        $config .= "}\n\n";
        $config .= "location ~* \\.css$ {\n";
        $config .= "    expires " . $to_nginx_expires($css_ttl) . ";\n";
        $config .= "    add_header Cache-Control \"public, immutable\";\n";
        $config .= "    access_log off;\n";
        $config .= "}\n\n";
        $config .= "location ~* \\.(?:js|mjs)$ {\n";
        $config .= "    expires " . $to_nginx_expires($js_ttl) . ";\n";
        $config .= "    add_header Cache-Control \"public, immutable\";\n";
        $config .= "    access_log off;\n";
        $config .= "}\n\n";
        $config .= "location ~* \\.(?:ttf|otf|woff|woff2|eot)$ {\n";
        $config .= "    expires " . $to_nginx_expires($fonts_ttl) . ";\n";
        $config .= "    add_header Cache-Control \"public, immutable\";\n";
        $config .= "    add_header Access-Control-Allow-Origin \"*\";\n";
        $config .= "    access_log off;\n";
        $config .= "}\n";

        return new WP_REST_Response([
            'success' => true,
            'config' => $config,
            'cache_path' => $cache_path,
        ], 200);
    }

    /**
     * Sync runtime performance module flags from cache settings.
     *
     * @param array<string,mixed> $settings Cache settings payload.
     * @return void
     */
    private function sync_performance_active_modules_from_cache_settings(array $settings): void {
        $this->sync_active_module_flags([
            'cache' => !empty($settings['cache_enabled']),
            'modern_cache' => !empty($settings['cache_enabled']),
            'browser_cache' => !empty($settings['browser_cache_enabled']),
            'cache_preload' => !empty($settings['preload_enabled']),
            'resource_hints' => !empty($settings['dns_prefetch_enabled']) || !empty($settings['font_preload_enabled']) || !empty($settings['link_preload_enabled']),
            'speculation_rules' => !empty($settings['prefetch_enabled']) || !empty($settings['prerender_enabled']),
            'css_opt' => !empty($settings['css_minify']) || !empty($settings['css_combine']) || !empty($settings['css_async']),
            'js_opt' => !empty($settings['js_defer']) || !empty($settings['js_delay']) || !empty($settings['tracker_delay_enabled']),
        ]);
    }

    /**
     * Sync runtime performance module flags from image settings.
     *
     * @param array<string,mixed> $settings Image optimization settings payload.
     * @return void
     */
    private function sync_performance_active_modules_from_image_settings(array $settings): void {
        $this->sync_active_module_flags([
            'image_optimization' => !empty($settings['webp_enabled']) || !empty($settings['avif_enabled']) || !empty($settings['jxl_enabled']) || !empty($settings['jpegli_enabled']) || !empty($settings['optimize_on_upload']) || !empty($settings['smart_compression']),
            'lazyload' => !empty($settings['lazyload_images']) || !empty($settings['lazyload_iframes']) || !empty($settings['lazyload_videos']) || !empty($settings['lazyload_enabled']),
        ]);
    }

    /**
     * Sync runtime performance module flags from asset settings.
     *
     * @param array<string,mixed> $settings Asset optimization settings payload.
     * @return void
     */
    private function sync_performance_active_modules_from_asset_settings(array $settings): void {
        $this->sync_active_module_flags([
            'js_minify' => !empty($settings['js_minify_enabled']),
            'html_minify' => !empty($settings['minify_html']) || !empty($settings['remove_html_comments']),
            'font_optimization' => !empty($settings['host_google_fonts_locally']) || !empty($settings['font_display_swap']),
            'inp_optimizer' => !empty($settings['inp_optimizer_enabled']),
            'lcp_beacon' => !empty($settings['lcp_optimizer_enabled']) || !empty($settings['lcp_fetchpriority_enabled']) || !empty($settings['lcp_preload_lcp']) || !empty($settings['lcp_optimize_first_image']) || !empty($settings['lcp_remove_above_fold_lazy']),
            'lcp_optimizer' => !empty($settings['lcp_optimizer_enabled']) || !empty($settings['lcp_fetchpriority_enabled']) || !empty($settings['lcp_preload_lcp']) || !empty($settings['lcp_optimize_first_image']) || !empty($settings['lcp_remove_above_fold_lazy']),
        ]);
    }

    /**
     * Persist active module flags in prorank_settings.
     *
     * @param array<string,bool> $flags Module slug => enabled.
     * @return void
     */
    private function sync_active_module_flags(array $flags): void {
        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']
            : [];

        foreach ($flags as $module_slug => $enabled) {
            $active_modules[$module_slug] = (bool) $enabled;
        }

        $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_flag($active_modules[$module_slug] ?? false)) {
                $performance_group_enabled = true;
                break;
            }
        }

        // Keep the parent performance group aligned with the merged child state,
        // not only the current subsection being saved.
        $active_modules['performance'] = $performance_group_enabled;
        $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 = [];
        }
        $group_states['performance'] = $performance_group_enabled;
        update_option('prorank_seo_module_groups', $group_states);
    }

    /**
     * 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 {
            $plugin = \ProRank\SEO\Plugin::get_instance();
            $module_manager = $plugin->modules();

            if ($module_manager) {
                $slugs = [];
                foreach ($module_manager->get_modules() as $slug => $module) {
                    if (
                        is_object($module)
                        && method_exists($module, 'get_parent_slug')
                        && $module->get_parent_slug() === 'performance'
                    ) {
                        $slugs[] = (string) $slug;
                    }
                }

                if (!empty($slugs)) {
                    return array_values(array_unique($slugs));
                }
            }
        } catch (\Throwable $e) {
            // Fall back to the known subset from the existing active modules.
        }

        $known = [
            'cache',
            'modern_cache',
            'browser_cache',
            'cache_preload',
            'resource_hints',
            'speculation_rules',
            'css_opt',
            'js_opt',
            'js_minify',
            'html_minify',
            'critical_css',
            'unused_css',
            'font_optimization',
            'font_subsetting',
            'image_optimization',
            'lazyload',
            'database_optimization',
            'performance_monitor',
            'inp_optimizer',
            'lcp_beacon',
            'lcp_optimizer',
            'view_transitions',
            'object_cache',
            'video_optimizer',
            'script_manager',
        ];

        return array_values(array_unique(array_merge($known, $fallback_slugs)));
    }

    /**
     * Normalize bool-like module flags.
     *
     * @param mixed $value
     * @return bool
     */
    private function is_truthy_flag($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));
            return !in_array($normalized, ['', '0', 'false', 'no', 'off', 'disabled'], true);
        }

        return !empty($value);
    }
}
