<?php

/**
 * WordPress Dashboard Widget for ProRank SEO
 *
 * Uses local database for audit data
 *
 * @package ProRank\SEO\Admin
 */

declare(strict_types=1);

namespace ProRank\SEO\Admin;

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


/**
 * Dashboard Widget class
 */


class DashboardWidget {
    /**
     * Free runtime only supports local audit data.
     */
    private function can_use_cloud_audit(): bool {
        return false;
    }


    /**
     * Initialize the dashboard widget
     */
    public function init(): void {
        add_action('wp_dashboard_setup', [$this, 'add_dashboard_widget']);
        add_action('admin_enqueue_scripts', [$this, 'enqueue_widget_styles']);

        // Transient invalidation hooks
        $invalidation_options = [
            'prorank_cache_settings',
            'prorank_asset_optimization_settings',
            'prorank_image_optimization_settings',
            'prorank_image_optimization_stats',
            'prorank_rum_buffer',
            'prorank_auto_link_rules',
            'prorank_auto_links_created',
            'prorank_external_audit_id',
            'prorank_external_audit_status',
            'prorank_external_audit_progress',
            'prorank_external_audit_started',
        ];

        foreach ($invalidation_options as $option) {
            add_action("update_option_{$option}", [$this, 'invalidate_widget_cache']);
        }

        // Audit completion — clear when audit state changes
        add_action('prorank_audit_completed', [$this, 'invalidate_widget_cache']);
        add_action('prorank_audit_started', [$this, 'invalidate_widget_cache']);

        // Schema save — clear on schema postmeta update
        add_action('updated_postmeta', [$this, 'maybe_invalidate_on_schema_save'], 10, 4);
        add_action('added_post_meta', [$this, 'maybe_invalidate_on_schema_save'], 10, 4);
    }

    /**
     * Clear widget transients.
     */
    public function invalidate_widget_cache(): void {
        delete_transient('prorank_widget_all_data');
        delete_transient('prorank_widget_rum');
        delete_transient('prorank_widget_linking');
        delete_transient('prorank_widget_schema');
        delete_transient('prorank_widget_modules');
        delete_transient('prorank_widget_working_signals');
    }

    /**
     * Conditionally invalidate cache when schema postmeta is saved.
     */
    public function maybe_invalidate_on_schema_save($meta_id, $post_id, $meta_key, $meta_value): void {
        if (
            $meta_key === '_prorank_schema_type' ||
            $meta_key === '_prorank_seo_schema_type' ||
            $meta_key === '_prorank_webp_generated' ||
            $meta_key === '_prorank_avif_generated'
        ) {
            $this->invalidate_widget_cache();
        }
    }

    /**
     * Add the widget to WordPress dashboard
     */
    public function add_dashboard_widget(): void {
        wp_add_dashboard_widget(
            'prorank_seo_dashboard_widget',
            __('ProRank SEO', 'prorank-seo'),
            [$this, 'render_widget'],
            null,
            null,
            'normal',
            'high'
        );
    }

    /**
     * Enqueue widget-specific styles
     */
    public function enqueue_widget_styles($hook): void {
        if ($hook !== 'index.php') {
            return;
        }

        wp_add_inline_style('dashboard', $this->get_widget_styles());
    }

    // -------------------------------------------------------------------------
    // Data Layer
    // -------------------------------------------------------------------------

    /**
     * Master aggregator — returns all widget data, cached 5 min.
     */
    private function get_all_widget_data(): array {
        $cached = get_transient('prorank_widget_all_data');
        if (is_array($cached)) {
            return $cached;
        }

        // Avoid calling the paginated audit summary path on every dashboard
        // render just to validate a transient that already exists.
        $latest_audit = $this->get_latest_completed_audit_summary();

        $rum = $this->get_rum_data();
        $performance = $this->get_performance_metrics();
        $modules = $this->get_module_status();

        $data = [
            'audit'        => $this->get_audit_data($latest_audit),
            'rum'          => $rum,
            'search'       => $this->get_search_metrics(),
            'not_found'    => $this->get_not_found_metrics(),
            'redirects'    => $this->get_redirect_metrics(),
            'performance'  => $performance,
            'linking'      => $this->get_internal_linking_stats(),
            'schema'       => $this->get_schema_stats(),
            'modules'      => $modules,
            'working_signals' => $this->get_working_signals($modules, $performance, $rum),
            'license'      => $this->get_license_info(),
        ];

        set_transient('prorank_widget_all_data', $data, 5 * MINUTE_IN_SECONDS);
        return $data;
    }

    /**
     * Check whether cached widget data still points at the newest completed audit.
     *
     * @param array      $cached       Cached widget payload.
     * @param array|null $latest_audit Newest completed audit summary.
     * @return bool
     */
    private function cached_widget_matches_latest_audit(array $cached, ?array $latest_audit): bool {
        $cached_audit = is_array($cached['audit'] ?? null) ? $cached['audit'] : [];

        if ($latest_audit === null) {
            return empty($cached_audit['id']);
        }

        return (string) ($cached_audit['id'] ?? '') === (string) ($latest_audit['id'] ?? '')
            && (string) ($cached_audit['source'] ?? 'local') === (string) ($latest_audit['source'] ?? 'local');
    }

    /**
     * Get audit data from the newest completed audit across local and cloud sources.
     *
     * @param array|null $latest_completed Newest completed audit summary.
     * @return array
     */
    private function get_audit_data(?array $latest_completed = null): array {
        if ($latest_completed === null) {
            $latest_completed = $this->get_latest_completed_audit_summary();
        }

        if ($latest_completed !== null) {
            return [
                'id' => $latest_completed['id'],
                'source' => $latest_completed['source'],
                'state' => 'completed',
                'score' => $latest_completed['score'],
                'last_updated' => $latest_completed['completed_at'] ?? null,
                'stats' => $latest_completed['stats'],
                'progress' => [],
            ];
        }

        global $wpdb;
        $table_audits = $wpdb->prefix . 'prorank_audits';

        if (!$this->table_exists($table_audits)) {
            return [
                'state' => 'idle',
                'score' => null,
                'last_updated' => null,
                'stats' => [],
                'progress' => [],
            ];
        }

        $has_progress_column = $this->table_column_exists($table_audits, 'progress');
        $has_pages_crawled_column = $this->table_column_exists($table_audits, 'pages_crawled');
        $has_total_urls_column = $this->table_column_exists($table_audits, 'total_urls');

        // No completed audit exists yet — fall back to the running local audit, if any.
        if ($has_progress_column) {
            $running = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT audit_id, score, stats, started_at, progress
                     FROM %i
                     WHERE status IN (%s, %s)
                     ORDER BY started_at DESC
                     LIMIT 1",
                    $table_audits,
                    'running',
                    'crawling'
                )
            );
        } else {
            $running = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT audit_id, score, stats, started_at, NULL AS progress
                     FROM %i
                     WHERE status IN (%s, %s)
                     ORDER BY started_at DESC
                     LIMIT 1",
                    $table_audits,
                    'running',
                    'crawling'
                )
            );
        }

        if ($running) {
            $progress = [];
            if (!empty($running->progress)) {
                $progress = json_decode($running->progress, true) ?: [];
            }

            return [
                'state' => 'running',
                'score' => null,
                'last_updated' => $running->started_at,
                'stats' => [],
                'progress' => $progress,
            ];
        }

        return [
            'state' => 'idle',
            'score' => null,
            'last_updated' => null,
            'stats' => [],
            'progress' => [],
        ];
    }

    /**
     * Get the newest completed audit summary across local and cloud sources.
     *
     * @return array|null
     */
    private function get_latest_completed_audit_summary(): ?array {
        $local = $this->get_latest_local_audit_summary();
        $cloud = $this->can_use_cloud_audit() ? $this->get_latest_cloud_audit_summary() : null;

        if ($local === null && $cloud === null) {
            return null;
        }

        if ($local === null) {
            return $cloud;
        }

        if ($cloud === null) {
            return $local;
        }

        return ($cloud['completed_at_ts'] ?? 0) > ($local['completed_at_ts'] ?? 0) ? $cloud : $local;
    }

    /**
     * Get the newest completed local audit summary.
     *
     * @return array|null
     */
    private function get_latest_local_audit_summary(): ?array {
        global $wpdb;
        $table_audits = $wpdb->prefix . 'prorank_audits';
        $table_issues = $wpdb->prefix . 'prorank_audit_issues';

        if (!$this->table_exists($table_audits)) {
            return null;
        }

        $audit_columns = $this->get_table_columns($table_audits);
        $order_by = $this->get_audit_order_by_clause($audit_columns);
        $pages_select = '0 AS pages_crawled';

        if ($this->table_column_exists($table_audits, 'pages_crawled')) {
            $pages_select = 'pages_crawled';
        } elseif ($this->table_column_exists($table_audits, 'total_urls')) {
            $pages_select = 'total_urls AS pages_crawled';
        }

        $audit = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT *, {$pages_select}
                 FROM %i
                 WHERE status = %s
                 ORDER BY {$order_by}
                 LIMIT 1",
                $table_audits,
                'completed'
            )
        );

        if (!$audit) {
            return null;
        }

        $audit_id = (string) ($audit->audit_id ?? $audit->id ?? '');
        if ($audit_id === '') {
            return null;
        }

        $score = is_numeric($audit->score) ? (int) $audit->score : null;
        if ($score === null) {
            return null;
        }

        $stats = [
            'critical_issues' => 0,
            'high_issues' => 0,
            'medium_issues' => 0,
            'low_issues' => 0,
            'warning_issues' => 0,
            'total_issues' => 0,
            'total_urls' => (int) ($audit->pages_crawled ?? 0),
        ];

        if (!empty($audit->stats)) {
            $decoded = json_decode($audit->stats, true);
            if (is_array($decoded)) {
                $by_severity = [];
                if (isset($decoded['unique_issues']['by_severity']) && is_array($decoded['unique_issues']['by_severity'])) {
                    $by_severity = $decoded['unique_issues']['by_severity'];
                } elseif (isset($decoded['unique_issues']) && is_array($decoded['unique_issues'])) {
                    $by_severity = $decoded['unique_issues'];
                }

                $stats['critical_issues'] = (int) ($decoded['critical_issues'] ?? $by_severity['critical'] ?? 0);
                $stats['high_issues'] = (int) ($decoded['high_issues'] ?? $by_severity['high'] ?? 0);
                $stats['medium_issues'] = (int) ($decoded['medium_issues'] ?? $by_severity['medium'] ?? 0);
                $stats['low_issues'] = (int) ($decoded['low_issues'] ?? $by_severity['low'] ?? 0);
                $stats['warning_issues'] = (int) ($decoded['warning_issues'] ?? $by_severity['warning'] ?? 0);

                if (isset($decoded['total_issues']) && is_numeric($decoded['total_issues'])) {
                    $stats['total_issues'] = (int) $decoded['total_issues'];
                } elseif (isset($decoded['unique_issues']['total']) && is_numeric($decoded['unique_issues']['total'])) {
                    $stats['total_issues'] = (int) $decoded['unique_issues']['total'];
                } elseif (isset($decoded['unique_issues']) && is_numeric($decoded['unique_issues'])) {
                    $stats['total_issues'] = (int) $decoded['unique_issues'];
                }

                if (isset($decoded['pages_crawled']) && is_numeric($decoded['pages_crawled'])) {
                    $stats['total_urls'] = (int) $decoded['pages_crawled'];
                }
            }
        }

        if ($stats['total_issues'] <= 0 && $this->table_exists($table_issues)) {
            $issue_rows = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT severity, COUNT(*) AS cnt
                     FROM %i
                     WHERE audit_id = %s
                     GROUP BY severity",
                    $table_issues,
                    $audit_id
                )
            );

            foreach ($issue_rows as $row) {
                $severity = (string) ($row->severity ?? '');
                $count = (int) ($row->cnt ?? 0);

                switch ($severity) {
                    case 'critical':
                        $stats['critical_issues'] = $count;
                        break;
                    case 'high':
                        $stats['high_issues'] = $count;
                        break;
                    case 'medium':
                        $stats['medium_issues'] = $count;
                        break;
                    case 'low':
                        $stats['low_issues'] = $count;
                        break;
                    case 'warning':
                        $stats['warning_issues'] = $count;
                        break;
                }
            }
        }

        if ($stats['total_issues'] <= 0) {
            $stats['total_issues'] = $stats['critical_issues']
                + $stats['high_issues']
                + $stats['medium_issues']
                + $stats['low_issues']
                + $stats['warning_issues'];
        }

        $completed_at = $audit->completed_at ?? $audit->end_time ?? $audit->started_at ?? $audit->start_time ?? null;

        return [
            'id' => $audit_id,
            'source' => 'local',
            'score' => $score,
            'completed_at' => $completed_at,
            'completed_at_ts' => $this->get_audit_timestamp($completed_at),
            'stats' => $stats,
        ];
    }

    /**
     * Get the newest completed non-local audit summary.
     *
     * @return array|null
     */
    private function get_latest_cloud_audit_summary(): ?array {
        return null;
    }

    /**
     * Build an ORDER BY clause that matches the available audit table columns.
     *
     * @param array<int,string> $columns Table column names.
     * @return string
     */
    private function get_audit_order_by_clause(array $columns): string {
        $completed_candidates = [];
        $started_candidates = [];

        if (in_array('completed_at', $columns, true)) {
            $completed_candidates[] = 'completed_at';
        }
        if (in_array('end_time', $columns, true)) {
            $completed_candidates[] = 'end_time';
        }
        if (in_array('started_at', $columns, true)) {
            $completed_candidates[] = 'started_at';
            $started_candidates[] = 'started_at';
        }
        if (in_array('start_time', $columns, true)) {
            $completed_candidates[] = 'start_time';
            $started_candidates[] = 'start_time';
        }

        $parts = [];
        if ($completed_candidates !== []) {
            $parts[] = 'COALESCE(' . implode(', ', $completed_candidates) . ') DESC';
        }
        if ($started_candidates !== []) {
            $parts[] = 'COALESCE(' . implode(', ', $started_candidates) . ') DESC';
        }
        if (in_array('id', $columns, true)) {
            $parts[] = 'id DESC';
        } elseif (in_array('audit_id', $columns, true)) {
            $parts[] = 'audit_id DESC';
        }

        return $parts === [] ? '1 DESC' : implode(', ', $parts);
    }

    /**
     * Normalize a date/time value to a Unix timestamp.
     *
     * @param mixed $value Date/time value.
     * @return int
     */
    private function get_audit_timestamp($value): int {
        if ($value instanceof \DateTimeInterface) {
            return $value->getTimestamp();
        }

        if (!is_scalar($value) || $value === '') {
            return 0;
        }

        $timestamp = strtotime((string) $value);

        return $timestamp !== false ? (int) $timestamp : 0;
    }

    /**
     * Get Core Web Vitals data from RUM buffer.
     * Reference: DashboardController::get_rum_summary()
     */
    private function get_rum_data(): array {
        $cached = get_transient('prorank_widget_rum');
        if (is_array($cached)) {
            return $cached;
        }

        $buffer = get_option('prorank_rum_buffer', []);
        if (!is_array($buffer) || empty($buffer)) {
            $result = [
                'samples' => 0,
                'averages' => [
                    'lcp' => null,
                    'inp' => null,
                    'cls' => null,
                ],
            ];
            set_transient('prorank_widget_rum', $result, 5 * MINUTE_IN_SECONDS);
            return $result;
        }

        $samples = array_slice($buffer, 0, 50);
        $values = ['lcp' => [], 'inp' => [], 'cls' => []];

        foreach ($samples as $sample) {
            foreach (['lcp', 'inp', 'cls'] as $metric) {
                if (isset($sample[$metric]) && is_numeric($sample[$metric])) {
                    $value = (float) $sample[$metric];
                    if (in_array($metric, ['lcp', 'inp'], true) && $value <= 0) {
                        continue;
                    }
                    $values[$metric][] = $value;
                }
            }
        }

        $p75 = [
            'lcp' => $this->calculate_percentile($values['lcp'], 0.75, true),
            'inp' => $this->calculate_percentile($values['inp'], 0.75, true),
            'cls' => $this->calculate_percentile($values['cls'], 0.75, false),
        ];

        $result = [
            'samples' => count($samples),
            'averages' => $p75,
            'p75' => $p75,
            'metric_counts' => [
                'lcp' => count($values['lcp']),
                'inp' => count($values['inp']),
                'cls' => count($values['cls']),
            ],
        ];

        set_transient('prorank_widget_rum', $result, 5 * MINUTE_IN_SECONDS);
        return $result;
    }

    /**
     * Calculate a nearest-rank percentile from RUM samples.
     */
    private function calculate_percentile(array $values, float $percentile, bool $integer): mixed {
        $values = array_values(array_filter($values, 'is_numeric'));
        if (empty($values)) {
            return null;
        }

        sort($values, SORT_NUMERIC);
        $index = (int) ceil($percentile * count($values)) - 1;
        $index = max(0, min($index, count($values) - 1));
        $value = (float) $values[$index];

        return $integer ? (int) round($value) : round($value, 3);
    }

    /**
     * Classify a Core Web Vital metric against web.dev thresholds.
     *
     * @return string 'good' | 'warn' | 'poor'
     */
    private function get_cwv_status(string $metric, $value): string {
        if ($value === null) {
            return 'unknown';
        }

        $thresholds = [
            'lcp' => [2500, 4000],
            'inp' => [200, 500],
            'cls' => [0.1, 0.25],
        ];

        if (!isset($thresholds[$metric])) {
            return 'unknown';
        }

        [$good, $poor] = $thresholds[$metric];

        if ($value <= $good) {
            return 'good';
        }
        if ($value <= $poor) {
            return 'warn';
        }
        return 'poor';
    }

    /**
     * Get Search Console metrics used in dashboard overview.
     */
    private function get_search_metrics(): array {
        return [
            'connected' => false,
            'search_traffic' => 0,
            'impressions' => 0,
            'keywords' => 0,
            'avg_position' => null,
        ];
    }

    /**
     * Get 404 monitor metrics.
     */
    private function get_not_found_metrics(): array {
        global $wpdb;

        $metrics = [
            'total_urls' => 0,
            'total_hits' => 0,
        ];

        $table_name = $wpdb->prefix . 'prorank_404_monitor';
        if (!$this->table_exists($table_name)) {
            return $metrics;
        }

        $row = $wpdb->get_row("SELECT COUNT(*) as total_urls, COALESCE(SUM(hits), 0) as total_hits FROM {$table_name}");
        if ($row) {
            $metrics['total_urls'] = (int) ($row->total_urls ?? 0);
            $metrics['total_hits'] = (int) ($row->total_hits ?? 0);
        }

        return $metrics;
    }

    /**
     * Get redirect metrics.
     */
    private function get_redirect_metrics(): array {
        global $wpdb;

        $metrics = [
            'total_redirects' => 0,
            'total_hits' => 0,
        ];

        $table_name = $wpdb->prefix . 'prorank_redirects';
        if (!$this->table_exists($table_name)) {
            return $metrics;
        }

        $row = $wpdb->get_row("SELECT COUNT(*) as total_redirects, COALESCE(SUM(hits), 0) as total_hits FROM {$table_name}");
        if ($row) {
            $metrics['total_redirects'] = (int) ($row->total_redirects ?? 0);
            $metrics['total_hits'] = (int) ($row->total_hits ?? 0);
        }

        return $metrics;
    }

    /**
     * Get performance metrics for quick dashboard snapshot.
     */
    private function get_performance_metrics(): array {
        $cache_settings = wp_parse_args((array) get_option('prorank_cache_settings', []), [
            'cache_enabled' => false,
        ]);

        $asset_settings = wp_parse_args((array) get_option('prorank_asset_optimization_settings', []), [
            'js_minify_enabled' => false,
        ]);

        $image_settings = wp_parse_args((array) get_option('prorank_image_optimization_settings', []), [
            'webp_enabled' => false,
            'avif_enabled' => false,
            'optimize_on_upload' => false,
        ]);

        $cached_image_stats = (array) get_option('prorank_image_optimization_stats', []);
        $total_images = (int) ($cached_image_stats['total_images'] ?? $cached_image_stats['total'] ?? 0);
        $optimized_images = (int) ($cached_image_stats['optimized_images'] ?? $cached_image_stats['optimized'] ?? 0);
        $bytes_saved = (int) ($cached_image_stats['bytes_saved'] ?? $cached_image_stats['total_savings'] ?? 0);

        try {
            $plugin = \ProRank\SEO\Plugin::get_instance();
            if ($plugin && $plugin->is_initialized()) {
                $image_module = $plugin->modules()->get_module('image_optimization');
                if ($image_module && method_exists($image_module, 'get_stats')) {
                    $module_stats = $image_module->get_stats();
                    $total_images = (int) ($module_stats['total'] ?? $total_images);
                    $optimized_images = (int) ($module_stats['optimized'] ?? $optimized_images);
                    if (isset($module_stats['bytes_saved']) || isset($module_stats['total_savings'])) {
                        $bytes_saved = (int) ($module_stats['bytes_saved'] ?? $module_stats['total_savings'] ?? $bytes_saved);
                    }
                }
            }
        } catch (\Throwable $e) {
            // Keep cached stats fallback.
        }

        $active_flags = [
            !empty($cache_settings['cache_enabled']),
            !empty($asset_settings['js_minify_enabled']),
            !empty($image_settings['webp_enabled']) || !empty($image_settings['avif_enabled']),
            !empty($image_settings['optimize_on_upload']),
        ];

        return [
            'active_tools' => count(array_filter($active_flags)),
            'cache_enabled' => !empty($cache_settings['cache_enabled']),
            'js_minify' => !empty($asset_settings['js_minify_enabled']),
            'total_images' => $total_images,
            'optimized_images' => $optimized_images,
            'bytes_saved' => $bytes_saved,
        ];
    }

    /**
     * Get internal linking stats.
     */
    private function get_internal_linking_stats(): array {
        $cached = get_transient('prorank_widget_linking');
        if (is_array($cached)) {
            return $cached;
        }

        $rules = get_option('prorank_auto_link_rules', []);
        $active_rules = is_array($rules) ? count($rules) : 0;
        $links_created = (int) get_option('prorank_auto_links_created', 0);

        $result = [
            'links_created' => $links_created,
            'active_rules' => $active_rules,
        ];

        set_transient('prorank_widget_linking', $result, 30 * MINUTE_IN_SECONDS);
        return $result;
    }

    /**
     * Get schema markup coverage stats.
     */
    private function get_schema_stats(): array {
        global $wpdb;

        $cached = get_transient('prorank_widget_schema');
        if (is_array($cached)) {
            return $cached;
        }

        $count = 0;

        // Count distinct post IDs with schema from either meta key
        $count = (int) $wpdb->get_var(
            "SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta}
             WHERE meta_key IN ('_prorank_schema_type', '_prorank_seo_schema_type')
             AND meta_value != '' AND meta_value IS NOT NULL"
        );

        $result = [
            'pages_with_schema' => $count,
        ];

        set_transient('prorank_widget_schema', $result, 30 * MINUTE_IN_SECONDS);
        return $result;
    }

    /**
     * Get performance module ON/OFF states (10 key modules).
     */
    private function get_module_status(): array {
        $cached = get_transient('prorank_widget_modules');
        if (is_array($cached)) {
            return $cached;
        }

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

        // Cache: check dedicated option as fallback
        $cache_enabled = !empty($cache_settings['cache_enabled']);
        if (!$cache_enabled) {
            $cache_enabled = (bool) get_option('prorank_page_cache_enabled', false);
        }

        $result = [
            'cache'         => $cache_enabled,
            'js_delay'      => !empty($cache_settings['js_delay']),
            'image_opt'     => !empty($image_settings['optimize_on_upload']),
            'lazy_load'     => !empty($image_settings['lazyload_images']),
            'webp'          => !empty($image_settings['webp_enabled']),
            'avif'          => !empty($image_settings['avif_enabled']),
        ];

        set_transient('prorank_widget_modules', $result, 10 * MINUTE_IN_SECONDS);
        return $result;
    }

    /**
     * Get runtime-backed module evidence for status dots.
     *
     * @param array $modules Module enabled/disabled states.
     * @param array $performance Performance summary metrics.
     * @param array $rum RUM summary metrics.
     */
    private function get_working_signals(array $modules, array $performance, array $rum): array {
        $cached = get_transient('prorank_widget_working_signals');
        if (is_array($cached)) {
            return $cached;
        }

        $signals = [];
        $cache_stats = (array) get_transient('prorank_cache_stats');
        $cache_hits = (int) ($cache_stats['hits'] ?? 0);
        $cache_misses = (int) ($cache_stats['misses'] ?? 0);
        $cached_pages = $this->count_host_html_cache_files();

        if (!empty($modules['cache'])) {
            if ($cache_hits > 0 || $cached_pages > 0) {
                $signals['cache'] = [
                    'state' => 'good',
                    'evidence' => sprintf(
                        /* translators: 1: cache hits, 2: cached pages */
                        __('%1$s hits, %2$s pages', 'prorank-seo'),
                        number_format_i18n($cache_hits),
                        number_format_i18n($cached_pages)
                    ),
                ];
            } else {
                $signals['cache'] = [
                    'state' => 'warn',
                    'evidence' => __('Enabled, no runtime hits yet', 'prorank-seo'),
                ];
            }
        } else {
            $signals['cache'] = ['state' => 'off', 'evidence' => __('Disabled', 'prorank-seo')];
        }

        if (!empty($modules['js_delay'])) {
            $signals['js_delay'] = [
                'state' => ($cache_hits + $cache_misses) > 0 ? 'warn' : 'unknown',
                'evidence' => __('No direct runtime counter', 'prorank-seo'),
            ];
        } else {
            $signals['js_delay'] = ['state' => 'off', 'evidence' => __('Disabled', 'prorank-seo')];
        }

        $optimized_images = (int) ($performance['optimized_images'] ?? 0);
        $total_images = (int) ($performance['total_images'] ?? 0);
        if (!empty($modules['image_opt'])) {
            if ($optimized_images > 0) {
                $signals['image_opt'] = [
                    'state' => 'good',
                    'evidence' => sprintf(
                        '%1$s/%2$s',
                        number_format_i18n($optimized_images),
                        number_format_i18n(max($optimized_images, $total_images))
                    ),
                ];
            } elseif ($total_images > 0) {
                $signals['image_opt'] = [
                    'state' => 'warn',
                    'evidence' => sprintf('0/%s', number_format_i18n($total_images)),
                ];
            } else {
                $signals['image_opt'] = [
                    'state' => 'unknown',
                    'evidence' => __('No image jobs yet', 'prorank-seo'),
                ];
            }
        } else {
            $signals['image_opt'] = ['state' => 'off', 'evidence' => __('Disabled', 'prorank-seo')];
        }

        if (!empty($modules['lazy_load'])) {
            $signals['lazy_load'] = [
                'state' => !empty($rum['samples']) ? 'warn' : 'unknown',
                'evidence' => __('No direct runtime counter', 'prorank-seo'),
            ];
        } else {
            $signals['lazy_load'] = ['state' => 'off', 'evidence' => __('Disabled', 'prorank-seo')];
        }

        $webp_generated = $this->count_generated_attachments('_prorank_webp_generated');
        if (!empty($modules['webp'])) {
            if ($webp_generated > 0) {
                $signals['webp'] = [
                    'state' => 'good',
                    'evidence' => sprintf(
                        /* translators: %s: number of attachments */
                        __('%s generated', 'prorank-seo'),
                        number_format_i18n($webp_generated)
                    ),
                ];
            } else {
                $signals['webp'] = [
                    'state' => 'warn',
                    'evidence' => __('Enabled, no generated files', 'prorank-seo'),
                ];
            }
        } else {
            $signals['webp'] = ['state' => 'off', 'evidence' => __('Disabled', 'prorank-seo')];
        }

        $avif_generated = $this->count_generated_attachments('_prorank_avif_generated');
        if (!empty($modules['avif'])) {
            if ($avif_generated > 0) {
                $signals['avif'] = [
                    'state' => 'good',
                    'evidence' => sprintf(
                        /* translators: %s: number of attachments */
                        __('%s generated', 'prorank-seo'),
                        number_format_i18n($avif_generated)
                    ),
                ];
            } else {
                $signals['avif'] = [
                    'state' => 'warn',
                    'evidence' => __('Enabled, no generated files', 'prorank-seo'),
                ];
            }
        } else {
            $signals['avif'] = ['state' => 'off', 'evidence' => __('Disabled', 'prorank-seo')];
        }

        if (!empty($modules['lcp_opt'])) {
            $lcp_value = $rum['p75']['lcp'] ?? $rum['averages']['lcp'] ?? null;
            if (is_numeric($lcp_value)) {
                $signals['lcp_opt'] = [
                    'state' => 'good',
                    'evidence' => 'p75 ' . number_format_i18n((int) $lcp_value) . 'ms',
                ];
            } else {
                $signals['lcp_opt'] = [
                    'state' => 'warn',
                    'evidence' => __('No RUM data yet', 'prorank-seo'),
                ];
            }
        } else {
            $signals['lcp_opt'] = ['state' => 'off', 'evidence' => __('Disabled', 'prorank-seo')];
        }

        if (!empty($modules['inp_opt'])) {
            $inp_value = $rum['p75']['inp'] ?? $rum['averages']['inp'] ?? null;
            if (is_numeric($inp_value)) {
                $signals['inp_opt'] = [
                    'state' => 'good',
                    'evidence' => 'p75 ' . number_format_i18n((int) $inp_value) . 'ms',
                ];
            } else {
                $signals['inp_opt'] = [
                    'state' => 'warn',
                    'evidence' => __('No RUM data yet', 'prorank-seo'),
                ];
            }
        } else {
            $signals['inp_opt'] = ['state' => 'off', 'evidence' => __('Disabled', 'prorank-seo')];
        }

        set_transient('prorank_widget_working_signals', $signals, 5 * MINUTE_IN_SECONDS);
        return $signals;
    }

    /**
     * Count cached HTML pages for the current host.
     */
    private function count_host_html_cache_files(): int {
        $upload_dir = wp_upload_dir();
        $host = (string) (wp_parse_url(home_url(), PHP_URL_HOST) ?? '');
        if ($host === '' || empty($upload_dir['basedir'])) {
            return 0;
        }

        $candidate_dirs = [
            trailingslashit((string) $upload_dir['basedir']) . 'prorank-cache/html/' . $host,
            trailingslashit((string) $upload_dir['basedir']) . 'prorank-seo/page-cache/' . $host,
            WP_CONTENT_DIR . '/cache/prorank-seo/' . $host,
            WP_CONTENT_DIR . '/cache/prorank/' . $host,
        ];

        $count = 0;
        foreach (array_unique($candidate_dirs) as $cache_dir) {
            if (!is_dir($cache_dir)) {
                continue;
            }

            try {
                $iterator = new \RecursiveIteratorIterator(
                    new \RecursiveDirectoryIterator($cache_dir, \RecursiveDirectoryIterator::SKIP_DOTS),
                    \RecursiveIteratorIterator::LEAVES_ONLY
                );
            } catch (\Throwable $e) {
                continue;
            }

            foreach ($iterator as $file) {
                if (!$file->isFile()) {
                    continue;
                }
                if (strtolower((string) $file->getExtension()) === 'html') {
                    $count++;
                }
                if ($count >= 50000) {
                    return $count;
                }
            }
        }

        return $count;
    }

    /**
     * Count attachments with generated modern format metadata.
     */
    private function count_generated_attachments(string $meta_key): int {
        global $wpdb;

        $count = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(DISTINCT pm.post_id)
                 FROM {$wpdb->postmeta} pm
                 INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
                 WHERE pm.meta_key = %s
                   AND pm.meta_value <> ''
                   AND p.post_type = 'attachment'
                   AND p.post_status <> 'trash'",
                $meta_key
            )
        );

        return max(0, $count);
    }

    /**
     * Get license/edition info for the header badge.
     */
    private function get_license_info(): array {
        return [
            'tier' => 'free',
            'display_name' => 'Free',
        ];
    }

    // -------------------------------------------------------------------------
    // Rendering
    // -------------------------------------------------------------------------

    /**
     * Render the widget content
     */
    public function render_widget(): void {
        $data = $this->get_all_widget_data();

        $audit       = $data['audit'];
        $rum         = $data['rum'];
        $search      = $data['search'];
        $not_found   = $data['not_found'];
        $redirects   = $data['redirects'];
        $performance = $data['performance'];
        $linking     = $data['linking'];
        $schema      = $data['schema'];
        $modules     = $data['modules'];
        $working_signals = $data['working_signals'] ?? [];
        $license     = $data['license'];

        $score = $audit['score'];
        $state = $audit['state'];
        $progress = $audit['progress'] ?? [];
        $audit_stats = is_array($audit['stats'] ?? null) ? $audit['stats'] : [];
        $audit_issue_total = (int) ($audit_stats['total_issues'] ?? 0);
        $audit_pages_total = (int) ($audit_stats['total_urls'] ?? 0);
        $has_completed_audit = $state === 'completed' && $score !== null;

        $admin_url = admin_url('admin.php');
        $site_audit_url = add_query_arg(['page' => 'prorank-site-audit'], $admin_url);
        $internal_linking_url = add_query_arg(['page' => 'prorank-internal-linking'], $admin_url);
        ?>
        <div class="prw-widget">

            <?php // === Section 1: Header Bar === ?>
            <div class="prw-header">
                <div class="prw-header-left">
                    <svg class="prw-logo" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                        <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" stroke="#3b82f6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                    </svg>
                    <span class="prw-brand">ProRank SEO</span>
                    <span class="prw-badge prw-badge--<?php echo esc_attr($license['tier']); ?>">
                        <?php echo esc_html($license['display_name']); ?>
                    </span>
                </div>
                <div class="prw-header-right">
                    <?php if ($state === 'running' || $state === 'crawling'): ?>
                        <div class="prw-score-mini prw-score-mini--running">
                            <span class="dashicons dashicons-update prw-spin"></span>
                        </div>
                    <?php elseif ($score !== null): ?>
                        <div class="prw-score-mini">
                            <svg viewBox="0 0 44 44" class="prw-score-ring">
                                <circle cx="22" cy="22" r="18" fill="none" stroke="#e2e8f0" stroke-width="4" />
                                <circle cx="22" cy="22" r="18" fill="none"
                                    stroke="<?php echo esc_attr($this->get_score_color((int)$score)); ?>"
                                    stroke-width="4" stroke-linecap="round"
                                    stroke-dasharray="<?php echo esc_attr(((int)$score / 100) * 113.1); ?> 113.1"
                                    transform="rotate(-90 22 22)" />
                            </svg>
                            <span class="prw-score-num"><?php echo esc_html((string)$score); ?></span>
                        </div>
                    <?php else: ?>
                        <div class="prw-score-mini prw-score-mini--empty">
                            <svg viewBox="0 0 44 44" class="prw-score-ring">
                                <circle cx="22" cy="22" r="18" fill="none" stroke="#e2e8f0" stroke-width="4" />
                            </svg>
                            <span class="prw-score-num prw-score-num--empty">--</span>
                        </div>
                    <?php endif; ?>
                </div>
            </div>

            <?php // === Audit Running Banner === ?>
            <?php if ($state === 'running' || $state === 'crawling'): ?>
                <div class="prw-running-banner">
                    <div class="prw-running-text">
                        <?php esc_html_e('Audit in progress', 'prorank-seo'); ?>
                        <?php if (!empty($progress['percent'])): ?>
                            — <?php echo esc_html((int)$progress['percent']); ?>%
                        <?php endif; ?>
                    </div>
                    <?php if (!empty($progress['percent'])): ?>
                        <div class="prw-progress-track">
                            <div class="prw-progress-fill" style="width:<?php echo esc_attr((int)$progress['percent']); ?>%"></div>
                        </div>
                    <?php endif; ?>
                </div>
            <?php endif; ?>

            <?php // === Section 2: Core Web Vitals === ?>
            <div class="prw-section">
                <div class="prw-section-title"><?php esc_html_e('Core Web Vitals', 'prorank-seo'); ?></div>
                <?php if ($rum['samples'] > 0): ?>
                    <div class="prw-cwv-row">
                        <?php
                        $cwv_metrics = [
                            'lcp' => ['label' => 'LCP', 'unit' => 'ms', 'format' => fn($v) => $v !== null ? number_format_i18n($v) . 'ms' : '—'],
                            'inp' => ['label' => 'INP', 'unit' => 'ms', 'format' => fn($v) => $v !== null ? number_format_i18n($v) . 'ms' : '—'],
                            'cls' => ['label' => 'CLS', 'unit' => '',  'format' => fn($v) => $v !== null ? number_format_i18n($v, 3) : '—'],
                        ];
                        foreach ($cwv_metrics as $key => $meta):
                            $value = $rum['p75'][$key] ?? $rum['averages'][$key] ?? null;
                            $status = $this->get_cwv_status($key, $value);
                        ?>
                            <div class="prw-cwv-item">
                                <span class="prw-cwv-dot prw-cwv-dot--<?php echo esc_attr($status); ?>"></span>
                                <span class="prw-cwv-value"><?php echo esc_html(($meta['format'])($value)); ?></span>
                                <span class="prw-cwv-label"><?php echo esc_html($meta['label']); ?></span>
                            </div>
                        <?php endforeach; ?>
                    </div>
                    <div class="prw-cwv-samples">
                        <?php
                        /* translators: %s: Number of Core Web Vitals samples. */
                        $samples_label = esc_html__('p75 · %s samples', 'prorank-seo');
                        printf(
                            esc_html($samples_label),
                            esc_html(number_format_i18n($rum['samples']))
                        ); ?>
                    </div>
                <?php else: ?>
                    <div class="prw-cwv-empty">
                        <a href="<?php echo esc_url(add_query_arg(['page' => 'prorank-performance'], $admin_url)); ?>">
                            <?php esc_html_e('Enable RUM to see real user metrics', 'prorank-seo'); ?>
                        </a>
                    </div>
                <?php endif; ?>
            </div>

            <?php // === Section 3: Quick Stats Grid === ?>
            <div class="prw-section">
                <div class="prw-stats-grid">
                    <?php // SEO Issues ?>
                    <div class="prw-stat">
                        <?php if ($has_completed_audit): ?>
                            <div class="prw-stat-value"><?php echo esc_html($this->format_compact_number($audit_issue_total)); ?></div>
                        <?php else: ?>
                            <div class="prw-stat-value prw-stat-value--link">
                                <a href="<?php echo esc_url($site_audit_url); ?>">
                                    <?php esc_html_e('Run Audit', 'prorank-seo'); ?>
                                </a>
                            </div>
                        <?php endif; ?>
                        <div class="prw-stat-label"><?php esc_html_e('SEO Issues', 'prorank-seo'); ?></div>
                    </div>

                    <?php // URLs Audited ?>
                    <div class="prw-stat">
                        <div class="prw-stat-value">
                            <?php echo esc_html($has_completed_audit ? $this->format_compact_number($audit_pages_total) : '—'); ?>
                        </div>
                        <div class="prw-stat-label"><?php esc_html_e('URLs Audited', 'prorank-seo'); ?></div>
                    </div>

                    <?php // Images Optimized ?>
                    <div class="prw-stat">
                        <div class="prw-stat-value"><?php echo esc_html($this->format_compact_number($performance['optimized_images'])); ?></div>
                        <div class="prw-stat-label"><?php esc_html_e('Images Optimized', 'prorank-seo'); ?></div>
                        <?php if ($performance['bytes_saved'] > 0): ?>
                            <div class="prw-stat-sub"><?php echo esc_html(size_format($performance['bytes_saved'])); ?> <?php esc_html_e('saved', 'prorank-seo'); ?></div>
                        <?php endif; ?>
                    </div>

                    <?php // 404 Errors ?>
                    <div class="prw-stat">
                        <div class="prw-stat-value"><?php echo esc_html($this->format_compact_number($not_found['total_urls'])); ?></div>
                        <div class="prw-stat-label"><?php esc_html_e('404 Errors', 'prorank-seo'); ?></div>
                    </div>

                    <?php // Redirects ?>
                    <div class="prw-stat">
                        <div class="prw-stat-value"><?php echo esc_html($this->format_compact_number($redirects['total_redirects'])); ?></div>
                        <div class="prw-stat-label"><?php esc_html_e('Redirects', 'prorank-seo'); ?></div>
                    </div>

                    <?php // Schema Pages ?>
                    <div class="prw-stat">
                        <div class="prw-stat-value"><?php echo esc_html($this->format_compact_number($schema['pages_with_schema'])); ?></div>
                        <div class="prw-stat-label"><?php esc_html_e('Schema Pages', 'prorank-seo'); ?></div>
                    </div>
                </div>
            </div>

            <?php // === Section 4: Performance Module Status === ?>
            <div class="prw-section">
                <div class="prw-section-title"><?php esc_html_e('Performance Modules', 'prorank-seo'); ?></div>
                <div class="prw-modules-grid">
                    <?php
                    $module_labels = [
                        'cache'        => __('Cache', 'prorank-seo'),
                        'js_delay'     => __('JS Delay', 'prorank-seo'),
                        'image_opt'    => __('Img Opt', 'prorank-seo'),
                        'lazy_load'    => __('Lazy', 'prorank-seo'),
                        'webp'         => __('WebP', 'prorank-seo'),
                        'avif'         => __('AVIF', 'prorank-seo'),
                    ];
                    foreach ($module_labels as $slug => $label):
                        $active = !empty($modules[$slug]);
                    ?>
                        <div class="prw-module <?php echo $active ? 'prw-module--on' : 'prw-module--off'; ?>">
                            <span class="prw-module-dot"></span>
                            <span class="prw-module-label"><?php echo esc_html($label); ?></span>
                        </div>
                    <?php endforeach; ?>
                </div>

                <div class="prw-signals-title"><?php esc_html_e('Working Signals', 'prorank-seo'); ?></div>
                <div class="prw-signals-grid">
                    <?php
                    foreach ($module_labels as $slug => $label):
                        $signal = $working_signals[$slug] ?? ['state' => 'unknown', 'evidence' => __('No data', 'prorank-seo')];
                        $state = in_array($signal['state'], ['good', 'warn', 'off', 'unknown'], true) ? $signal['state'] : 'unknown';
                    ?>
                        <div class="prw-signal prw-signal--<?php echo esc_attr($state); ?>">
                            <div class="prw-signal-top">
                                <span class="prw-signal-dot"></span>
                                <span class="prw-signal-label"><?php echo esc_html($label); ?></span>
                            </div>
                            <div class="prw-signal-evidence"><?php echo esc_html((string) $signal['evidence']); ?></div>
                        </div>
                    <?php endforeach; ?>
                </div>
            </div>

            <?php // === Section 5: Quick Actions === ?>
            <div class="prw-actions">
                <a href="<?php echo esc_url($site_audit_url); ?>" class="prw-action" title="<?php esc_attr_e('Run Audit', 'prorank-seo'); ?>">
                    <span class="dashicons dashicons-chart-area"></span>
                    <span><?php esc_html_e('Audit', 'prorank-seo'); ?></span>
                </a>
                <a href="<?php echo esc_url(add_query_arg(['page' => 'prorank-performance'], $admin_url)); ?>" class="prw-action" title="<?php esc_attr_e('Performance', 'prorank-seo'); ?>">
                    <span class="dashicons dashicons-performance"></span>
                    <span><?php esc_html_e('Speed', 'prorank-seo'); ?></span>
                </a>
                <?php
                $il_detector = new \ProRank\SEO\Core\Compatibility\CompetitorDetector();
                if (!$il_detector->should_enable_internal_linking_safe_mode()) : ?>
                <a href="<?php echo esc_url($internal_linking_url); ?>" class="prw-action" title="<?php esc_attr_e('Internal Linking', 'prorank-seo'); ?>">
                    <span class="dashicons dashicons-chart-bar"></span>
                    <span><?php esc_html_e('Links', 'prorank-seo'); ?></span>
                </a>
                <?php endif; ?>
                <a href="<?php echo esc_url(add_query_arg(['page' => 'prorank-performance'], $admin_url)); ?>" class="prw-action" title="<?php esc_attr_e('Images', 'prorank-seo'); ?>">
                    <span class="dashicons dashicons-format-image"></span>
                    <span><?php esc_html_e('Images', 'prorank-seo'); ?></span>
                </a>
                <a href="<?php echo esc_url(add_query_arg(['page' => 'prorank-technical-seo'], $admin_url)); ?>" class="prw-action" title="<?php esc_attr_e('Technical SEO', 'prorank-seo'); ?>">
                    <span class="dashicons dashicons-admin-tools"></span>
                    <span><?php esc_html_e('Technical', 'prorank-seo'); ?></span>
                </a>
            </div>

            <?php // === Section 6: Footer === ?>
            <div class="prw-footer">
                <span class="prw-footer-period"><?php esc_html_e('Latest site snapshot', 'prorank-seo'); ?></span>
                <a href="<?php echo esc_url(add_query_arg(['page' => 'prorank-seo'], $admin_url)); ?>" class="prw-footer-link">
                    <?php esc_html_e('Full Dashboard', 'prorank-seo'); ?> &rarr;
                </a>
            </div>

        </div>
        <?php
    }

    // -------------------------------------------------------------------------
    // Utility Methods
    // -------------------------------------------------------------------------

    /**
     * Get column names for a custom table.
     *
     * @param string $table_name Database table name.
     * @return array<int,string>
     */
    private function get_table_columns(string $table_name): array {
        global $wpdb;

        $columns = $wpdb->get_col("SHOW COLUMNS FROM `{$table_name}`", 0);
        if (!is_array($columns)) {
            return [];
        }

        return array_map('strval', $columns);
    }

    /**
     * Check if a custom table exists.
     */
    private function table_exists(string $table_name): bool {
        global $wpdb;

        return $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)) === $table_name;
    }

    /**
     * Check if a column exists in a custom table.
     */
    private function table_column_exists(string $table_name, string $column_name): bool {
        global $wpdb;

        $column = $wpdb->get_var(
            $wpdb->prepare(
                "SHOW COLUMNS FROM {$table_name} LIKE %s",
                $column_name
            )
        );

        return is_string($column) && $column !== '';
    }

    /**
     * Format large numbers (1,200 -> 1.2K).
     */
    private function format_compact_number($value): string {
        if ($value === null) {
            return '—';
        }

        $number = (float) $value;
        $abs = abs($number);

        if ($abs >= 1000000) {
            return number_format_i18n($number / 1000000, 1) . 'M';
        }

        if ($abs >= 1000) {
            return number_format_i18n($number / 1000, 1) . 'K';
        }

        return number_format_i18n((int) round($number));
    }

    /**
     * Format average position metric.
     */
    private function format_position($position): string {
        if ($position === null) {
            return '—';
        }

        return number_format_i18n((float) $position, 1);
    }

    /**
     * Get score color
     */
    private function get_score_color(int $score): string {
        if ($score >= 90) return '#10b981';
        if ($score >= 70) return '#84cc16';
        if ($score >= 50) return '#f59e0b';
        if ($score >= 30) return '#f97316';
        return '#ef4444';
    }

    /**
     * Get score class for styling
     */
    private function get_score_class(int $score): string {
        if ($score >= 90) return 'excellent';
        if ($score >= 70) return 'good';
        if ($score >= 50) return 'fair';
        if ($score >= 30) return 'poor';
        return 'critical';
    }

    /**
     * Get score label
     */
    private function get_score_label(int $score): string {
        if ($score >= 90) return __('Excellent', 'prorank-seo');
        if ($score >= 70) return __('Good', 'prorank-seo');
        if ($score >= 50) return __('Fair', 'prorank-seo');
        if ($score >= 30) return __('Poor', 'prorank-seo');
        return __('Critical', 'prorank-seo');
    }

    // -------------------------------------------------------------------------
    // Styles
    // -------------------------------------------------------------------------

    /**
     * Get widget styles
     */
    private function get_widget_styles(): string {
        return '
        /* === ProRank Widget (prw-) === */
        .prw-widget {
            margin: -11px -12px;
        }

        /* --- Header --- */
        .prw-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 12px 16px;
            background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
            border-bottom: 1px solid #e2e8f0;
        }

        .prw-header-left {
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .prw-logo {
            flex-shrink: 0;
        }

        .prw-brand {
            font-weight: 700;
            font-size: 14px;
            color: #0f172a;
            letter-spacing: -0.01em;
        }

        .prw-badge {
            display: inline-block;
            padding: 1px 7px;
            border-radius: 999px;
            font-size: 10px;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.04em;
            line-height: 1.6;
        }

        .prw-badge--premium,
        .prw-badge--pro {
            background: #dbeafe;
            color: #1d4ed8;
        }

        .prw-badge--agency {
            background: #ede9fe;
            color: #6d28d9;
        }

        .prw-badge--free {
            background: #f1f5f9;
            color: #64748b;
        }

        /* Score mini circle */
        .prw-score-mini {
            position: relative;
            width: 44px;
            height: 44px;
            flex-shrink: 0;
        }

        .prw-score-ring {
            width: 44px;
            height: 44px;
        }

        .prw-score-num {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            font-size: 14px;
            font-weight: 800;
            color: #0f172a;
            line-height: 1;
        }

        .prw-score-num--empty {
            color: #94a3b8;
            font-size: 12px;
        }

        .prw-score-mini--running {
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .prw-score-mini--running .dashicons {
            font-size: 22px;
            width: 22px;
            height: 22px;
            color: #3b82f6;
        }

        /* Spin animation */
        .prw-spin {
            animation: prw-spin 1.5s linear infinite;
        }

        @keyframes prw-spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        /* --- Running Banner --- */
        .prw-running-banner {
            padding: 8px 16px;
            background: #eff6ff;
            border-bottom: 1px solid #dbeafe;
        }

        .prw-running-text {
            font-size: 12px;
            font-weight: 600;
            color: #1d4ed8;
            margin-bottom: 6px;
        }

        .prw-progress-track {
            height: 4px;
            background: #bfdbfe;
            border-radius: 2px;
            overflow: hidden;
        }

        .prw-progress-fill {
            height: 100%;
            background: linear-gradient(90deg, #3b82f6, #22c55e);
            border-radius: 2px;
            transition: width 0.3s ease;
        }

        /* --- Section --- */
        .prw-section {
            padding: 12px 16px;
            border-bottom: 1px solid #e2e8f0;
        }

        .prw-section-title {
            font-size: 11px;
            font-weight: 700;
            color: #64748b;
            text-transform: uppercase;
            letter-spacing: 0.05em;
            margin-bottom: 8px;
        }

        /* --- CWV Row --- */
        .prw-cwv-row {
            display: flex;
            gap: 12px;
        }

        .prw-cwv-item {
            flex: 1;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 3px;
            padding: 8px 4px;
            background: #f8fafc;
            border: 1px solid #e2e8f0;
            border-radius: 6px;
        }

        .prw-cwv-dot {
            width: 8px;
            height: 8px;
            border-radius: 50%;
            flex-shrink: 0;
        }

        .prw-cwv-dot--good { background: #22c55e; }
        .prw-cwv-dot--warn { background: #f59e0b; }
        .prw-cwv-dot--poor { background: #ef4444; }
        .prw-cwv-dot--unknown { background: #cbd5e1; }

        .prw-cwv-value {
            font-size: 15px;
            font-weight: 700;
            color: #0f172a;
            line-height: 1.2;
        }

        .prw-cwv-label {
            font-size: 10px;
            font-weight: 600;
            color: #64748b;
            text-transform: uppercase;
            letter-spacing: 0.04em;
        }

        .prw-cwv-samples {
            text-align: center;
            font-size: 11px;
            color: #94a3b8;
            margin-top: 6px;
        }

        .prw-cwv-empty {
            padding: 10px 0;
            text-align: center;
            font-size: 12px;
        }

        .prw-cwv-empty a {
            color: #3b82f6;
            text-decoration: none;
        }

        .prw-cwv-empty a:hover {
            text-decoration: underline;
        }

        /* --- Stats Grid --- */
        .prw-stats-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 10px;
        }

        .prw-stat {
            text-align: center;
            padding: 10px 6px 8px;
            background: #f8fafc;
            border: 1px solid #e2e8f0;
            border-radius: 6px;
        }

        .prw-stat-value {
            font-size: 20px;
            font-weight: 800;
            color: #0f172a;
            line-height: 1.2;
        }

        .prw-stat-value--link {
            font-size: 13px;
            font-weight: 600;
        }

        .prw-stat-value--link a {
            color: #3b82f6;
            text-decoration: none;
        }

        .prw-stat-value--link a:hover {
            text-decoration: underline;
        }

        .prw-stat-label {
            font-size: 10px;
            font-weight: 600;
            color: #64748b;
            text-transform: uppercase;
            letter-spacing: 0.03em;
            margin-top: 2px;
        }

        .prw-stat-sub {
            font-size: 10px;
            color: #10b981;
            font-weight: 500;
            margin-top: 1px;
        }

        /* --- Module Grid --- */
        .prw-modules-grid {
            display: grid;
            grid-template-columns: repeat(5, 1fr);
            gap: 6px;
        }

        .prw-module {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 3px;
            padding: 6px 2px;
            border-radius: 4px;
        }

        .prw-module-dot {
            width: 8px;
            height: 8px;
            border-radius: 50%;
        }

        .prw-module--on .prw-module-dot {
            background: #22c55e;
            box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2);
        }

        .prw-module--off .prw-module-dot {
            background: #cbd5e1;
        }

        .prw-module-label {
            font-size: 9px;
            font-weight: 600;
            color: #475569;
            text-transform: uppercase;
            letter-spacing: 0.03em;
            text-align: center;
            line-height: 1.2;
        }

        .prw-module--off .prw-module-label {
            color: #94a3b8;
        }

        .prw-signals-title {
            margin-top: 10px;
            margin-bottom: 6px;
            font-size: 10px;
            font-weight: 700;
            color: #64748b;
            text-transform: uppercase;
            letter-spacing: 0.04em;
        }

        .prw-signals-grid {
            display: grid;
            grid-template-columns: repeat(5, 1fr);
            gap: 6px;
        }

        .prw-signal {
            background: #f8fafc;
            border: 1px solid #e2e8f0;
            border-radius: 6px;
            padding: 6px 5px;
            min-height: 54px;
        }

        .prw-signal-top {
            display: flex;
            align-items: center;
            gap: 4px;
            margin-bottom: 3px;
        }

        .prw-signal-dot {
            width: 7px;
            height: 7px;
            border-radius: 50%;
            flex-shrink: 0;
            background: #cbd5e1;
        }

        .prw-signal--good .prw-signal-dot {
            background: #22c55e;
            box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2);
        }

        .prw-signal--warn .prw-signal-dot {
            background: #f59e0b;
            box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.18);
        }

        .prw-signal--off .prw-signal-dot {
            background: #94a3b8;
        }

        .prw-signal--unknown .prw-signal-dot {
            background: #60a5fa;
            box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.18);
        }

        .prw-signal .prw-signal-label {
            font-size: 9px;
            font-weight: 700;
            color: #334155;
            text-transform: uppercase;
            letter-spacing: 0.03em;
            line-height: 1.2;
        }

        .prw-signal-evidence {
            font-size: 9px;
            color: #64748b;
            line-height: 1.25;
            word-break: break-word;
        }

        /* --- Quick Actions --- */
        .prw-actions {
            display: flex;
            border-bottom: 1px solid #e2e8f0;
        }

        .prw-action {
            flex: 1;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 3px;
            padding: 10px 4px;
            text-decoration: none;
            color: #475569;
            font-size: 10px;
            font-weight: 600;
            transition: background 0.15s, color 0.15s;
            border-right: 1px solid #e2e8f0;
        }

        .prw-action:last-child {
            border-right: none;
        }

        .prw-action:hover {
            background: #f1f5f9;
            color: #1d4ed8;
        }

        .prw-action .dashicons {
            font-size: 18px;
            width: 18px;
            height: 18px;
        }

        /* --- Footer --- */
        .prw-footer {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 8px 16px;
        }

        .prw-footer-period {
            font-size: 11px;
            color: #94a3b8;
        }

        .prw-footer-link {
            font-size: 12px;
            font-weight: 600;
            color: #3b82f6;
            text-decoration: none;
        }

        .prw-footer-link:hover {
            color: #1d4ed8;
            text-decoration: underline;
        }

        /* --- Responsive --- */
        @media (max-width: 782px) {
            .prw-stats-grid {
                grid-template-columns: repeat(2, 1fr);
            }

            .prw-modules-grid {
                grid-template-columns: repeat(3, 1fr);
            }

            .prw-signals-grid {
                grid-template-columns: repeat(3, 1fr);
            }

            .prw-cwv-row {
                flex-direction: column;
                gap: 6px;
            }

            .prw-cwv-item {
                flex-direction: row;
                justify-content: center;
                gap: 8px;
                padding: 6px 12px;
            }

            .prw-actions {
                flex-wrap: wrap;
            }

            .prw-action {
                flex-basis: 33.333%;
                border-bottom: 1px solid #e2e8f0;
            }

            .prw-action:nth-child(3) {
                border-right: none;
            }
        }
        ';
    }
}
