<?php

/**
 * Quick Exclusions Database
 *
 * Pre-built exclusion rules for 60+ popular WordPress plugins.
 * Inspired by Perfmatters and WP Rocket exclusion databases.
 *
 * @package ProRank\SEO\Modules\Performance
 * @since   2.0.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Modules\Performance;

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


/**
 * QuickExclusions class
 *
 * Provides automatic script exclusion rules for popular plugins
 */


class QuickExclusions {

    /**
     * Check whether delaying jQuery is enabled in settings.
     *
     * @return bool
     */
    private static function is_delay_jquery_enabled(): bool {
        static $enabled = null;
        if ($enabled !== null) {
            return $enabled;
        }

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

        $value = $settings['js_delay_jquery'] ?? false;
        if (is_string($value)) {
            $enabled = in_array(strtolower($value), ['true', '1', 'yes', 'on'], true);
        } else {
            $enabled = (bool) $value;
        }

        // Safety guard: delaying jQuery before Elementor frontend boot can
        // trigger runtime errors on some stacks. Keep this disabled by default
        // when Elementor is active unless explicitly overridden.
        // If the advanced Elementor delay mode is enabled, treat it as an
        // explicit opt-in for a more aggressive delayed bootstrap strategy.
        if ($enabled && self::is_elementor_active()) {
            $allow_with_elementor_default = self::is_delay_elementor_modules_enabled();
            $allow_with_elementor = (bool) apply_filters(
                'prorank_delay_jquery_with_elementor',
                $allow_with_elementor_default
            );
            if (!$allow_with_elementor) {
                $enabled = false;
            }
        }

        return $enabled;
    }

    /**
     * Check whether Elementor plugin is active.
     *
     * @return bool
     */
    private static function is_elementor_active(): bool {
        $active_plugins = get_option('active_plugins', []);
        if (!is_array($active_plugins)) {
            return false;
        }

        return in_array('elementor/elementor.php', $active_plugins, true);
    }

    /**
     * Check whether the active (or parent) theme is Neve.
     *
     * @return bool
     */
    private static function is_neve_theme_active(): bool {
        static $is_neve = null;
        if ($is_neve !== null) {
            return $is_neve;
        }

        if (!function_exists('wp_get_theme')) {
            $is_neve = false;
            return $is_neve;
        }

        try {
            $theme = wp_get_theme();
            $template = strtolower((string) $theme->get_template());
            $stylesheet = strtolower((string) $theme->get_stylesheet());
            $name = strtolower((string) $theme->get('Name'));
            $parent = strtolower((string) $theme->get('Template'));

            $haystack = implode(' ', [$template, $stylesheet, $name, $parent]);
            $is_neve = strpos($haystack, 'neve') !== false;
        } catch (\Throwable $e) {
            $is_neve = false;
        }

        return $is_neve;
    }

    /**
     * Check whether any Bookly plugin/addon is active.
     *
     * @return bool
     */
    private static function is_bookly_active(): bool {
        $active_plugins = get_option('active_plugins', []);
        if (!is_array($active_plugins)) {
            return false;
        }

        foreach ($active_plugins as $plugin_file) {
            if (is_string($plugin_file) && strpos($plugin_file, 'bookly-') === 0) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check whether current frontend request has Bookly scripts enqueued.
     *
     * @return bool
     */
    private static function is_bookly_enqueued_on_request(): bool {
        static $has_bookly = null;
        if ($has_bookly !== null) {
            return $has_bookly;
        }

        $has_bookly = false;

        if (!function_exists('wp_scripts')) {
            return $has_bookly;
        }

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

        $queue = $wp_scripts->queue ?? [];
        if (!is_array($queue)) {
            return $has_bookly;
        }

        foreach ($queue as $handle) {
            if (!is_string($handle)) {
                continue;
            }

            $handle_l = strtolower($handle);
            if (strpos($handle_l, 'bookly') !== false) {
                $has_bookly = true;
                break;
            }

            if (isset($wp_scripts->registered[$handle])) {
                $src = (string) ($wp_scripts->registered[$handle]->src ?? '');
                if ($src !== '' && strpos(strtolower($src), 'bookly') !== false) {
                    $has_bookly = true;
                    break;
                }
            }
        }

        return $has_bookly;
    }

    /**
     * Check whether current request is likely a Bookly runtime context.
     *
     * Bookly can be active site-wide while its scripts are only needed on
     * pages that render Bookly shortcodes/flows.
     *
     * @return bool
     */
    private static function is_bookly_runtime_context(): bool {
        static $is_context = null;
        if ($is_context !== null) {
            return $is_context;
        }

        $is_context = false;

        if (is_admin()) {
            return $is_context;
        }

        $uri = strtolower(prorank_get_server_var('REQUEST_URI'));
        if ($uri !== '' && strpos($uri, 'bookly') !== false) {
            $is_context = true;
            return $is_context;
        }

        foreach (['bookly_fid', 'bookly_sid', 'bookly_service_id', 'bookly_staff_id', 'bookly_date'] as $param) {
            if (prorank_has_request_var($param)) {
                $is_context = true;
                return $is_context;
            }
        }

        if (function_exists('is_singular') && is_singular()) {
            $post = get_post();
            if ($post instanceof \WP_Post) {
                $content = (string) $post->post_content;
                if (
                    $content !== '' &&
                    (
                        preg_match('/\[bookly[^\]]*\]/i', $content) === 1 ||
                        strpos(strtolower($content), 'bookly') !== false
                    )
                ) {
                    $is_context = true;
                }
            }
        }

        return $is_context;
    }

    /**
     * Check whether aggressive Elementor module delay is enabled in settings.
     *
     * This is an advanced opt-in toggle for benchmarking/tuning. Default is off.
     *
     * @return bool
     */
    private static function is_delay_elementor_modules_enabled(): bool {
        static $enabled = null;
        if ($enabled !== null) {
            return $enabled;
        }

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

        $value = $settings['js_delay_elementor_modules'] ?? false;
        if (is_string($value)) {
            $enabled = in_array(strtolower($value), ['true', '1', 'yes', 'on'], true);
            return $enabled;
        }

        $enabled = (bool) $value;
        return $enabled;
    }

    /**
     * Detect whether the script is WordPress core jQuery / jQuery Migrate.
     *
     * @param string $handle
     * @param string $src
     * @return bool
     */
    private static function is_jquery_script(string $handle, string $src): bool {
        $handle = strtolower($handle);
        if (in_array($handle, ['jquery', 'jquery-core', 'jquery-migrate'], true)) {
            return true;
        }

        $src = strtolower($src);
        if ($src === '') {
            return false;
        }

        // Core jQuery paths.
        if (strpos($src, '/wp-includes/js/jquery/jquery') !== false) {
            return true;
        }

        return false;
    }

    /**
     * Detect whether the script is WordPress jQuery UI core.
     *
     * @param string $handle
     * @param string $src
     * @return bool
     */
    private static function is_jquery_ui_core_script(string $handle, string $src): bool {
        $handle_l = strtolower($handle);
        if ($handle_l === 'jquery-ui-core') {
            return true;
        }

        $src_l = strtolower($src);
        return $src_l !== '' && strpos($src_l, '/wp-includes/js/jquery/ui/core.min.js') !== false;
    }

    /**
     * Detect reCAPTCHA-related scripts.
     *
     * @param string $handle
     * @param string $src
     * @return bool
     */
    private static function is_recaptcha_script(string $handle, string $src): bool {
        $handle_l = strtolower($handle);
        $src_l = strtolower($src);

        if (
            strpos($handle_l, 'recaptcha') !== false ||
            strpos($handle_l, 'grecaptcha') !== false
        ) {
            return true;
        }

        if ($src_l === '') {
            return false;
        }

        return
            strpos($src_l, 'google.com/recaptcha') !== false ||
            strpos($src_l, 'gstatic.com/recaptcha') !== false ||
            strpos($src_l, '/recaptcha/api.js') !== false ||
            strpos($src_l, 'enterprise.js') !== false;
    }

    /**
     * Determine whether current request should keep reCAPTCHA synchronous.
     *
     * We only force sync on high-risk auth/commerce endpoints. On regular pages
     * we allow delay/defer so heavy reCAPTCHA bundles are removed from the
     * initial critical path.
     *
     * @return bool
     */
    private static function should_keep_recaptcha_sync(): bool {
        static $keep_sync = null;
        if ($keep_sync !== null) {
            return $keep_sync;
        }

        $keep_sync = false;
        $uri = strtolower(prorank_get_server_var('REQUEST_URI'));
        if ($uri === '') {
            return $keep_sync;
        }

        $high_risk_paths = [
            'wp-login.php',
            '/checkout',
            '/my-account',
            '/wc-api',
            '/cart',
            'action=register',
            'action=lostpassword',
        ];

        foreach ($high_risk_paths as $needle) {
            if (strpos($uri, $needle) !== false) {
                $keep_sync = true;
                break;
            }
        }

        return $keep_sync;
    }

    /**
     * Detect Elementor scripts that can be delayed in aggressive mode.
     *
     * @param string $handle
     * @param string $src
     * @return bool
     */
    private static function is_elementor_delay_override_target(string $handle, string $src): bool {
        $handle_l = strtolower($handle);
        $src_l = strtolower($src);

        $handle_patterns = [
            'elementor-frontend-modules',
            'elementor-frontend',
            'elementor-webpack-runtime',
            'elementor-pro-frontend',
            'elementor-pro-webpack',
            'swiper',
        ];

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

        $src_patterns = [
            '/elementor/assets/js/frontend-modules.min.js',
            '/elementor/assets/js/frontend.min.js',
            '/elementor/assets/js/webpack.runtime.min.js',
            '/elementor-pro/assets/js/frontend.min.js',
            '/elementor-pro/assets/js/webpack-pro.runtime.min.js',
            '/elementor/assets/lib/swiper/',
            'swiper.min.js',
        ];

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

        return false;
    }

    /**
     * Get all exclusion rules
     *
     * @return array
     */
    public static function get_all(): array {
        return [
            // E-commerce
            'woocommerce' => [
                'name' => 'WooCommerce',
                'condition' => 'woocommerce/woocommerce.php',
                'delay_exclusions' => [
                    'jquery.min.js',
                    'jquery.js',
                    'wc-cart',
                    'wc-cart-fragments',
                    'wc-checkout',
                    'wc-add-to-cart',
                    'wc-add-to-cart-variation',
                    'wc-single-product',
                    'wc-country-select',
                    'wc-address-i18n',
                    'selectWoo',
                    'jquery-blockui',
                    'woocommerce',
                    'js-cookie',
                    'flexslider',
                    'photoswipe',
                    'jquery.zoom',
                    'jquery.zoom.min.js',
                ],
                'defer_exclusions' => [
                    'wc-checkout',
                    'wc-cart',
                ],
            ],
            'easy-digital-downloads' => [
                'name' => 'Easy Digital Downloads',
                'condition' => 'easy-digital-downloads/easy-digital-downloads.php',
                'delay_exclusions' => [
                    'jquery.min.js',
                    'edd-ajax',
                    'edd-checkout-global',
                ],
            ],
            'surecart' => [
                'name' => 'SureCart',
                'condition' => 'surecart/surecart.php',
                'delay_exclusions' => [
                    'surecart',
                    'hooks.min.js',
                    'i18n.min.js',
                    'url.min.js',
                    'api-fetch.min.js',
                    'a11y.min.js',
                    'dom-ready.min.js',
                ],
                'defer_exclusions' => [
                    'hooks.min.js',
                    'i18n.min.js',
                ],
            ],

            // Page Builders
            'elementor' => [
                'name' => 'Elementor',
                'condition' => 'elementor/elementor.php',
                'delay_exclusions' => [
                    'elementor-frontend',
                    'elementor-frontend-modules',
                    'elementor-webpack-runtime',
                    'elementor-pro-frontend',
                    'elementor-waypoints',
                    'imagesloaded',
                    'swiper',
                    'elementor-dialog',
                    'share-link',
                    'e-gallery',
                ],
                // Keep delay exclusions for compatibility, but allow defer so
                // runtime files are non-blocking and outside the critical path.
                'defer_exclusions' => [],
            ],
            'elementor-pro' => [
                'name' => 'Elementor Pro',
                'condition' => 'elementor-pro/elementor-pro.php',
                'delay_exclusions' => [
                    'elementor-pro-frontend',
                    'elementor-pro-webpack',
                    'lottie',
                    'smartmenus',
                    'jquery.smartmenus',
                ],
                // Keep delay exclusions for compatibility, but allow defer so
                // runtime files are non-blocking and outside the critical path.
                'defer_exclusions' => [],
            ],
            'jet-menu' => [
                'name' => 'JetMenu',
                'condition' => 'jet-menu/jet-menu.php',
                'delay_exclusions' => [
                    'jquery.min.js',
                    'jquery-migrate.min.js',
                    'elementor-frontend',
                    'elementor-pro-frontend',
                    'jet-blog',
                    'jet-elements',
                    'jet-menu',
                    'elementorfrontendconfig',
                    'elementorprofrontendconfig',
                    'jetenginesettings',
                    'jetmenupublicsettings',
                ],
                'defer_exclusions' => [
                    'jet-menu',
                    'jetmenupublicsettings',
                ],
            ],
            'kadence-blocks' => [
                'name' => 'Kadence Blocks',
                'condition' => 'kadence-blocks/kadence-blocks.php',
                'delay_exclusions' => [
                    'kadence-blocks-tabs-js',
                    'kad-splide-js',
                    'kadence-splide-js',
                    'kadence-slide-init-js',
                    'kadence-blocks-splide-init-js',
                    'kadence-blocks-pro-splide-init-js',
                ],
            ],
            'generateblocks-pro' => [
                'name' => 'GenerateBlocks Pro',
                'condition' => 'generateblocks-pro/plugin.php',
                'delay_exclusions' => [
                    'generateblocks-pro',
                    'classic-menu.js',
                    'sticky-element.js',
                ],
            ],
            'atarim' => [
                'name' => 'Atarim',
                'condition' => 'atarim-visual-collaboration/atarim-visual-collaboration.php',
                'delay_exclusions' => [
                    'jquery.min.js',
                    'atarim-client-interface',
                    'jquery_wpf',
                    'upgrade_url',
                ],
                'defer_exclusions' => [
                    'atarim-client-interface',
                    'jquery_wpf',
                    'upgrade_url',
                ],
            ],
            'divi' => [
                'name' => 'Divi Theme/Builder',
                'condition' => fn() => defined('ET_BUILDER_VERSION'),
                'delay_exclusions' => [
                    'et-builder-modules-global-functions-script',
                    'divi-custom-script',
                    'et_pb_animation_bundle',
                    'et-builder-modules-script',
                    'easypiechart',
                    'salvattore',
                    'magnific-popup',
                    'waypoints',
                ],
            ],
            'wpbakery' => [
                'name' => 'WPBakery Page Builder',
                'condition' => 'js_composer/js_composer.php',
                'delay_exclusions' => [
                    // Main frontend bootstrap (js_composer_front.min.js)
                    'wpb_composer_front_js',
                    // Interactive modules that read DOM state on load
                    'vc_accordion',
                    'vc_tabs',
                    'vc_tta_accordion',
                    'vc_pageable',
                    'vc_carousel',
                    'vc_grid',
                    // WooCommerce add-to-cart wrapper element
                    'vc_woocommerce-add-to-cart',
                    // Layout/animation deps wired by WPBakery modules
                    'waypoints',
                    'isotope.pkgd',
                    // Lightbox used by vc_single_image/vc_gallery
                    'prettyphoto',
                    // URL fragments — match all variants of the bundled assets
                    '/plugins/js_composer/assets/',
                ],
                'defer_exclusions' => [
                    'wpb_composer_front_js',
                    '/plugins/js_composer/assets/',
                ],
            ],
            'bricks' => [
                'name' => 'Bricks Builder',
                'condition' => 'bricks/bricks.php',
                'delay_exclusions' => [
                    'bricks-frontend',
                    'bricks-isotope',
                    'bricks-swiper',
                ],
            ],
            'oxygen' => [
                'name' => 'Oxygen Builder',
                'condition' => 'oxygen/functions.php',
                'delay_exclusions' => [
                    'oxygen-frontend',
                    'oxy-aos',
                    'oxy-lottie',
                ],
            ],
            'beaver-builder' => [
                'name' => 'Beaver Builder',
                'condition' => 'bb-plugin/fl-builder.php',
                'delay_exclusions' => [
                    'fl-builder-layout',
                    'jquery-magnificpopup',
                    'jquery-fitvids',
                    'jquery-waypoints',
                ],
            ],
            'seedprod' => [
                'name' => 'SeedProd',
                // Matches free (coming-soon) and pro (seedprod-coming-soon-pro / -5).
                'condition' => static function (): bool {
                    $active = (array) get_option('active_plugins', []);
                    foreach ($active as $entry) {
                        $entry = (string) $entry;
                        if ($entry === 'coming-soon/coming-soon.php') {
                            return true;
                        }
                        if (strpos($entry, 'seedprod-coming-soon-pro') === 0) {
                            return true;
                        }
                    }
                    return defined('SEEDPROD_VERSION')
                        || defined('SEEDPROD_PRO_VERSION')
                        || defined('SEEDPROD_SLUG')
                        || defined('SEEDPROD_PLUGIN_VERSION')
                        || class_exists('SeedProd_Lite_Init')
                        || class_exists('SeedProd_Pro');
                },
                'delay_exclusions' => [
                    // SeedProd-prefixed runtime handles
                    'seedprod-landing-page',
                    'seedprod_pro_js',
                    'seedprod-fa-icons',
                    'seedprod-tsparticles-js',
                    'seedprod-masonry-js',
                    'seedprod-imagesloaded-js',
                    'seedprod-isotope-js',
                    'seedprod-iframeresizer',
                    // URL fragments for the SeedProd-shipped public assets only
                    '/plugins/coming-soon/public/js/',
                    '/plugins/seedprod-coming-soon-pro/public/js/',
                    '/plugins/seedprod-coming-soon-pro-5/public/js/',
                    'sp-scripts.min.js',
                    'tsparticles.min.js',
                    'masonry.pkgd.js',
                    'imagesloaded.pkgd.min.js',
                    'isotope.pkgd.js',
                    'tubular.js',
                    'lightbox.min.js',
                ],
                'defer_exclusions' => [
                    'seedprod-landing-page',
                    'seedprod-tsparticles-js',
                    'seedprod-masonry-js',
                    'seedprod-imagesloaded-js',
                    'seedprod-isotope-js',
                    '/plugins/coming-soon/public/js/',
                    '/plugins/seedprod-coming-soon-pro/public/js/',
                    '/plugins/seedprod-coming-soon-pro-5/public/js/',
                ],
            ],

            // Cookie & Consent Plugins
            'borlabs-cookie' => [
                'name' => 'Borlabs Cookie',
                'condition' => 'borlabs-cookie/borlabs-cookie.php',
                'delay_exclusions' => [
                    'borlabs-cookie',
                    'BorlabsCookie',
                ],
            ],
            'complianz' => [
                'name' => 'Complianz GDPR',
                'condition' => 'complianz-gdpr/complianz-gdpr.php',
                'delay_exclusions' => [
                    'cmplz-cookiebanner',
                    'complianz',
                ],
            ],
            'cookieyes' => [
                'name' => 'CookieYes',
                'condition' => 'cookie-law-info/cookie-law-info.php',
                'delay_exclusions' => [
                    'cookie-law-info',
                    'cookieyes',
                    'cli-',
                ],
            ],
            'cookie-notice' => [
                'name' => 'Cookie Notice',
                'condition' => 'cookie-notice/cookie-notice.php',
                'delay_exclusions' => [
                    'cookie-notice',
                    'cnargs',
                    'cdn.hu-manity.co',
                    'huoptions',
                ],
                'defer_exclusions' => [
                    'cookie-notice',
                    'cnargs',
                ],
            ],
            'gdpr-cookie-compliance' => [
                'name' => 'GDPR Cookie Compliance',
                'condition' => 'gdpr-cookie-compliance/moove-gdpr.php',
                'delay_exclusions' => [
                    'moove_gdpr_frontend',
                    'moove-gdpr',
                ],
            ],
            'lightweight-cookie-notice' => [
                'name' => 'Lightweight Cookie Notice',
                'condition' => 'lightweight-cookie-notice-free/init.php',
                'delay_exclusions' => [
                    'lightweight-cookie-notice-free/public/assets/js/production/general.js',
                    'daextlwcnf-general-js-after',
                    'daextlwcnf-general-js-extra',
                ],
                'defer_exclusions' => [
                    'lightweight-cookie-notice-free/public/assets/js/production/general.js',
                    'daextlwcnf-general-js-after',
                    'daextlwcnf-general-js-extra',
                ],
            ],
            'real-cookie-banner' => [
                'name' => 'Real Cookie Banner',
                'condition' => 'real-cookie-banner/index.php',
                'delay_exclusions' => [
                    'real-cookie-banner',
                    'rcb-banner',
                ],
            ],
            'real-cookie-banner-pro' => [
                'name' => 'Real Cookie Banner Pro',
                'condition' => 'real-cookie-banner-pro/index.php',
                'delay_exclusions' => [
                    'vendor-banner.pro.js',
                    'banner.pro.js',
                    'realcookiebanner',
                    'real-cookie-banner-pro-banner-js-before',
                ],
                'defer_exclusions' => [
                    'vendor-banner.pro.js',
                    'banner.pro.js',
                    'realcookiebanner',
                ],
            ],
            'thrive-leads' => [
                'name' => 'Thrive Leads',
                'condition' => 'thrive-leads/thrive-leads.php',
                'delay_exclusions' => [
                    'jquery.min.js',
                    'tve_frontend-js',
                ],
                'defer_exclusions' => [
                    'tve_frontend-js',
                ],
            ],
            'termageddon-usercentrics' => [
                'name' => 'Termageddon + Usercentrics',
                'condition' => 'termageddon-usercentrics/termageddon-usercentrics.php',
                'delay_exclusions' => [
                    'jquery.min.js',
                    'termageddon',
                    'usercentrics',
                    'uc_ui',
                ],
                'defer_exclusions' => [
                    'termageddon',
                    'usercentrics',
                    'uc_ui',
                ],
            ],

            // Forms
            'gravity-forms' => [
                'name' => 'Gravity Forms',
                'condition' => 'gravityforms/gravityforms.php',
                'delay_exclusions' => [
                    'jquery.min.js',
                    'gform_gravityforms',
                    'gform_json',
                    'gform_placeholder',
                    'gform_conditional_logic',
                    'gform_masked_input',
                    'plupload',
                    'gaddon_frontend',
                    'gforms_stripe_frontend',
                    'gform_datepicker',
                    'gform_gravityforms-js-extra',
                    'gform_gravityforms_theme_vendors',
                    'gform_gravityforms_theme',
                    'gform_stripe_vendor_theme_js',
                    'gform_stripe_theme_js',
                ],
                'defer_exclusions' => [
                    // jQuery must stay synchronous when GF is active —
                    // GF scripts depend on it and deferring jQuery
                    // breaks form initialisation.
                    'jquery-core',
                    'jquery-migrate',
                    // All GF scripts must stay synchronous. The theme and
                    // Stripe theme scripts (excluded from defer by the
                    // global 'stripe' safety pattern) depend on the core
                    // GF runtime, utils, and other scripts. Deferring any
                    // of them breaks the execution order because deferred
                    // head scripts run after synchronous footer scripts.
                    'gform_json',
                    'gform_gravityforms',
                    'gform_gravityforms_utils',
                    'gform_conditional_logic',
                    'gform_placeholder',
                    'gform_masked_input',
                    'gaddon_frontend',
                    'gform_datepicker',
                    'gform_gravityforms_theme_vendors',
                    'gform_gravityforms_theme',
                ],
            ],
            'wpforms' => [
                'name' => 'WPForms',
                'condition' => 'wpforms-lite/wpforms.php',
                'delay_exclusions' => [
                    'wpforms',
                    'wpforms-validation',
                    'wpforms-generic-utils',
                ],
            ],
            'wpforms-pro' => [
                'name' => 'WPForms Pro',
                'condition' => 'wpforms/wpforms.php',
                'delay_exclusions' => [
                    'wpforms',
                    'wpforms-validation',
                    'wpforms-generic-utils',
                    'wpforms-payment',
                    'wpforms-modern',
                    'wpforms-dropzone',
                    'wpforms-file-upload',
                    'wpforms-smart-phone-field-core',
                    'wpforms-maskedinput',
                    'wpforms-mailcheck',
                    'wpforms-punycode',
                    'wpforms-flatpickr',
                    'wpforms-jquery-timepicker',
                    'wpforms-stripe',
                    'wpforms-address-field',
                ],
                'defer_exclusions' => [
                    // jQuery must stay synchronous when WPForms is
                    // active — WPForms scripts depend on it and
                    // deferring jQuery breaks form initialisation.
                    'jquery-core',
                    'jquery-migrate',
                    // All WPForms scripts must stay synchronous.
                    // Deferring breaks multi-page form navigation,
                    // validation, and Stripe payment elements.
                    'wpforms',
                    'wpforms-validation',
                    'wpforms-generic-utils',
                    'wpforms-modern',
                    'wpforms-dropzone',
                    'wpforms-file-upload',
                    'wpforms-smart-phone-field-core',
                    'wpforms-maskedinput',
                    'wpforms-mailcheck',
                    'wpforms-punycode',
                    'wpforms-flatpickr',
                    'wpforms-jquery-timepicker',
                    'wpforms-stripe',
                    'wpforms-address-field',
                ],
            ],
            'contact-form-7' => [
                'name' => 'Contact Form 7',
                'condition' => 'contact-form-7/wp-contact-form-7.php',
                'delay_exclusions' => [
                    'contact-form-7',
                    'wpcf7-recaptcha',
                ],
            ],
            'formidable' => [
                'name' => 'Formidable Forms',
                'condition' => 'formidable/formidable.php',
                'delay_exclusions' => [
                    'formidable',
                    'frm-validation',
                ],
            ],
            'fluent-forms' => [
                'name' => 'Fluent Forms',
                'condition' => 'fluentform/fluentform.php',
                'delay_exclusions' => [
                    'fluent-form-submission',
                    'fluentform-advanced',
                ],
            ],
            'fluent-forms-pro' => [
                'name' => 'Fluent Forms Pro',
                'condition' => 'fluentformpro/fluentformpro.php',
                'delay_exclusions' => [
                    'jquery.min.js',
                    'fluentform',
                    'turnstile',
                ],
                'defer_exclusions' => [
                    'fluentform',
                    'turnstile',
                ],
            ],
            'ninja-forms' => [
                'name' => 'Ninja Forms',
                'condition' => 'ninja-forms/ninja-forms.php',
                'delay_exclusions' => [
                    'nf-front-end',
                    'nf-front-end-deps',
                ],
            ],
            'wp-armour' => [
                'name' => 'WP Armour',
                'condition' => 'honeypot/wp-armour.php',
                'delay_exclusions' => [
                    'wpa_field_info',
                ],
                'defer_exclusions' => [
                    'wpa_field_info',
                ],
            ],
            'ws-form' => [
                'name' => 'WS Form',
                'condition' => 'ws-form/ws-form.php',
                'delay_exclusions' => [
                    'jquery.min.js',
                    'jquery/ui',
                    'ws-form',
                    'wsf-wp-footer',
                    'quicktags-js-extra',
                ],
                'defer_exclusions' => [
                    'ws-form',
                    'wsf-wp-footer',
                ],
            ],
            'ws-form-pro' => [
                'name' => 'WS Form Pro',
                'condition' => 'ws-form-pro/ws-form.php',
                'delay_exclusions' => [
                    'jquery.min.js',
                    'jquery/ui',
                    'ws-form',
                    'wsf-wp-footer',
                    'quicktags-js-extra',
                ],
                'defer_exclusions' => [
                    'ws-form',
                    'wsf-wp-footer',
                ],
            ],
            'wp-recipe-maker' => [
                'name' => 'WP Recipe Maker',
                'condition' => 'wp-recipe-maker/wp-recipe-maker.php',
                'delay_exclusions' => [
                    'wprm-public-js',
                    'wprm-public-js-extra',
                    'wprmp-public-js',
                    'wprmp-public-js-extra',
                    'wprm-shared-js',
                    'wprmp-admin-js',
                    'wprm-admin-js',
                    'wprm-admin-modal-js',
                ],
                'defer_exclusions' => [
                    'wprm-public-js',
                    'wprm-public-js-extra',
                    'wprmp-public-js',
                    'wprmp-public-js-extra',
                    'wprm-shared-js',
                ],
            ],

            // Sliders
            'revslider' => [
                'name' => 'Revolution Slider',
                'condition' => 'revslider/revslider.php',
                'delay_exclusions' => [
                    'revslider',
                    'rev_slider',
                    'rs-plugin-settings',
                    'tp-tools',
                ],
            ],
            'smart-slider-3' => [
                'name' => 'Smart Slider 3',
                'condition' => 'smart-slider-3/smart-slider-3.php',
                'delay_exclusions' => [
                    'smartslider3',
                    'n2-ss',
                ],
            ],
            'smart-slider-3-pro' => [
                'name' => 'Smart Slider 3 Pro',
                'condition' => 'nextend-smart-slider3-pro/nextend-smart-slider3-pro.php',
                'delay_exclusions' => [
                    'nextend-smart-slider3-pro',
                    'n2-ss',
                ],
            ],
            'meta-slider' => [
                'name' => 'MetaSlider',
                'condition' => 'ml-slider/ml-slider.php',
                'delay_exclusions' => [
                    'metaslider',
                    'flexslider',
                    'nivo-slider',
                ],
            ],
            'modula-slider' => [
                'name' => 'Modula Slider',
                'condition' => 'modula-slider/modula-slider.php',
                'delay_exclusions' => [
                    'jquery.min.js',
                    'modula-slider',
                ],
            ],
            'presto-player' => [
                'name' => 'Presto Player',
                'condition' => 'presto-player/presto-player.php',
                'delay_exclusions' => [
                    'presto',
                    'presto-player',
                ],
            ],
            'sheknows-infuse' => [
                'name' => 'SHE Media Infuse',
                'condition' => 'sheknows-infuse/sheknows-infuse.php',
                'delay_exclusions' => [
                    'blogherads',
                ],
                'defer_exclusions' => [
                    'blogherads',
                ],
            ],
            'slick-engagement' => [
                'name' => 'Slickstream',
                'condition' => 'slick-engagement/slick-engagement.php',
                'delay_exclusions' => [
                    'slickstream',
                ],
                'defer_exclusions' => [
                    'slickstream',
                ],
            ],

            // Analytics & Tracking
            'google-site-kit' => [
                'name' => 'Google Site Kit',
                'condition' => 'google-site-kit/google-site-kit.php',
                'delay_exclusions' => [
                    'google_gtagjs',
                    'googlesitekit',
                    '/google-site-kit/dist/assets/js/',
                ],
                'defer_exclusions' => [
                    'google_gtagjs',
                    'googlesitekit',
                    '/google-site-kit/dist/assets/js/',
                ],
            ],
            'monsterinsights' => [
                'name' => 'MonsterInsights',
                'condition' => 'google-analytics-for-wordpress/googleanalytics.php',
                'delay_exclusions' => [
                    'monsterinsights',
                    'mi-',
                ],
            ],
            'ga-google-analytics' => [
                'name' => 'GA Google Analytics',
                'condition' => 'ga-google-analytics/ga-google-analytics.php',
                'delay_exclusions' => [
                    'ga-google-analytics',
                ],
            ],
            'plausible-analytics' => [
                'name' => 'Plausible Analytics',
                'condition' => 'plausible-analytics/plausible-analytics.php',
                'delay_exclusions' => [
                    'plausible',
                ],
            ],
            'pixel-your-site' => [
                'name' => 'PixelYourSite',
                'condition' => 'pixelyoursite/pixelyoursite.php',
                'delay_exclusions' => [
                    'pys-',
                    'pixelyoursite',
                ],
            ],
            'mediavine' => [
                'name' => 'Mediavine',
                'condition' => 'mediavine-control-panel/mediavine-control-panel.php',
                'delay_exclusions' => [
                    'mediavine',
                ],
            ],
            'monumetric-ads' => [
                'name' => 'Monumetric Ads',
                'condition' => 'monumetric-ads/monumetric-ads.php',
                'delay_exclusions' => [
                    '$mmt',
                    'monu.delivery',
                ],
            ],
            'shortpixel-adaptive-images' => [
                'name' => 'ShortPixel Adaptive Images',
                'condition' => 'shortpixel-adaptive-images/short-pixel-ai.php',
                'delay_exclusions' => [
                    'jquery.min.js',
                    'spai',
                ],
            ],
            'adthrive-ads' => [
                'name' => 'AdThrive',
                'condition' => 'adthrive-ads/adthrive-ads.php',
                'delay_exclusions' => [
                    'adthrive',
                    'raptive',
                    'admanagementconfig',
                ],
            ],
            'facebook-for-woocommerce' => [
                'name' => 'Facebook for WooCommerce',
                'condition' => 'facebook-for-woocommerce/facebook-for-woocommerce.php',
                'delay_exclusions' => [
                    'facebook-for-wc',
                    'fbq',
                ],
            ],

            // Live Chat
            'tawk-to' => [
                'name' => 'Tawk.to Live Chat',
                'condition' => 'tawkto-live-chat/tawkto.php',
                'delay_exclusions' => [
                    'tawk-',
                    'Tawk_API',
                ],
            ],
            'tidio' => [
                'name' => 'Tidio Chat',
                'condition' => 'tidio-live-chat/tidio-live-chat.php',
                'delay_exclusions' => [
                    'tidio-',
                    'tidioChatCode',
                ],
            ],
            'crisp' => [
                'name' => 'Crisp Live Chat',
                'condition' => 'crisp/crisp.php',
                'delay_exclusions' => [
                    'crisp',
                    '$crisp',
                ],
            ],
            'hubspot' => [
                'name' => 'HubSpot',
                'condition' => 'leadin/leadin.php',
                'delay_exclusions' => [
                    'hubspot',
                    'hs-script-loader',
                ],
            ],
            'zendesk-chat' => [
                'name' => 'Zendesk Chat',
                'condition' => 'zopim-live-chat/zopim.php',
                'delay_exclusions' => [
                    'zopim',
                    'zendesk',
                ],
            ],
            'intercom' => [
                'name' => 'Intercom',
                'condition' => fn() => defined('INTERCOM_PLUGIN_VERSION'),
                'delay_exclusions' => [
                    'intercom',
                    'intercomSettings',
                ],
            ],

            // Social & Sharing
            'addtoany' => [
                'name' => 'AddToAny Share',
                'condition' => 'add-to-any/add-to-any.php',
                'delay_exclusions' => [
                    'addtoany',
                ],
            ],
            'social-warfare' => [
                'name' => 'Social Warfare',
                'condition' => 'social-warfare/social-warfare.php',
                'delay_exclusions' => [
                    'social_warfare_script',
                    'swp_',
                ],
            ],
            'social-pug' => [
                'name' => 'Social Pug',
                'condition' => 'social-pug/index.php',
                'delay_exclusions' => [
                    'dpsp-frontend-js-pro-js',
                ],
            ],
            'grow-for-wp' => [
                'name' => 'Grow for WP',
                'condition' => 'grow-for-wp/grow-for-wp.php',
                'delay_exclusions' => [
                    'mv-script-wrapper-js',
                ],
            ],
            'easy-social-share' => [
                'name' => 'Easy Social Share Buttons',
                'condition' => 'easy-social-share-buttons3/easy-social-share-buttons3.php',
                'delay_exclusions' => [
                    'essb_core',
                    'essb_',
                ],
            ],

            // SEO
            'yoast-seo' => [
                'name' => 'Yoast SEO',
                'condition' => 'wordpress-seo/wp-seo.php',
                'delay_exclusions' => [],
                'defer_exclusions' => [],
            ],
            'rank-math' => [
                'name' => 'Rank Math',
                'condition' => 'seo-by-rank-math/rank-math.php',
                'delay_exclusions' => [],
            ],
            'all-in-one-seo' => [
                'name' => 'All in One SEO',
                'condition' => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
                'delay_exclusions' => [],
            ],

            // Popups & Opt-ins
            'optinmonster' => [
                'name' => 'OptinMonster',
                'condition' => 'optinmonster/optin-monster-wp-api.php',
                'delay_exclusions' => [
                    'optinmonster',
                    'omapi',
                ],
            ],
            'popup-maker' => [
                'name' => 'Popup Maker',
                'condition' => 'popup-maker/popup-maker.php',
                'delay_exclusions' => [
                    'popup-maker',
                    'pum-',
                ],
            ],
            'convert-pro' => [
                'name' => 'Convert Pro',
                'condition' => 'convertpro/convertpro.php',
                'delay_exclusions' => [
                    'convert-pro',
                    'cp-popup',
                ],
            ],
            'mailchimp-for-wp' => [
                'name' => 'Mailchimp for WordPress',
                'condition' => 'mailchimp-for-wp/mailchimp-for-wp.php',
                'delay_exclusions' => [
                    'mc4wp-forms',
                ],
            ],

            // Security
            'wordfence' => [
                'name' => 'Wordfence Security',
                'condition' => 'wordfence/wordfence.php',
                'delay_exclusions' => [
                    'wordfence',
                ],
            ],
            'sucuri' => [
                'name' => 'Sucuri Security',
                'condition' => 'sucuri-scanner/sucuri.php',
                'delay_exclusions' => [
                    'sucuri',
                ],
            ],

            // Media & Lightbox
            'fancybox' => [
                'name' => 'FancyBox for WordPress',
                'condition' => 'fancybox-for-wordpress/fancybox.php',
                'delay_exclusions' => [
                    'fancybox',
                ],
            ],
            'modula' => [
                'name' => 'Modula Gallery',
                'condition' => 'modula-best-grid-gallery/Starter.php',
                'delay_exclusions' => [
                    'modula',
                    'modula-fancybox',
                ],
            ],
            'envira-gallery' => [
                'name' => 'Envira Gallery',
                'condition' => 'envira-gallery-lite/envira-gallery-lite.php',
                'delay_exclusions' => [
                    'envira-gallery',
                ],
            ],

            // Membership & LMS
            'learndash' => [
                'name' => 'LearnDash LMS',
                'condition' => 'sfwd-lms/sfwd_lms.php',
                'delay_exclusions' => [
                    'learndash_',
                    'sfwd-',
                ],
            ],
            'memberpress' => [
                'name' => 'MemberPress',
                'condition' => 'memberpress/memberpress.php',
                'delay_exclusions' => [
                    'mepr-',
                    'memberpress',
                ],
            ],
            'restrict-content-pro' => [
                'name' => 'Restrict Content Pro',
                'condition' => 'restrict-content-pro/restrict-content-pro.php',
                'delay_exclusions' => [
                    'rcp-',
                ],
            ],

            // Booking & Scheduling
            'woocommerce-bookings' => [
                'name' => 'WooCommerce Bookings',
                'condition' => 'woocommerce-bookings/woocommerce-bookings.php',
                'delay_exclusions' => [
                    'wc-bookings',
                    'jquery-ui-datepicker',
                ],
            ],
            'booking-calendar' => [
                'name' => 'Booking Calendar',
                'condition' => 'booking/wpdev-booking.php',
                'delay_exclusions' => [
                    'wpbc_',
                    'booking',
                ],
            ],
            'amelia' => [
                'name' => 'Amelia Booking',
                'condition' => 'ameliabooking/ameliabooking.php',
                'delay_exclusions' => [
                    'amelia-',
                ],
            ],
            'bookly' => [
                'name' => 'Bookly',
                'condition' => fn() => self::is_bookly_active(),
                'delay_exclusions' => [
                    'bookly',
                    'jquery.hammer',
                    'intlTelInput',
                    'daterangepicker',
                    'select2',
                ],
                'defer_exclusions' => [
                    'bookly',
                ],
            ],

            // Theme runtime compatibility
            'neve-theme' => [
                'name' => 'Neve Theme Runtime',
                'condition' => fn() => self::is_neve_theme_active(),
                'delay_exclusions' => [
                    // Core theme runtime that controls header/menu/hero behavior.
                    // Delaying these can cause CLS and menu interaction regressions.
                    'neve-script-js',
                    'hfg_module-js',
                    '/themes/neve/assets/js/build/modern/frontend.js',
                    '/neve-pro-addon/includes/modules/header_footer_grid/assets/js/build/front-end.js',
                ],
                'defer_exclusions' => [
                    'neve-script-js',
                    'hfg_module-js',
                ],
            ],

            // WordPress Core & Common
            'wordpress-core' => [
                'name' => 'WordPress Core',
                'condition' => fn() => true, // Always active
                'delay_exclusions' => [
                    // Keep core runtime/bootstrap scripts out of interaction-delay.
                    // Delaying these can cause late init on widget-heavy pages
                    // (maps/forms/navigation) and visual holes until interaction.
                    'jquery.min.js',
                    'jquery.js',
                    'jquery-core',
                    'jquery-migrate',
                    // Keep layout/runtime core libraries out of interaction-delay.
                    // They can still run deferred, but delaying to user interaction
                    // often causes late layout passes.
                    'jquery-ui-core',
                    'imagesloaded',
                    'masonry',
                    'wp-polyfill',
                    'regenerator-runtime',
                    // WP utility/runtime scripts should remain non-delayed for
                    // broad theme/plugin compatibility. They can still be defer-
                    // optimized via defer controls.
                    'wp-hooks',
                    'wp-i18n',
                    'wp-api-fetch',
                    'wp-url',
                    'wp-dom-ready',
                    'wp-a11y',
                    'wp-date',
                    'wp-data',
                    'moment',
                ],
                'defer_exclusions' => [
                    // WP dist scripts have inline translation/data scripts that call
                    // wp.i18n.setLocaleData() synchronously. If we defer (not delay) them,
                    // the inline scripts fail with "wp is not defined".
                    // When js_delay is ON, these get delayed instead (with inline siblings),
                    // so defer exclusion is moot. When js_delay is OFF, we must not defer.
                    //
                    // NOTE: jQuery/jQuery-migrate are intentionally NOT excluded here.
                    // They can be safely deferred on most sites and are additionally
                    // protected by inline-companion safety checks in JsOptimizeModule.
                    'wp-hooks',
                    'wp-i18n',
                    'wp-api-fetch',
                    'wp-url',
                    'wp-dom-ready',
                    'wp-a11y',
                    'wp-date',
                    'wp-data',
                    'moment',
                ],
            ],

            // reCAPTCHA & Anti-Spam
            'google-recaptcha' => [
                'name' => 'Google reCAPTCHA',
                'condition' => fn() => true, // Check by script URL
                'delay_exclusions' => [
                    'recaptcha',
                    'google.com/recaptcha',
                    'gstatic.com/recaptcha',
                    'grecaptcha',
                ],
            ],
            'hcaptcha' => [
                'name' => 'hCaptcha',
                'condition' => 'hcaptcha-for-forms-and-more/hcaptcha.php',
                'delay_exclusions' => [
                    'hcaptcha',
                ],
            ],
            'akismet' => [
                'name' => 'Akismet Anti-Spam',
                'condition' => 'akismet/akismet.php',
                'delay_exclusions' => [
                    'akismet',
                ],
            ],

            // Performance plugins (when coexistence is intentionally allowed)
            'wp-rocket' => [
                'name' => 'WP Rocket',
                'condition' => 'wp-rocket/wp-rocket.php',
                'delay_exclusions' => [
                    'rocket-browser-checker',
                    'rocket-preload-links',
                    'rocket-lazyload-script',
                    'rocket-fonts',
                    'data-rocket',
                    '/wp-content/plugins/wp-rocket/',
                ],
            ],
            'flyingpress' => [
                'name' => 'FlyingPress',
                'condition' => 'flying-press/flying-press.php',
                'delay_exclusions' => [
                    'flyingpress',
                    'flying-press',
                    'flp_lazy',
                    '/wp-content/plugins/flying-press/',
                ],
            ],
            'perfmatters' => [
                'name' => 'Perfmatters',
                'condition' => 'perfmatters/perfmatters.php',
                'delay_exclusions' => [
                    'perfmatters',
                    'pmdelayedscript',
                    '/wp-content/plugins/perfmatters/',
                ],
            ],
            'wp-meteor' => [
                'name' => 'WP Meteor',
                'condition' => 'wp-meteor/wp-meteor.php',
                'delay_exclusions' => [
                    'wp-meteor',
                    'wpmeteor',
                    '/wp-content/plugins/wp-meteor/',
                ],
            ],
            'litespeed-cache' => [
                'name' => 'LiteSpeed Cache',
                'condition' => 'litespeed-cache/litespeed-cache.php',
                'delay_exclusions' => [
                    'litespeed',
                    'litespeed-cache',
                    'litespeed_lazyload',
                    '/wp-content/plugins/litespeed-cache/',
                ],
            ],

            // Multi-purpose
            'jetpack' => [
                'name' => 'Jetpack',
                'condition' => 'jetpack/jetpack.php',
                'delay_exclusions' => [
                    'jetpack',
                    'jp-',
                ],
            ],
        ];
    }

    /**
     * Get exclusions for active plugins
     *
     * @return array
     */
    public static function get_active_exclusions(): array {
        $all_exclusions = self::get_all();
        $active_plugins = get_option('active_plugins', []);
        $delay_exclusions = [];
        $defer_exclusions = [];

        foreach ($all_exclusions as $key => $plugin) {
            $is_active = false;

            // Check condition
            if (isset($plugin['condition'])) {
                if (is_callable($plugin['condition'])) {
                    $is_active = call_user_func($plugin['condition']);
                } elseif (is_string($plugin['condition'])) {
                    $is_active = in_array($plugin['condition'], $active_plugins, true);
                }
            }

            if ($is_active) {
                // Merge delay exclusions
                if (!empty($plugin['delay_exclusions'])) {
                    $delay_exclusions = array_merge($delay_exclusions, $plugin['delay_exclusions']);
                }

                // Merge defer exclusions
                if (!empty($plugin['defer_exclusions'])) {
                    $defer_exclusions = array_merge($defer_exclusions, $plugin['defer_exclusions']);
                }
            }
        }

        return [
            'delay' => array_unique($delay_exclusions),
            'defer' => array_unique($defer_exclusions),
        ];
    }

    /**
     * Check if a script should be excluded from delay
     *
     * @param string $handle Script handle
     * @param string $src    Script source URL
     * @return bool
     */
    public static function should_exclude_from_delay(string $handle, string $src): bool {
        static $exclusions = null;
        $bookly_runtime_context = self::is_bookly_active()
            && self::is_bookly_enqueued_on_request()
            && self::is_bookly_runtime_context();

        // reCAPTCHA: default to delay (cuts large unused JS on initial load),
        // but keep synchronous on high-risk auth/commerce endpoints.
        if (self::is_recaptcha_script($handle, $src)) {
            $keep_sync = self::should_keep_recaptcha_sync();
            $keep_sync = (bool) apply_filters(
                'prorank_recaptcha_keep_sync',
                $keep_sync,
                $handle,
                $src
            );

            if (!$keep_sync) {
                return false;
            }

            return true;
        }

        // Bookly compatibility: keep jQuery stack + jQuery UI core synchronous
        // when Bookly is present on the current request.
        if (
            $bookly_runtime_context &&
            (self::is_jquery_script($handle, $src) || self::is_jquery_ui_core_script($handle, $src))
        ) {
            return true;
        }

        // Allow delaying jQuery when explicitly enabled (advanced).
        if (self::is_delay_jquery_enabled() && self::is_jquery_script($handle, $src) && !$bookly_runtime_context) {
            return false;
        }

        // Allow delaying core Elementor frontend runtime when explicitly enabled.
        // High-risk scripts (e.g. SmartMenus) remain excluded by quick exclusions.
        if (self::is_delay_elementor_modules_enabled()) {
            if (self::is_elementor_delay_override_target($handle, $src)) {
                return false;
            }
        }

        if ($exclusions === null) {
            $exclusions = self::get_active_exclusions();
        }

        foreach ($exclusions['delay'] as $pattern) {
            // Check handle match
            if ($handle === $pattern || strpos($handle, $pattern) !== false) {
                return true;
            }

            // Check URL match
            if (!empty($src) && strpos($src, $pattern) !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check if a script should be excluded from defer
     *
     * @param string $handle Script handle
     * @param string $src    Script source URL
     * @return bool
     */
    public static function should_exclude_from_defer(string $handle, string $src): bool {
        static $exclusions = null;
        $bookly_runtime_context = self::is_bookly_active()
            && self::is_bookly_enqueued_on_request()
            && self::is_bookly_runtime_context();

        // Keep reCAPTCHA synchronous only on high-risk auth/commerce endpoints.
        if (self::is_recaptcha_script($handle, $src)) {
            $keep_sync = self::should_keep_recaptcha_sync();
            $keep_sync = (bool) apply_filters(
                'prorank_recaptcha_keep_sync',
                $keep_sync,
                $handle,
                $src
            );

            if ($keep_sync) {
                return true;
            }
        }

        // Bookly compatibility: keep jQuery synchronous only on requests where
        // Bookly scripts are actually enqueued (booking pages/flows), not
        // globally across the whole site.
        if (
            $bookly_runtime_context &&
            self::is_jquery_script($handle, $src) &&
            self::is_bookly_enqueued_on_request()
        ) {
            return true;
        }

        if (
            $bookly_runtime_context &&
            self::is_jquery_ui_core_script($handle, $src) &&
            self::is_bookly_enqueued_on_request()
        ) {
            return true;
        }

        if ($exclusions === null) {
            $exclusions = self::get_active_exclusions();
        }

        foreach ($exclusions['defer'] as $pattern) {
            if ($handle === $pattern || strpos($handle, $pattern) !== false) {
                return true;
            }

            if (!empty($src) && strpos($src, $pattern) !== false) {
                return true;
            }
        }

        // Allow jQuery/jQuery-migrate to use defer by default to reduce
        // render-blocking time. Active quick-exclusion rules checked above
        // (e.g., Bookly compatibility) can still force jQuery to stay sync.
        if (self::is_jquery_script($handle, $src)) {
            $allow_defer_jquery = (bool) apply_filters('prorank_defer_jquery', true, $handle, $src);
            if ($allow_defer_jquery) {
                return false;
            }
        }

        return false;
    }

    /**
     * Get list of available quick exclusions for settings UI
     *
     * @return array
     */
    public static function get_available_plugins(): array {
        $all = self::get_all();
        $active_plugins = get_option('active_plugins', []);
        $available = [];

        foreach ($all as $key => $plugin) {
            $is_active = false;

            if (isset($plugin['condition'])) {
                if (is_callable($plugin['condition'])) {
                    $is_active = call_user_func($plugin['condition']);
                } elseif (is_string($plugin['condition'])) {
                    $is_active = in_array($plugin['condition'], $active_plugins, true);
                }
            }

            $available[$key] = [
                'name' => $plugin['name'],
                'active' => $is_active,
                'exclusion_count' => count($plugin['delay_exclusions'] ?? []) + count($plugin['defer_exclusions'] ?? []),
            ];
        }

        return $available;
    }
}
