<?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',
        ];

        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',
        ];

        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',
                ],
                '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',
                ],
            ],

            // 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' => [],
            ],
            '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' => [
                    'vc_woocommerce-add-to-cart',
                    'wpb_composer_front_js',
                    'vc_accordion',
                    'vc_tabs',
                    'vc_tta_accordion',
                    'waypoints',
                ],
            ],
            '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',
                ],
            ],

            // 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-',
                ],
            ],
            'gdpr-cookie-compliance' => [
                'name' => 'GDPR Cookie Compliance',
                'condition' => 'gdpr-cookie-compliance/moove-gdpr.php',
                'delay_exclusions' => [
                    'moove_gdpr_frontend',
                    'moove-gdpr',
                ],
            ],
            'real-cookie-banner' => [
                'name' => 'Real Cookie Banner',
                'condition' => 'real-cookie-banner/index.php',
                'delay_exclusions' => [
                    'real-cookie-banner',
                    'rcb-banner',
                ],
            ],

            // 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',
                ],
            ],
            'ninja-forms' => [
                'name' => 'Ninja Forms',
                'condition' => 'ninja-forms/ninja-forms.php',
                'delay_exclusions' => [
                    'nf-front-end',
                    'nf-front-end-deps',
                ],
            ],

            // 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',
                ],
            ],
            'meta-slider' => [
                'name' => 'MetaSlider',
                'condition' => 'ml-slider/ml-slider.php',
                'delay_exclusions' => [
                    'metaslider',
                    'flexslider',
                    'nivo-slider',
                ],
            ],

            // Analytics & Tracking
            'google-site-kit' => [
                'name' => 'Google Site Kit',
                'condition' => 'google-site-kit/google-site-kit.php',
                'delay_exclusions' => [
                    'google_gtagjs',
                    'googlesitekit',
                ],
            ],
            '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',
                ],
            ],
            'pixel-your-site' => [
                'name' => 'PixelYourSite',
                'condition' => 'pixelyoursite/pixelyoursite.php',
                'delay_exclusions' => [
                    'pys-',
                    'pixelyoursite',
                ],
            ],
            '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_',
                ],
            ],
            '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/',
                ],
            ],
            '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;
    }
}
