<?php

/**
 * Frontend diagnostics REST controller.
 *
 * @package ProRank\SEO\Core\RestApi
 */

declare(strict_types=1);

namespace ProRank\SEO\Core\RestApi;

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

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

/**
 * Diagnose frontend optimization state for a same-origin URL.
 */
class FrontendDiagnosticsController extends BaseController {

    /**
     * REST base.
     *
     * @var string
     */
    protected $rest_base = 'diagnose/frontend';

    /**
     * Register routes.
     *
     * @return void
     */
    public function register_routes(): void {
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base,
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [$this, 'diagnose'],
                    'permission_callback' => [$this, 'check_permission'],
                    'args'                => [
                        'url' => [
                            'required'          => false,
                            'type'              => 'string',
                            'sanitize_callback' => 'esc_url_raw',
                        ],
                        'safe_mode' => [
                            'required'          => false,
                            'type'              => 'string',
                            'sanitize_callback' => 'sanitize_key',
                        ],
                        'cache_bust' => [
                            'required' => false,
                            'type'     => 'boolean',
                            'default'  => false,
                        ],
                    ],
                ],
            ]
        );
    }

    /**
     * Diagnose a frontend URL.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response|WP_Error
     */
    public function diagnose(WP_REST_Request $request) {
        $url = $this->normalize_same_origin_url((string) ($request->get_param('url') ?: home_url('/')));
        if ($url instanceof WP_Error) {
            return $url;
        }

        $requested_safe_mode = sanitize_key((string) $request->get_param('safe_mode'));
        if (!in_array($requested_safe_mode, ['', 'js', 'css', 'all'], true)) {
            return new WP_Error(
                'invalid_safe_mode',
                __('safe_mode must be one of: js, css, all.', 'prorank-seo'),
                ['status' => 400]
            );
        }

        if ($requested_safe_mode !== '') {
            $url = add_query_arg('prorank-safe-mode', $requested_safe_mode, $url);
        }

        if ((bool) $request->get_param('cache_bust')) {
            $url = add_query_arg('_prorank_diagnose', (string) time(), $url);
        }

        $started = microtime(true);
        $response = wp_remote_get(
            $url,
            [
                'timeout'            => 15,
                'redirection'        => 3,
                'sslverify'          => true,
                'reject_unsafe_urls' => true,
                'headers'            => [
                    'User-Agent' => 'ProRank SEO Frontend Diagnostics; ' . home_url('/'),
                    'Accept'     => 'text/html,application/xhtml+xml',
                ],
            ]
        );
        $elapsed_ms = (int) round((microtime(true) - $started) * 1000);

        if (is_wp_error($response)) {
            return new WP_Error(
                'frontend_fetch_failed',
                $response->get_error_message(),
                ['status' => 502]
            );
        }

        $status = (int) wp_remote_retrieve_response_code($response);
        $headers = $this->normalize_headers(wp_remote_retrieve_headers($response));
        $html = (string) wp_remote_retrieve_body($response);
        $content_type = strtolower((string) ($headers['content-type'] ?? ''));

        $diagnostics = $this->analyze_html($html, $headers);
        $warnings = $diagnostics['warnings'];

        if ($status < 200 || $status >= 400) {
            $warnings[] = sprintf('Frontend fetch returned HTTP %d.', $status);
        }

        if ($content_type !== '' && strpos($content_type, 'text/html') === false) {
            $warnings[] = 'Fetched response is not HTML.';
        }

        $safe_mode_header = (string) ($headers['x-prorank-safe-mode'] ?? '');
        $preflight_header = (string) ($headers['x-prorank-preflight'] ?? '');
        if ($requested_safe_mode !== '' && strtolower($safe_mode_header) !== $requested_safe_mode) {
            $warnings[] = 'Safe-mode was requested but X-ProRank-Safe-Mode was not present in the response.';
        }

        return rest_ensure_response(
            [
                'url'                         => $url,
                'http_status'                 => $status,
                'response_time_ms'            => $elapsed_ms,
                'content_type'                => $headers['content-type'] ?? '',
                'safe_mode_requested'         => $requested_safe_mode,
                'safe_mode_header'            => $safe_mode_header,
                'preflight_header'            => $preflight_header,
                'cache'                       => [
                    'x_prorank_cache' => $headers['x-prorank-cache'] ?? '',
                    'cache_control'   => $headers['cache-control'] ?? '',
                    'vary'            => $headers['vary'] ?? '',
                ],
                'settings'                    => $this->get_runtime_settings_snapshot(),
                'menu_scripts_detected'       => $diagnostics['menu_scripts_detected'],
                'menu_scripts_deferred'       => $diagnostics['menu_scripts_deferred'],
                'menu_scripts_delayed'        => $diagnostics['menu_scripts_delayed'],
                'delayed_scripts'             => $diagnostics['delayed_scripts'],
                'inline_jquery_blocks_before_jquery_handle' => $diagnostics['inline_jquery_blocks_before_jquery_handle'],
                'inline_jquery_examples'      => $diagnostics['inline_jquery_examples'],
                'inline_dependency_violations' => $diagnostics['inline_dependency_violations'],
                'protected_styles_detected'    => $diagnostics['protected_styles_detected'],
                'critical_styles_stripped'      => $diagnostics['critical_styles_stripped'],
                'ucss_state'                  => $diagnostics['ucss_state'],
                'warnings'                    => array_values(array_unique(array_filter($warnings))),
            ]
        );
    }

    /**
     * Normalize and restrict diagnostics URL to the current site host.
     *
     * @param string $raw_url Raw URL or path.
     * @return string|WP_Error
     */
    private function normalize_same_origin_url(string $raw_url) {
        $raw_url = trim($raw_url);
        if ($raw_url === '') {
            $raw_url = home_url('/');
        }

        if (str_starts_with($raw_url, '/')) {
            $raw_url = home_url($raw_url);
        }

        $url = esc_url_raw($raw_url);
        $parts = wp_parse_url($url);
        $home_parts = wp_parse_url(home_url('/'));

        if (!is_array($parts) || !is_array($home_parts)) {
            return new WP_Error('invalid_url', __('A valid same-origin http(s) URL is required.', 'prorank-seo'), ['status' => 400]);
        }

        $scheme = strtolower((string) ($parts['scheme'] ?? ''));
        $host = strtolower((string) ($parts['host'] ?? ''));
        $home_host = strtolower((string) ($home_parts['host'] ?? ''));

        if (!in_array($scheme, ['http', 'https'], true) || $host === '' || $home_host === '') {
            return new WP_Error('invalid_url', __('A valid same-origin http(s) URL is required.', 'prorank-seo'), ['status' => 400]);
        }

        if ($this->strip_www($host) !== $this->strip_www($home_host)) {
            return new WP_Error('external_url_rejected', __('Frontend diagnostics only supports same-site URLs.', 'prorank-seo'), ['status' => 400]);
        }

        return $url;
    }

    /**
     * Analyze fetched HTML.
     *
     * @param string              $html    HTML response body.
     * @param array<string,mixed> $headers Normalized response headers.
     * @return array<string,mixed>
     */
    private function analyze_html(string $html, array $headers): array {
        $scripts = $this->extract_script_tags($html);
        $styles = $this->extract_link_tags($html);
        $menu_detected = [];
        $menu_deferred = [];
        $menu_delayed = [];
        $delayed_scripts = [];
        $jquery_index = null;
        $inline_jquery_before = 0;
        $inline_jquery_examples = [];
        $inline_dependency_violations = [];
        $warnings = [];
        $runtime_providers = $this->get_inline_dependency_runtime_providers();
        $provider_positions = [];
        $critical_style_state = $this->detect_critical_style_rows($styles);
        $protected_styles_detected = $critical_style_state['detected'];
        $critical_styles_stripped = $critical_style_state['unsafe'];

        foreach ($scripts as $index => $script) {
            $attrs = $script['attrs'];
            $src = (string) ($attrs['src'] ?? '');
            $id = (string) ($attrs['id'] ?? '');
            $type = strtolower((string) ($attrs['type'] ?? ''));
            $handle = $id !== '' ? preg_replace('/-js$/', '', $id) : '';
            $label = $this->detect_menu_script_label($id, $src, (string) $handle);
            $is_delayed = $type === 'text/prorank-delayed'
                || isset($attrs['data-prorank-delay'])
                || isset($attrs['data-prorank-delay-interaction']);
            $is_deferred = isset($attrs['defer'])
                || isset($attrs['async'])
                || in_array(strtolower((string) ($attrs['data-wp-strategy'] ?? '')), ['async', 'defer'], true);

            if ($jquery_index === null && ($id === 'jquery-core-js' || stripos($src, '/jquery.min.js') !== false)) {
                $jquery_index = $index;
            }

            if ($src !== '') {
                foreach ($runtime_providers as $provider => $config) {
                    if (isset($provider_positions[$provider])) {
                        continue;
                    }

                    if (!$this->script_matches_runtime_provider($id, $src, (string) $handle, (array) ($config['script_markers'] ?? []))) {
                        continue;
                    }

                    $provider_positions[$provider] = [
                        'index'    => $index,
                        'label'    => (string) ($config['label'] ?? $provider),
                        'id'       => $id,
                        'handle'   => (string) $handle,
                        'src'      => $src,
                        'deferred' => $is_deferred,
                        'delayed'  => $is_delayed,
                    ];
                }
            }

            if ($label !== '') {
                $menu_detected[$label] = [
                    'label'  => $label,
                    'id'     => $id,
                    'handle' => (string) $handle,
                    'src'    => $src,
                ];

                if ($is_deferred) {
                    $menu_deferred[] = [
                        'label'            => $label,
                        'id'               => $id,
                        'src'              => $src,
                        'async'            => isset($attrs['async']),
                        'defer'            => isset($attrs['defer']),
                        'data_wp_strategy' => $attrs['data-wp-strategy'] ?? '',
                    ];
                }

                if ($is_delayed) {
                    $menu_delayed[] = [
                        'label' => $label,
                        'id'    => $id,
                        'src'   => $src,
                        'type'  => $type,
                    ];
                }
            }

            if ($is_delayed) {
                $delayed_scripts[] = [
                    'id'   => $id,
                    'src'  => $src,
                    'type' => $type,
                ];
            }
        }

        if ($jquery_index !== null) {
            foreach ($scripts as $index => $script) {
                if ($index >= $jquery_index) {
                    break;
                }

                if (($script['attrs']['src'] ?? '') !== '') {
                    continue;
                }

                if (preg_match('/(?:jQuery|\\$)\\s*\\(|jQuery\\.|window\\.jQuery/', $script['body'])) {
                    $inline_jquery_before++;
                    if (count($inline_jquery_examples) < 3) {
                        $inline_jquery_examples[] = trim(substr(preg_replace('/\\s+/', ' ', $script['body']) ?: $script['body'], 0, 200));
                    }
                }
            }
        }

        foreach ($scripts as $index => $script) {
            $attrs = $script['attrs'];
            if (($attrs['src'] ?? '') !== '') {
                continue;
            }

            $type = strtolower((string) ($attrs['type'] ?? ''));
            $inline_is_delayed = $type === 'text/prorank-delayed'
                || isset($attrs['data-prorank-delay'])
                || isset($attrs['data-prorank-delay-interaction']);
            if ($inline_is_delayed) {
                continue;
            }

            $body = (string) ($script['body'] ?? '');
            if ($body === '') {
                continue;
            }

            foreach ($runtime_providers as $provider => $config) {
                if (!isset($provider_positions[$provider])) {
                    continue;
                }

                if (!$this->inline_script_matches_runtime_provider($body, (array) ($config['inline_patterns'] ?? []))) {
                    continue;
                }

                $provider_row = $provider_positions[$provider];
                $reason = '';
                if ($index < (int) $provider_row['index']) {
                    $reason = 'inline_before_provider';
                } elseif (!empty($provider_row['delayed'])) {
                    $reason = 'provider_delayed_before_inline';
                } elseif (!empty($provider_row['deferred'])) {
                    $reason = 'provider_deferred_before_inline';
                }

                if ($reason === '') {
                    continue;
                }

                $inline_dependency_violations[] = [
                    'provider' => (string) $provider,
                    'label'    => (string) ($provider_row['label'] ?? $provider),
                    'reason'   => $reason,
                    'id'       => (string) ($provider_row['id'] ?? ''),
                    'handle'   => (string) ($provider_row['handle'] ?? ''),
                    'src'      => (string) ($provider_row['src'] ?? ''),
                    'snippet'  => trim(substr(preg_replace('/\s+/', ' ', $body) ?: $body, 0, 200)),
                ];
            }
        }

        if (!empty($menu_deferred)) {
            $warnings[] = 'Menu/runtime scripts have async/defer attributes.';
        }

        if (!empty($menu_delayed)) {
            $warnings[] = 'Menu/runtime scripts are delayed by ProRank.';
        }

        if ($inline_jquery_before > 0) {
            $warnings[] = 'Inline jQuery-dependent scripts appear before jquery-core.';
        }

        if (!empty($inline_dependency_violations)) {
            $warnings[] = 'Inline runtime-dependent scripts would execute before their provider is ready.';
        }

        if (!empty($critical_styles_stripped)) {
            $warnings[] = 'Protected builder/theme stylesheets were made non-blocking or replaced.';
        }

        return [
            'menu_scripts_detected' => array_values($menu_detected),
            'menu_scripts_deferred' => $menu_deferred,
            'menu_scripts_delayed'  => $menu_delayed,
            'delayed_scripts'       => $delayed_scripts,
            'inline_jquery_blocks_before_jquery_handle' => $inline_jquery_before,
            'inline_jquery_examples' => $inline_jquery_examples,
            'inline_dependency_violations' => array_slice($inline_dependency_violations, 0, 10),
            'protected_styles_detected' => array_slice($protected_styles_detected, 0, 20),
            'critical_styles_stripped' => array_slice($critical_styles_stripped, 0, 10),
            'ucss_state'            => $this->detect_ucss_state($html, $headers),
            'warnings'              => $warnings,
        ];
    }

    /**
     * Runtime globals whose inline users must not outrun their provider script.
     *
     * @return array<string,array<string,mixed>>
     */
    private function get_inline_dependency_runtime_providers(): array {
        return [
            'elementorFrontend' => [
                'label'           => 'Elementor frontend',
                'inline_patterns' => [
                    '/\b(?:window\.)?elementorFrontend\b/i',
                    '/\b(?:window\.)?elementorProFrontend\b/i',
                ],
                'script_markers'  => [
                    'elementor-frontend-js',
                    'elementor-pro-frontend-js',
                    'elementor-frontend-modules',
                    '/elementor/assets/js/frontend.min.js',
                    '/elementor-pro/assets/js/frontend.min.js',
                ],
            ],
            'wp.hooks' => [
                'label'           => 'WordPress hooks',
                'inline_patterns' => ['/\bwp\.hooks\b/i'],
                'script_markers'  => ['wp-hooks-js', '/wp-includes/js/dist/hooks'],
            ],
            'wp.i18n' => [
                'label'           => 'WordPress i18n',
                'inline_patterns' => ['/\bwp\.i18n\b/i'],
                'script_markers'  => ['wp-i18n-js', '/wp-includes/js/dist/i18n'],
            ],
            'wp.apiFetch' => [
                'label'           => 'WordPress apiFetch',
                'inline_patterns' => ['/\bwp\.apiFetch\b/i'],
                'script_markers'  => ['wp-api-fetch-js', '/wp-includes/js/dist/api-fetch'],
            ],
            'wp.element' => [
                'label'           => 'WordPress element',
                'inline_patterns' => ['/\bwp\.element\b/i'],
                'script_markers'  => ['wp-element-js', '/wp-includes/js/dist/element'],
            ],
            'Swiper' => [
                'label'           => 'Swiper',
                'inline_patterns' => ['/\b(?:new\s+)?Swiper\b/i'],
                'script_markers'  => ['swiper-js', 'swiper.min.js', '/swiper/'],
            ],
            'imagesLoaded' => [
                'label'           => 'imagesLoaded',
                'inline_patterns' => ['/\bimagesLoaded\b/i'],
                'script_markers'  => ['imagesloaded-js', 'imagesloaded.min.js'],
            ],
        ];
    }

    /**
     * Whether a script tag is the provider for a tracked runtime global.
     *
     * @param string            $id      Script id.
     * @param string            $src     Script src.
     * @param string            $handle  Script handle.
     * @param array<int,string> $markers Provider markers.
     * @return bool
     */
    private function script_matches_runtime_provider(string $id, string $src, string $handle, array $markers): bool {
        $haystack = strtolower(trim($id . ' ' . $src . ' ' . $handle));
        foreach ($markers as $marker) {
            $marker = strtolower((string) $marker);
            if ($marker !== '' && strpos($haystack, $marker) !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * Whether inline code references a tracked runtime global.
     *
     * @param string            $body     Inline script body.
     * @param array<int,string> $patterns Regex patterns.
     * @return bool
     */
    private function inline_script_matches_runtime_provider(string $body, array $patterns): bool {
        foreach ($patterns as $pattern) {
            if (@preg_match((string) $pattern, $body) === 1) {
                return true;
            }
        }

        return false;
    }

    /**
     * Extract script tags and parsed attributes.
     *
     * @param string $html HTML.
     * @return array<int,array<string,mixed>>
     */
    private function extract_script_tags(string $html): array {
        if ($html === '' || stripos($html, '<script') === false) {
            return [];
        }

        preg_match_all('/<script\\b([^>]*)>(.*?)<\\/script>/is', $html, $matches, PREG_SET_ORDER);
        $scripts = [];

        foreach ($matches as $match) {
            $scripts[] = [
                'attrs' => $this->parse_html_attributes((string) ($match[1] ?? '')),
                'body'  => (string) ($match[2] ?? ''),
            ];
        }

        return $scripts;
    }

    /**
     * Extract link tags and parsed attributes.
     *
     * @param string $html HTML.
     * @return array<int,array<string,mixed>>
     */
    private function extract_link_tags(string $html): array {
        if ($html === '' || stripos($html, '<link') === false) {
            return [];
        }

        preg_match_all('/<link\b([^>]*)>/is', $html, $matches, PREG_SET_ORDER);
        $links = [];

        foreach ($matches as $match) {
            $links[] = [
                'attrs' => $this->parse_html_attributes((string) ($match[1] ?? '')),
            ];
        }

        return $links;
    }

    /**
     * Detect protected builder/theme styles that were made unsafe for render.
     *
     * @param array<int,array<string,mixed>> $styles Link tag rows.
     * @return array{detected:array<int,array<string,mixed>>,unsafe:array<int,array<string,mixed>>}
     */
    private function detect_critical_style_rows(array $styles): array {
        if (!class_exists(PerformanceCompatibilityRegistry::class)) {
            return [
                'detected' => [],
                'unsafe'   => [],
            ];
        }

        $handle_patterns = PerformanceCompatibilityRegistry::get_builder_blocking_style_handle_patterns();
        $href_patterns = PerformanceCompatibilityRegistry::get_builder_blocking_style_href_patterns();
        $detected = [];
        $unsafe = [];

        foreach ($styles as $style) {
            $attrs = is_array($style['attrs'] ?? null) ? $style['attrs'] : [];
            $id = (string) ($attrs['id'] ?? '');
            $href = (string) ($attrs['href'] ?? '');
            $rel = strtolower((string) ($attrs['rel'] ?? ''));
            $media = strtolower((string) ($attrs['media'] ?? ''));
            $handle = $id !== '' ? preg_replace('/-css$/', '', $id) : '';

            if (!$this->style_matches_patterns((string) $handle, $id, $href, $handle_patterns, $href_patterns)) {
                continue;
            }

            $row = [
                'label'  => (string) ($handle ?: $id ?: basename((string) wp_parse_url($href, PHP_URL_PATH))),
                'id'     => $id,
                'handle' => (string) $handle,
                'href'   => $href,
                'rel'    => $rel,
                'media'  => $media,
            ];
            $detected[] = $row;

            $reason = $this->get_unsafe_critical_style_reason($attrs, $href, $rel, $media);
            if ($reason !== '') {
                $row['reason'] = $reason;
                $unsafe[] = $row;
            }
        }

        return [
            'detected' => $detected,
            'unsafe'   => $unsafe,
        ];
    }

    /**
     * Whether a stylesheet matches protected builder/theme style patterns.
     *
     * @param string            $handle          Style handle.
     * @param string            $id              Style id.
     * @param string            $href            Style href.
     * @param array<int,string> $handle_patterns Handle patterns.
     * @param array<int,string> $href_patterns   Href patterns.
     * @return bool
     */
    private function style_matches_patterns(string $handle, string $id, string $href, array $handle_patterns, array $href_patterns): bool {
        $handle_haystack = strtolower(trim($handle . ' ' . $id));
        foreach ($handle_patterns as $pattern) {
            $pattern = strtolower((string) $pattern);
            if ($pattern !== '' && strpos($handle_haystack, $pattern) !== false) {
                return true;
            }
        }

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

        return false;
    }

    /**
     * Return the reason a protected stylesheet is unsafe, or empty when safe.
     *
     * @param array<string,mixed> $attrs Style attributes.
     * @param string              $href  Style href.
     * @param string              $rel   Lowercase rel.
     * @param string              $media Lowercase media.
     * @return string
     */
    private function get_unsafe_critical_style_reason(array $attrs, string $href, string $rel, string $media): string {
        if ($rel !== '' && $rel !== 'stylesheet') {
            return 'rel_' . sanitize_key($rel);
        }

        if ($media === 'print' && isset($attrs['onload'])) {
            return 'print_onload_async';
        }

        if (isset($attrs['data-prorank-async']) || isset($attrs['data-prorank-css-async'])) {
            return 'prorank_async_style';
        }

        if (stripos($href, '/ucss-cache/') !== false || stripos($href, '.used.css') !== false) {
            return 'used_css_replacement';
        }

        return '';
    }

    /**
     * Parse HTML attributes into a lowercase-key map.
     *
     * @param string $attribute_string Raw attribute string.
     * @return array<string,string|bool>
     */
    private function parse_html_attributes(string $attribute_string): array {
        $attributes = [];
        preg_match_all('~\\s([a-zA-Z0-9_:-]+)(?:\\s*=\\s*(?:"([^"]*)"|\\x27([^\\x27]*)\\x27|([^\\s"\\x27=<>`]+)))?~', $attribute_string, $matches, PREG_SET_ORDER);

        foreach ($matches as $match) {
            $name = strtolower((string) ($match[1] ?? ''));
            if ($name === '') {
                continue;
            }

            $value = true;
            if (array_key_exists(2, $match) && $match[2] !== '') {
                $value = html_entity_decode((string) $match[2], ENT_QUOTES);
            } elseif (array_key_exists(3, $match) && $match[3] !== '') {
                $value = html_entity_decode((string) $match[3], ENT_QUOTES);
            } elseif (array_key_exists(4, $match) && $match[4] !== '') {
                $value = html_entity_decode((string) $match[4], ENT_QUOTES);
            }

            $attributes[$name] = $value;
        }

        return $attributes;
    }

    /**
     * Detect menu/theme/builder runtime scripts.
     */
    private function detect_menu_script_label(string $id, string $src, string $handle): string {
        $haystack = strtolower($id . ' ' . $src . ' ' . $handle);
        $needles = [
            'jquery-core' => ['jquery-core-js', '/jquery.min.js'],
            'smartmenus' => ['smartmenus', 'jquery.smartmenus'],
            'elementor-frontend' => ['elementor-frontend-js', '/elementor/assets/js/frontend.min.js'],
            'elementor-frontend-modules' => ['elementor-frontend-modules', 'frontend-modules.min.js'],
            'elementor-pro-frontend' => ['elementor-pro-frontend', '/elementor-pro/assets/js/frontend.min.js'],
            'elementor-webpack-runtime' => ['elementor-webpack-runtime', 'webpack.runtime.min.js'],
            'elementor-pro-webpack' => ['elementor-pro-webpack', 'webpack-pro.runtime.min.js'],
            'neve-script' => ['neve-script', '/themes/neve/assets/js/build/modern/frontend.js'],
            'nav-menu' => ['nav-menu', 'menu.min.js', 'navigation'],
        ];

        foreach ($needles as $label => $patterns) {
            foreach ($patterns as $pattern) {
                if (strpos($haystack, strtolower($pattern)) !== false) {
                    return $label;
                }
            }
        }

        return '';
    }

    /**
     * Detect used-CSS state from headers and markup.
     *
     * @param string              $html HTML response.
     * @param array<string,mixed> $headers Headers.
     * @return string
     */
    private function detect_ucss_state(string $html, array $headers): string {
        $header = strtolower((string) ($headers['x-prorank-ucss'] ?? ''));
        if ($header !== '') {
            return $header;
        }

        if (stripos($html, '/ucss-cache/') !== false || stripos($html, '.used.css') !== false) {
            return 'served';
        }

        $asset_settings = get_option('prorank_asset_optimization_settings', []);
        if (is_array($asset_settings) && empty($asset_settings['unused_css_enabled'])) {
            return 'disabled';
        }

        return 'unknown';
    }

    /**
     * Return current runtime settings relevant to frontend optimization.
     *
     * @return array<string,mixed>
     */
    private function get_runtime_settings_snapshot(): array {
        $cache_settings = get_option('prorank_cache_settings', []);
        $asset_settings = get_option('prorank_asset_optimization_settings', []);
        $advanced_settings = get_option('prorank_seo_advanced_settings', []);

        $cache_settings = is_array($cache_settings) ? $cache_settings : [];
        $asset_settings = is_array($asset_settings) ? $asset_settings : [];
        $advanced_settings = is_array($advanced_settings) ? $advanced_settings : [];

        return [
            'js_defer' => !empty($cache_settings['js_defer']),
            'js_delay' => !empty($cache_settings['js_delay']),
            'tracker_delay_enabled' => !empty($cache_settings['tracker_delay_enabled']),
            'css_async' => !empty($cache_settings['css_async']),
            'unused_css_enabled' => !empty($asset_settings['unused_css_enabled']),
            'critical_css_enabled' => !empty($asset_settings['critical_css_enabled']),
            'delay_google_services' => !empty($advanced_settings['delay_google_services']),
            'delay_review_widgets' => array_key_exists('delay_review_widgets', $advanced_settings) ? !empty($advanced_settings['delay_review_widgets']) : null,
            'performance_compatibility_mode' => array_key_exists('performance_compatibility_mode', $advanced_settings) ? !empty($advanced_settings['performance_compatibility_mode']) : null,
        ];
    }

    /**
     * Normalize WP HTTP headers object/array.
     *
     * @param mixed $headers Headers.
     * @return array<string,mixed>
     */
    private function normalize_headers($headers): array {
        if (is_object($headers) && method_exists($headers, 'getAll')) {
            $headers = $headers->getAll();
        }

        if (!is_array($headers)) {
            return [];
        }

        $normalized = [];
        foreach ($headers as $key => $value) {
            $normalized[strtolower((string) $key)] = is_array($value) ? implode(', ', array_map('strval', $value)) : $value;
        }

        return $normalized;
    }

    /**
     * Strip a leading www. for same-site host comparison.
     */
    private function strip_www(string $host): string {
        return preg_replace('/^www\\./i', '', $host) ?: $host;
    }
}
