<?php

namespace WPSN\Modules\Diagnostics\Admin;

use WPSN\Admin\SettingsRepository;
use WPSN\Core\Capabilities;
use WPSN\Core\CompatibilityDetector;
use WPSN\Core\Container;
use WPSN\Core\Http\HttpClient;
use WPSN\Core\Settings;
use WPSN\Core\Queue\QueuePause;
use WPSN\Modules\Diagnostics\QualityGateService;
use WPSN\Modules\Diagnostics\ReleaseSmokeBadgeHtml;
use WPSN\Modules\Diagnostics\ReleaseSmokeCompactSummaryBuilder;
use WPSN\Modules\Diagnostics\ReleaseSmokeMatrixService;
use WPSN\Modules\Diagnostics\ReleaseSmokeDiffBuilder;
use WPSN\Modules\Stability\HeadSnapshotService;

defined('ABSPATH') || exit;

/**
 * Quality Gate
 *
 * A single screen that answers:
 * - Is sitemap reachable?
 * - Is robots.txt reachable?
 * - Does head output look sane?
 * - Is the internal queue healthy?
 *
 * IMPORTANT: Runs best-effort loopback checks against local URLs only.
 */
final class QualityGateAdmin
{
    public const PAGE_SLUG = 'wpsn-quality-gate';

    private const TTL = 10 * MINUTE_IN_SECONDS;

    private Container $container;
    private Settings $settings;

    public function __construct(Container $container)
    {
        $this->container = $container;
        $this->settings = $container->get('settings');
    }

    public function register(): void
    {
        add_action('admin_menu', [$this, 'addMenu']);

        add_action('admin_post_wpsn_quality_gate_run', [$this, 'handleRun']);
        add_action('admin_post_wpsn_quality_gate_clear', [$this, 'handleClear']);
        add_action('admin_post_wpsn_quality_gate_run_queue', [$this, 'handleRunQueueWorker']);
        add_action('admin_post_wpsn_quality_gate_save_settings', [$this, 'handleSaveSettings']);
        add_action('admin_post_wpsn_release_smoke_run', [$this, 'handleRunReleaseSmoke']);
        add_action('admin_post_wpsn_release_smoke_export', [$this, 'handleExportReleaseSmoke']);
        add_action('admin_post_wpsn_release_smoke_compare_export', [$this, 'handleExportReleaseSmokeCompare']);
        add_action('admin_post_wpsn_release_smoke_clear', [$this, 'handleClearReleaseSmoke']);
        add_action('admin_post_wpsn_release_smoke_pin', [$this, 'handlePinReleaseSmoke']);
        add_action('admin_post_wpsn_release_smoke_unpin', [$this, 'handleUnpinReleaseSmoke']);
    }

    public function addMenu(): void
    {
        add_management_page(
            __('Quality Gate', 'wp-seo-next'),
            __('Quality Gate', 'wp-seo-next'),
            Capabilities::CAP_TOOLS,
            self::PAGE_SLUG,
            [$this, 'render']
        );
    }

    private function resultsKey(): string
    {
        $uid = function_exists('get_current_user_id') ? (int) get_current_user_id() : 0;
        return 'wpsn_quality_gate_' . $uid;
    }

    /**
     * @param array<string,string> $args
     */
    private function redirect(array $args = []): void
    {
        $url = admin_url('tools.php?page=' . self::PAGE_SLUG);
        if (!empty($args)) {
            $url = add_query_arg($args, $url);
        }
        wp_safe_redirect($url);
        exit;
    }

    public function handleRun(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'wp-seo-next'));
        }
        check_admin_referer('wpsn_quality_gate_run');

        $uid = function_exists('get_current_user_id') ? (int) get_current_user_id() : 0;

        $svc = new QualityGateService($this->container);
        $res = $svc->runAllChecks([
            'trigger' => 'manual',
            'user_id' => $uid,
        ]);
        set_transient($this->resultsKey(), $res, self::TTL);

        // Also keep a global (site-wide) copy so Tasks Hub + scheduled runs have a single shared state.
        $svc->saveGlobalResults($res);

        /**
         * Fires after a Quality Gate run is completed.
         *
         * @param array<string,mixed> $res
         * @param array<string,mixed> $context
         */
        do_action('wpsn/quality_gate/run_completed', $res, [
            'trigger' => 'manual',
            'user_id' => $uid,
        ]);

        $this->redirect(['wpsn_notice' => 'ran']);
    }

    public function handleClear(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'wp-seo-next'));
        }
        check_admin_referer('wpsn_quality_gate_clear');

        delete_transient($this->resultsKey());

        // Clear the global results as well (Tasks Hub / shared state).
        (new QualityGateService($this->container))->clearGlobalResults();

        $this->redirect(['wpsn_notice' => 'cleared']);
    }

    public function handleSaveSettings(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'wp-seo-next'));
        }
        check_admin_referer('wpsn_quality_gate_save_settings');

        $schedule = isset($_POST['qg_schedule']) ? sanitize_key((string) wp_unslash($_POST['qg_schedule'])) : 'off';
        if (!in_array($schedule, ['off', 'daily', 'weekly'], true)) {
            $schedule = 'off';
        }

        $hour = isset($_POST['qg_hour_utc']) ? (int) $_POST['qg_hour_utc'] : 4;
        $hour = max(0, min(23, $hour));

        $weekday = isset($_POST['qg_weekday_utc']) ? (int) $_POST['qg_weekday_utc'] : 1;
        $weekday = max(0, min(6, $weekday));

        $repo = new SettingsRepository();
        $all = $repo->getAll();

        $all['quality_gate'] = [
            'automation' => [
                'schedule' => $schedule,
                'hour_utc' => $hour,
                'weekday_utc' => $weekday,
            ],
        ];

        $repo->update($all);

        // Ensure in-request cache is flushed.
        $this->settings->flush();

        $this->redirect(['wpsn_notice' => 'settings_saved']);
    }

    public function handleRunQueueWorker(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'wp-seo-next'));
        }
        check_admin_referer('wpsn_quality_gate_run_queue');

        // Manual worker runs can easily trigger 503 on shared/limited hosting.
        // Keep each manual click tiny by default (one job).
        $max = isset($_GET['max']) ? (int) $_GET['max'] : 1;
        $max = max(1, min(50, $max));

        // Hard cap manual worker jobs to keep requests short.
        // Advanced users can override via wp-config.php:
        //   define('WPSN_MANUAL_WORKER_MAX_JOBS', 5);
        $cap = defined('WPSN_MANUAL_WORKER_MAX_JOBS') ? (int) WPSN_MANUAL_WORKER_MAX_JOBS : 1;
        $cap = max(1, min(50, $cap));
        if ($max > $cap) {
            $max = $cap;
        }

        /** @var \WPSN\Core\Queue\QueueRunner $runner */
        $runner = $this->container->get('queue_runner');
        $processed = 0;
        try {
            $processed = (int) $runner->run($max);
        } catch (\Throwable $e) {
            // Ignore; UI will show queue status anyway.
        }

        $this->redirect([
            'wpsn_notice' => 'queue_ran',
            'processed' => (string) $processed,
        ]);
    }

    public function handleRunReleaseSmoke(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'wp-seo-next'));
        }
        check_admin_referer('wpsn_release_smoke_run');

        $svc = new ReleaseSmokeMatrixService($this->container);
        $report = $svc->run([
            'trigger' => 'manual',
            'user_id' => function_exists('get_current_user_id') ? (int) get_current_user_id() : 0,
        ]);
        $svc->save($report);

        $this->redirect(['wpsn_notice' => 'release_ran']);
    }

    public function handleExportReleaseSmoke(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'wp-seo-next'));
        }
        check_admin_referer('wpsn_release_smoke_export');

        $runId = isset($_GET['run_id']) ? sanitize_text_field((string) wp_unslash($_GET['run_id'])) : '';

        $svc = new ReleaseSmokeMatrixService($this->container);
        $report = $runId !== '' ? $svc->getRun($runId) : $svc->getLast();
        if (empty($report)) {
            $this->redirect(['wpsn_notice' => 'release_missing']);
        }

        $ts = isset($report['created_at_utc']) ? (string) $report['created_at_utc'] : gmdate('c');
        $ts = preg_replace('/[^0-9T:-]/', '-', $ts);
        $ts = is_string($ts) ? trim($ts, '-') : gmdate('Y-m-d-His');

        nocache_headers();
        header('Content-Type: application/json; charset=utf-8');
        header('Content-Disposition: attachment; filename="wpsn-release-smoke-' . $ts . '.json"');
        echo (string) wp_json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        exit;
    }

    public function handleExportReleaseSmokeCompare(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'wp-seo-next'));
        }
        check_admin_referer('wpsn_release_smoke_compare_export');

        $baseId = isset($_GET['baseline_run']) ? sanitize_text_field((string) wp_unslash($_GET['baseline_run'])) : '';
        $candidateId = isset($_GET['candidate_run']) ? sanitize_text_field((string) wp_unslash($_GET['candidate_run'])) : '';
        if ($baseId === '' || $candidateId === '' || $baseId === $candidateId) {
            $this->redirect(['wpsn_notice' => 'release_compare_missing']);
        }

        $svc = new ReleaseSmokeMatrixService($this->container);
        $baseline = $svc->getRun($baseId);
        $candidate = $svc->getRun($candidateId);
        if (empty($baseline) || empty($candidate)) {
            $this->redirect(['wpsn_notice' => 'release_compare_missing']);
        }

        $diff = (new ReleaseSmokeDiffBuilder())->compare($baseline, $candidate);
        $baseTs = isset($baseline['created_at_utc']) ? (string) $baseline['created_at_utc'] : 'baseline';
        $candidateTs = isset($candidate['created_at_utc']) ? (string) $candidate['created_at_utc'] : 'candidate';
        $fileTs = preg_replace('/[^0-9T:-]/', '-', $baseTs . '-vs-' . $candidateTs);
        $fileTs = is_string($fileTs) ? trim($fileTs, '-') : gmdate('Y-m-d-His');

        nocache_headers();
        header('Content-Type: application/json; charset=utf-8');
        header('Content-Disposition: attachment; filename="wpsn-release-smoke-compare-' . $fileTs . '.json"');
        echo (string) wp_json_encode($diff, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        exit;
    }

    public function handleClearReleaseSmoke(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'wp-seo-next'));
        }
        check_admin_referer('wpsn_release_smoke_clear');

        $svc = new ReleaseSmokeMatrixService($this->container);
        $keptPinned = !empty($svc->getPinned());
        $svc->clear();
        $this->redirect(['wpsn_notice' => $keptPinned ? 'release_cleared_kept_baseline' : 'release_cleared']);
    }

    public function handlePinReleaseSmoke(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'wp-seo-next'));
        }

        $runId = isset($_GET['run_id']) ? sanitize_text_field((string) wp_unslash($_GET['run_id'])) : '';
        if ($runId === '') {
            $this->redirect(['wpsn_notice' => 'release_pin_missing']);
        }

        check_admin_referer('wpsn_release_smoke_pin_' . $runId);

        $svc = new ReleaseSmokeMatrixService($this->container);
        if (!$svc->pinRun($runId)) {
            $this->redirect(['wpsn_notice' => 'release_pin_missing']);
        }

        $this->redirect(['wpsn_notice' => 'release_pinned']);
    }

    public function handleUnpinReleaseSmoke(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'wp-seo-next'));
        }

        check_admin_referer('wpsn_release_smoke_unpin');

        (new ReleaseSmokeMatrixService($this->container))->unpin();
        $this->redirect(['wpsn_notice' => 'release_unpinned']);
    }

    /**
     * @return array<string,mixed>
     */
    private function runAllChecks(): array
    {
        $disableOutput = $this->disableOutputEffective();

        $checks = [
            'sitemap' => $this->checkSitemap($disableOutput),
            'robots' => $this->checkRobots($disableOutput),
            'head' => $this->checkHeadOutput($disableOutput),
            'queue' => $this->checkQueue(),
        ];

        // Overall: worst severity wins.
        $sevOrder = ['ok' => 0, 'info' => 1, 'warn' => 2, 'critical' => 3];
        $worst = 'ok';
        foreach ($checks as $c) {
            $s = is_array($c) && isset($c['severity']) ? (string) $c['severity'] : 'info';
            if (($sevOrder[$s] ?? 1) > ($sevOrder[$worst] ?? 0)) {
                $worst = $s;
            }
        }

        return [
            'created_at_utc' => gmdate('c'),
            'ts' => time(),
            'plugin_version' => (string) (defined('WPSN_VERSION') ? WPSN_VERSION : ''),
            'disable_output_effective' => $disableOutput,
            'checks' => $checks,
            'severity' => $worst,
        ];
    }

    private function disableOutputEffective(): bool
    {
        $auto = (bool) $this->settings->get('compatibility.auto_disable_output_on_conflict', false);
        $stored = (bool) $this->settings->get('compatibility.disable_output', false);
        $const = defined('WPSN_DISABLE_OUTPUT') && (bool) WPSN_DISABLE_OUTPUT;
        return $const || $stored || ($auto && CompatibilityDetector::hasSeoConflict());
    }

    /**
     * @return array{ok:bool,severity:string,summary:string,details:array<string,mixed>}
     */
    private function checkSitemap(bool $disableOutputEffective): array
    {
        $enabled = (bool) $this->settings->moduleEnabled('sitemap', true);

        $details = [
            'module_enabled' => $enabled,
            'rewrite_rules_present' => $this->sitemapRewriteRulesPresent(),
            'tests' => [],
        ];

        // IMPORTANT: Only test local URLs. Avoid SSRF.
        $tests = [
            ['label' => '/sitemap_index.xml', 'url' => home_url('/sitemap_index.xml')],
            ['label' => '/sitemap.xml', 'url' => home_url('/sitemap.xml')],
            ['label' => '/?wpsn_sitemap=index', 'url' => add_query_arg(['wpsn_sitemap' => 'index'], home_url('/'))],
        ];

        $primaryOk = false;
        $fallbackOk = false;

        foreach ($tests as $t) {
            $probe = $this->fetchProbe((string) $t['label'], (string) $t['url'], 'application/xml,text/xml;q=0.9,*/*;q=0.8');
            $details['tests'][] = $probe;
            if (!empty($probe['ok']) && (string) $t['label'] === '/sitemap_index.xml') {
                $primaryOk = true;
            }
            if (!empty($probe['ok']) && (string) $t['label'] === '/?wpsn_sitemap=index') {
                $fallbackOk = true;
            }
        }

        if (!$enabled) {
            return [
                'ok' => false,
                'severity' => 'warn',
                'summary' => __('Sitemap module is disabled.', 'wp-seo-next'),
                'details' => $details,
            ];
        }

        if ($disableOutputEffective) {
            return [
                'ok' => false,
                'severity' => 'warn',
                'summary' => __('Output is disabled (Compatibility mode). Sitemap endpoints may not be served by WP SEO Next.', 'wp-seo-next'),
                'details' => $details,
            ];
        }

        if ($primaryOk) {
            return [
                'ok' => true,
                'severity' => 'ok',
                'summary' => __('Sitemap is reachable.', 'wp-seo-next'),
                'details' => $details,
            ];
        }

        if ($fallbackOk) {
            return [
                'ok' => false,
                'severity' => 'warn',
                'summary' => __('Sitemap works via fallback, but /sitemap_index.xml is not reachable. Likely rewrite rules / cache.', 'wp-seo-next'),
                'details' => $details,
            ];
        }

        return [
            'ok' => false,
            'severity' => 'critical',
            'summary' => __('Sitemap is not reachable (loopback).', 'wp-seo-next'),
            'details' => $details,
        ];
    }

    /**
     * @return array{ok:bool,severity:string,summary:string,details:array<string,mixed>}
     */
    private function checkRobots(bool $disableOutputEffective): array
    {
        $enabled = (bool) $this->settings->moduleEnabled('robots', true);
        $appendSitemap = (bool) $this->settings->get('robots_txt.append_sitemap', true);

        $probe = $this->fetchProbe('/robots.txt', home_url('/robots.txt'), 'text/plain,*/*;q=0.8');

        $details = [
            'module_enabled' => $enabled,
            'append_sitemap' => $appendSitemap,
            'probe' => $probe,
            'has_user_agent' => false,
            'has_sitemap' => false,
        ];

        $body = isset($probe['body_raw']) ? (string) $probe['body_raw'] : '';
        $ua = stripos($body, 'User-agent:') !== false;
        $sm = stripos($body, 'Sitemap:') !== false;
        $details['has_user_agent'] = $ua;
        $details['has_sitemap'] = $sm;

        if (empty($probe['ok'])) {
            return [
                'ok' => false,
                'severity' => 'critical',
                'summary' => __('robots.txt is not reachable (loopback).', 'wp-seo-next'),
                'details' => $details,
            ];
        }

        if (!$ua) {
            return [
                'ok' => false,
                'severity' => 'warn',
                'summary' => __('robots.txt is reachable, but missing "User-agent" rules.', 'wp-seo-next'),
                'details' => $details,
            ];
        }

        if ($disableOutputEffective || !$enabled) {
            return [
                'ok' => true,
                'severity' => 'warn',
                'summary' => __('robots.txt is reachable, but WP SEO Next output is disabled. Custom rules may not be appended.', 'wp-seo-next'),
                'details' => $details,
            ];
        }

        if ($appendSitemap && !$sm) {
            return [
                'ok' => true,
                'severity' => 'warn',
                'summary' => __('robots.txt is reachable, but Sitemap directive is missing.', 'wp-seo-next'),
                'details' => $details,
            ];
        }

        return [
            'ok' => true,
            'severity' => 'ok',
            'summary' => __('robots.txt is reachable.', 'wp-seo-next'),
            'details' => $details,
        ];
    }

    /**
     * @return array{ok:bool,severity:string,summary:string,details:array<string,mixed>}
     */
    private function checkHeadOutput(bool $disableOutputEffective): array
    {
        $svc = new HeadSnapshotService($this->container);
        $cases = $svc->buildDefaultCases();
        $payload = $svc->buildSnapshotPayload($cases);
        $siteHost = wp_parse_url(home_url('/'), PHP_URL_HOST);
        $siteHost = is_string($siteHost) ? $siteHost : '';

        $issues = [];
        $caseSummaries = [];
        $severity = 'ok';
        $ok = true;

        $payloadCases = isset($payload['cases']) && is_array($payload['cases']) ? $payload['cases'] : [];
        foreach ($payloadCases as $c) {
            if (!is_array($c)) {
                continue;
            }
            $label = isset($c['label']) ? (string) $c['label'] : '';
            $ctx = isset($c['context']) && is_array($c['context']) ? $c['context'] : [];
            $url = isset($ctx['url']) ? (string) $ctx['url'] : '';

            $struct = isset($c['head_struct']) && is_array($c['head_struct']) ? $c['head_struct'] : [];
            $title = isset($struct['title']) ? (string) $struct['title'] : '';
            $link = isset($struct['link']) && is_array($struct['link']) ? $struct['link'] : [];
            $meta = isset($struct['meta']) && is_array($struct['meta']) ? $struct['meta'] : [];

            $canon = '';
            if (isset($link['rel:canonical'])) {
                if (is_string($link['rel:canonical'])) {
                    $canon = $link['rel:canonical'];
                } elseif (is_array($link['rel:canonical']) && !empty($link['rel:canonical'][0])) {
                    $canon = (string) $link['rel:canonical'][0];
                }
            }

            $caseProblems = [];

            $missingTitle = trim($title) === '';
            $missingCanon = trim($canon) === '';
            $canonHostMismatch = false;

            if ($missingTitle) {
                $caseProblems[] = __('Missing <title>.', 'wp-seo-next');
            }

            if ($missingCanon) {
                $caseProblems[] = __('Missing canonical.', 'wp-seo-next');
            } else {
                $canonHost = wp_parse_url($canon, PHP_URL_HOST);
                $canonHost = is_string($canonHost) ? $canonHost : '';
                if ($siteHost !== '' && $canonHost !== '' && strtolower($canonHost) !== strtolower($siteHost)) {
                    $canonHostMismatch = true;
                    $caseProblems[] = sprintf(__('Canonical host mismatch: %s', 'wp-seo-next'), $canonHost);
                }
            }

            $robots = '';
            if (isset($meta['name:robots'])) {
                $rv = $meta['name:robots'];
                if (is_string($rv)) {
                    $robots = $rv;
                } elseif (is_array($rv) && !empty($rv[0])) {
                    $robots = (string) $rv[0];
                }
            }
            $missingRobots = $robots === '';
            $robotsNoindex = (!$missingRobots && stripos($robots, 'noindex') !== false);

            if ($missingRobots) {
                $caseProblems[] = __('No meta robots tag.', 'wp-seo-next');
            } elseif ($robotsNoindex) {
                $caseProblems[] = sprintf(__('Robots contains noindex (%s).', 'wp-seo-next'), $robots);
            }

            $caseSummaries[] = [
                'label' => $label !== '' ? $label : $url,
                'url' => $url,
                'title' => $title,
                'canonical' => $canon,
                'robots' => $robots,
                'problems' => $caseProblems,
            ];

            if (!empty($caseProblems)) {
                $ok = false;

                $caseSev = 'warn';
                if ($missingTitle || $missingCanon || $canonHostMismatch) {
                    $caseSev = 'critical';
                }

                if ($caseSev === 'critical') {
                    $severity = 'critical';
                } elseif ($severity !== 'critical') {
                    $severity = 'warn';
                }

                foreach ($caseProblems as $p) {
                    $p = (string) $p;
                    $issues[] = ($label !== '' ? $label : $url) . ': ' . $p;
                }
            }
        }

        $details = [
            'disable_output_effective' => $disableOutputEffective,
            'cases' => $caseSummaries,
            'issues' => $issues,
        ];

        if ($disableOutputEffective) {
            return [
                'ok' => false,
                'severity' => 'warn',
                'summary' => __('Head output is currently disabled (Compatibility mode).', 'wp-seo-next'),
                'details' => $details,
            ];
        }

        if ($ok) {
            return [
                'ok' => true,
                'severity' => 'ok',
                'summary' => __('Head output looks sane.', 'wp-seo-next'),
                'details' => $details,
            ];
        }

        return [
            'ok' => false,
            'severity' => $severity,
            'summary' => $severity === 'critical'
                ? __('Head output has critical issues.', 'wp-seo-next')
                : __('Head output has warnings.', 'wp-seo-next'),
            'details' => $details,
        ];
    }

    /**
     * @return array{ok:bool,severity:string,summary:string,details:array<string,mixed>}
     */
    private function checkQueue(): array
    {
        /** @var \WPSN\Core\Queue\QueueInterface $queue */
        $queue = $this->container->get('queue');

        $mode = (string) $this->settings->get('performance.queue.mode', 'manual');
        $counts = [];
        try {
            $counts = $queue->counts();
        } catch (\Throwable $e) {
            $counts = ['pending' => 0, 'running' => 0, 'done' => 0, 'failed' => 0];
        }

        $pending = (int) ($counts['pending'] ?? 0);
        $failed = (int) ($counts['failed'] ?? 0);
        $nextTick = wp_next_scheduled('wpsn_queue_tick');
        $wpCronDisabled = defined('DISABLE_WP_CRON') && DISABLE_WP_CRON;

        $paused = QueuePause::isPaused();
        $pauseUntil = QueuePause::pauseUntil();
        $pauseReason = QueuePause::reason();

        $details = [
            'mode' => $mode,
            'paused' => $paused,
            'paused_until_utc' => $paused && $pauseUntil > 0 ? gmdate('c', (int) $pauseUntil) : null,
            'pause_reason' => $pauseReason !== '' ? $pauseReason : null,
            'counts' => $counts,
            'next_tick_utc' => $nextTick ? gmdate('c', (int) $nextTick) : null,
            'wp_cron_disabled' => $wpCronDisabled,
        ];

        if ($paused) {
            if ($pending > 0) {
                return [
                    'ok' => false,
                    'severity' => 'warn',
                    'summary' => sprintf(__('Queue is paused with pending jobs: %d', 'wp-seo-next'), $pending),
                    'details' => $details,
                ];
            }

            return [
                'ok' => true,
                'severity' => 'info',
                'summary' => __('Queue is paused (no pending jobs).', 'wp-seo-next'),
                'details' => $details,
            ];
        }

        if ($failed > 0) {
            return [
                'ok' => false,
                'severity' => $failed >= 20 ? 'critical' : 'warn',
                'summary' => sprintf(__('Queue has failed jobs: %d', 'wp-seo-next'), $failed),
                'details' => $details,
            ];
        }

        if ($pending > 0 && $mode === 'manual') {
            return [
                'ok' => false,
                'severity' => 'warn',
                'summary' => sprintf(__('Queue has pending jobs (%d) but mode is Manual.', 'wp-seo-next'), $pending),
                'details' => $details,
            ];
        }

        if ($mode === 'auto') {
            if ($wpCronDisabled) {
                return [
                    'ok' => false,
                    'severity' => 'warn',
                    'summary' => __('Queue is Auto, but DISABLE_WP_CRON is enabled. Ensure a real cron hits wp-cron.php.', 'wp-seo-next'),
                    'details' => $details,
                ];
            }
            if (!$nextTick) {
                return [
                    'ok' => false,
                    'severity' => 'warn',
                    'summary' => __('Queue is Auto, but no next tick is scheduled.', 'wp-seo-next'),
                    'details' => $details,
                ];
            }
        }

        return [
            'ok' => true,
            'severity' => 'ok',
            'summary' => __('Queue looks healthy.', 'wp-seo-next'),
            'details' => $details,
        ];
    }

    /**
     * @return array<string,mixed>
     */
    private function fetchProbe(string $label, string $url, string $accept): array
    {
        $resp = HttpClient::get($url, [
            'timeout' => 8,
            'redirection' => 2,
            'headers' => [
                'Accept' => $accept,
                'X-WPSN-Debug' => 'quality_gate',
            ],
            'user-agent' => 'WPSN/' . (defined('WPSN_VERSION') ? WPSN_VERSION : 'dev') . '; ' . home_url('/'),
        ], [
            'service' => 'loopback',
            'context' => 'quality_gate_probe',
            'retries' => 0,
        ]);

        if (is_wp_error($resp)) {
            return [
                'label' => $label,
                'url' => $url,
                'ok' => false,
                'code' => 0,
                'content_type' => '',
                'len' => 0,
                'snip' => '',
                'error' => $resp->get_error_message(),
                'body_raw' => '',
            ];
        }

        $code = (int) wp_remote_retrieve_response_code($resp);
        $headers = wp_remote_retrieve_headers($resp);
        $ct = '';
        if (is_array($headers) && isset($headers['content-type'])) {
            $ct = (string) $headers['content-type'];
        } elseif (is_object($headers) && method_exists($headers, 'get')) {
            $ct = (string) $headers->get('content-type');
        }

        $body = (string) wp_remote_retrieve_body($resp);
        $len = strlen($body);
        $snip = substr($body, 0, 220);
        $snip = preg_replace('/\s+/', ' ', (string) $snip);

        $ok = $code >= 200 && $code < 300;

        return [
            'label' => $label,
            'url' => $url,
            'ok' => $ok,
            'code' => $code,
            'content_type' => $ct,
            'len' => $len,
            'snip' => $snip,
            'error' => '',
            'body_raw' => substr($body, 0, 4000),
        ];
    }

    private function sitemapRewriteRulesPresent(): bool
    {
        $rules = get_option('rewrite_rules');
        if (!is_array($rules) || $rules === []) {
            return false;
        }

        $needles = [
            '^sitemap\\.xml$',
            '^sitemap_index\\.xml$',
            '^([a-z0-9_-]+)-sitemap\\.xml$',
            '^([a-z0-9_-]+)-sitemap([0-9]+)\\.xml$',
        ];
        foreach ($needles as $n) {
            if (array_key_exists($n, $rules)) {
                return true;
            }
        }

        foreach ($rules as $regex => $_q) {
            $regex = (string) $regex;
            if (strpos($regex, 'sitemap\\.xml') !== false || strpos($regex, 'sitemap_index\\.xml') !== false) {
                return true;
            }
        }

        return false;
    }

    private function statusPill(string $severity): string
    {
        $severity = sanitize_key($severity);
        $cls = 'info';
        $label = strtoupper($severity);

        if ($severity === 'critical' || $severity === 'crit' || $severity === 'error' || $severity === 'bad') {
            $cls = 'bad';
            $label = 'CRITICAL';
        } elseif ($severity === 'warn' || $severity === 'warning') {
            $cls = 'warn';
            $label = 'WARN';
        } elseif ($severity === 'ok' || $severity === 'success') {
            $cls = 'ok';
            $label = 'OK';
        } elseif ($severity === 'info') {
            $cls = 'info';
            $label = 'INFO';
        }

        return '<span class="wpsn-pill ' . esc_attr($cls) . '">' . esc_html($label) . '</span>';
    }

    public function render(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to access this page.', 'wp-seo-next'));
        }

        $notice = isset($_GET['wpsn_notice']) ? sanitize_key((string) wp_unslash($_GET['wpsn_notice'])) : '';
        $processed = isset($_GET['processed']) ? (int) $_GET['processed'] : 0;

        $fromGlobal = false;
        $releaseSvc = new ReleaseSmokeMatrixService($this->container);
        $releaseLast = $releaseSvc->getLast();
        $releaseHistory = $releaseSvc->getHistory(10);
        $releasePinned = $releaseSvc->getPinned();
        $releasePinnedId = !empty($releasePinned['id']) ? (string) $releasePinned['id'] : '';
        $releaseCompareRuns = $this->releaseCompareRuns($releaseHistory, $releasePinned);
        $releaseCompareBaseId = isset($_GET['release_compare_base']) ? sanitize_text_field((string) wp_unslash($_GET['release_compare_base'])) : '';
        $releaseCompareCandidateId = isset($_GET['release_compare_candidate']) ? sanitize_text_field((string) wp_unslash($_GET['release_compare_candidate'])) : '';
        if ($releaseCompareCandidateId === '' && !empty($releaseHistory)) {
            foreach ($releaseHistory as $row) {
                if (!is_array($row) || empty($row['id'])) {
                    continue;
                }
                $candidateId = (string) $row['id'];
                if ($candidateId !== $releasePinnedId || $releasePinnedId === '') {
                    $releaseCompareCandidateId = $candidateId;
                    break;
                }
            }
        }
        if ($releaseCompareCandidateId === '' && !empty($releasePinnedId)) {
            $releaseCompareCandidateId = $releasePinnedId;
        }
        if ($releaseCompareBaseId === '') {
            if ($releasePinnedId !== '' && $releasePinnedId !== $releaseCompareCandidateId) {
                $releaseCompareBaseId = $releasePinnedId;
            } else {
                foreach ($releaseCompareRuns as $row) {
                    if (!is_array($row) || empty($row['id'])) {
                        continue;
                    }
                    $rowId = (string) $row['id'];
                    if ($rowId !== $releaseCompareCandidateId) {
                        $releaseCompareBaseId = $rowId;
                        break;
                    }
                }
            }
        }
        if ($releaseCompareBaseId === $releaseCompareCandidateId) {
            foreach ($releaseCompareRuns as $row) {
                if (!is_array($row) || empty($row['id'])) {
                    continue;
                }
                $rowId = (string) $row['id'];
                if ($rowId !== $releaseCompareCandidateId) {
                    $releaseCompareBaseId = $rowId;
                    break;
                }
            }
        }
        $releaseCompareBaseline = $releaseCompareBaseId !== '' ? $releaseSvc->getRun($releaseCompareBaseId) : [];
        $releaseCompareCandidate = $releaseCompareCandidateId !== '' ? $releaseSvc->getRun($releaseCompareCandidateId) : [];
        $releaseCompareDiff = [];
        if (!empty($releaseCompareBaseline) && !empty($releaseCompareCandidate) && $releaseCompareBaseId !== '' && $releaseCompareCandidateId !== '' && $releaseCompareBaseId !== $releaseCompareCandidateId) {
            $releaseCompareDiff = (new ReleaseSmokeDiffBuilder())->compare($releaseCompareBaseline, $releaseCompareCandidate);
        }

        $releaseCompactSummary = (new ReleaseSmokeCompactSummaryBuilder())->buildFromRuns($releaseLast, $releaseHistory, $releasePinned);

        $res = get_transient($this->resultsKey());
        $res = is_array($res) ? $res : [];

        if (empty($res)) {
            $global = (new QualityGateService($this->container))->getGlobalResults();
            if (is_array($global) && !empty($global)) {
                $res = $global;
                $fromGlobal = true;
            }
        }

        echo '<div class="wrap">';
        echo '<h1>' . esc_html__('Quality Gate', 'wp-seo-next') . '</h1>';
        echo '<p class="description" style="max-width:980px;">' . esc_html__('A quick health check for the most failure-prone SEO surfaces: sitemap, robots, head output, and background queue.', 'wp-seo-next') . '</p>';

        echo '<style>
            .wpsn-pill{display:inline-block;padding:3px 10px;border-radius:999px;background:#f0f0f1;margin-right:6px;font-size:12px;line-height:1.2;}
            .wpsn-pill.ok{background:#d1e7dd;}
            .wpsn-pill.warn{background:#fff3cd;}
            .wpsn-pill.bad{background:#f8d7da;}
            .wpsn-pill.info{background:#cff4fc;}
            .wpsn-card{background:#fff;border:1px solid #dcdcde;border-radius:6px;padding:12px;}
            .wpsn-actions .button{margin:0 6px 6px 0;}
            .wpsn-compare-pill{display:inline-block;padding:3px 10px;border-radius:999px;background:#f0f0f1;margin-right:6px;font-size:12px;line-height:1.2;}
            .wpsn-compare-pill.improved{background:#d1e7dd;}
            .wpsn-compare-pill.regressed{background:#f8d7da;}
            .wpsn-compare-pill.changed{background:#fff3cd;}
            .wpsn-compare-pill.unchanged{background:#e2e3e5;}
            .wpsn-compare-pill.added,.wpsn-compare-pill.removed{background:#cff4fc;}
            .wpsn-release-badges{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0 0 0;}
            .wpsn-release-badge{display:inline-block;padding:3px 10px;border-radius:999px;background:#f0f0f1;font-size:12px;line-height:1.2;}
            .wpsn-release-badge.ok{background:#d1e7dd;}
            .wpsn-release-badge.warn{background:#fff3cd;}
            .wpsn-release-badge.bad{background:#f8d7da;}
            .wpsn-release-badge.info{background:#cff4fc;}
        </style>';

        if ($notice === 'ran') {
            echo '<div class="notice notice-success"><p>' . esc_html__('Quality Gate checks completed.', 'wp-seo-next') . '</p></div>';
        } elseif ($notice === 'cleared') {
            echo '<div class="notice notice-info"><p>' . esc_html__('Quality Gate results cleared.', 'wp-seo-next') . '</p></div>';
        } elseif ($notice === 'settings_saved') {
            echo '<div class="notice notice-success"><p>' . esc_html__('Quality Gate automation settings saved.', 'wp-seo-next') . '</p></div>';
        } elseif ($notice === 'queue_ran') {
            echo '<div class="notice notice-success"><p>' . esc_html(sprintf(__('Queue worker ran (%d job(s) processed).', 'wp-seo-next'), $processed)) . '</p></div>';
        } elseif ($notice === 'release_ran') {
            echo '<div class="notice notice-success"><p>' . esc_html__('Release smoke matrix completed.', 'wp-seo-next') . '</p></div>';
        } elseif ($notice === 'release_cleared') {
            echo '<div class="notice notice-info"><p>' . esc_html__('Release smoke history cleared.', 'wp-seo-next') . '</p></div>';
        } elseif ($notice === 'release_cleared_kept_baseline') {
            echo '<div class="notice notice-info"><p>' . esc_html__('Release smoke history cleared. The pinned release baseline was kept.', 'wp-seo-next') . '</p></div>';
        } elseif ($notice === 'release_missing') {
            echo '<div class="notice notice-warning"><p>' . esc_html__('No saved release smoke report was found to export.', 'wp-seo-next') . '</p></div>';
        } elseif ($notice === 'release_compare_missing') {
            echo '<div class="notice notice-warning"><p>' . esc_html__('Pick two different saved release smoke runs to compare.', 'wp-seo-next') . '</p></div>';
        } elseif ($notice === 'release_pinned') {
            echo '<div class="notice notice-success"><p>' . esc_html__('Release smoke baseline pinned.', 'wp-seo-next') . '</p></div>';
        } elseif ($notice === 'release_unpinned') {
            echo '<div class="notice notice-info"><p>' . esc_html__('Pinned release smoke baseline removed.', 'wp-seo-next') . '</p></div>';
        } elseif ($notice === 'release_pin_missing') {
            echo '<div class="notice notice-warning"><p>' . esc_html__('Could not find the selected release smoke run to pin.', 'wp-seo-next') . '</p></div>';
        }

        echo '<div class="wpsn-actions" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center; margin:10px 0 14px 0;">';
        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:inline;">';
        echo '<input type="hidden" name="action" value="wpsn_quality_gate_run" />';
        wp_nonce_field('wpsn_quality_gate_run');
        submit_button(__('Run all checks now', 'wp-seo-next'), 'primary', 'submit', false);
        echo '</form>';

        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:inline;">';
        echo '<input type="hidden" name="action" value="wpsn_quality_gate_clear" />';
        wp_nonce_field('wpsn_quality_gate_clear');
        submit_button(__('Clear results', 'wp-seo-next'), 'secondary', 'submit', false);
        echo '</form>';

        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:inline;">';
        echo '<input type="hidden" name="action" value="wpsn_release_smoke_run" />';
        wp_nonce_field('wpsn_release_smoke_run');
        submit_button(__('Run release matrix', 'wp-seo-next'), 'secondary', 'submit', false);
        echo '</form>';

        $releaseExportUrl = wp_nonce_url(admin_url('admin-post.php?action=wpsn_release_smoke_export'), 'wpsn_release_smoke_export');
        echo '<a class="button button-secondary" href="' . esc_url($releaseExportUrl) . '">' . esc_html__('Download release JSON', 'wp-seo-next') . '</a>';

        echo '<a class="button button-secondary" href="' . esc_url(admin_url('tools.php?page=wpsn-tasks&tab=quality')) . '">' . esc_html__('Open related tasks', 'wp-seo-next') . '</a>';
        echo '</div>';

        $releaseCompactCompareUrl = '';
        if (!empty($releaseCompactSummary['has_comparison']) && !empty($releaseCompactSummary['baseline']['id']) && !empty($releaseCompactSummary['candidate']['id'])) {
            $releaseCompactCompareUrl = $this->releaseComparePageUrl((string) $releaseCompactSummary['baseline']['id'], (string) $releaseCompactSummary['candidate']['id']);
        }
        $this->renderReleaseCompactOverviewCard($releaseCompactSummary, $releaseCompactCompareUrl, $releaseExportUrl);

        if (empty($res)) {
            echo '<div class="notice notice-warning"><p>' . esc_html__('No recent results yet. Click “Run all checks now”.', 'wp-seo-next') . '</p></div>';
        } elseif ($fromGlobal) {
            echo '<div class="notice notice-info"><p>' . esc_html__('Showing last site-wide results (global). Run checks manually to store a per-user snapshot.', 'wp-seo-next') . '</p></div>';
        }

        $createdAt = isset($res['created_at_utc']) ? (string) $res['created_at_utc'] : '';
        if ($createdAt !== '') {
            echo '<p class="description">' . esc_html__('Last run (UTC):', 'wp-seo-next') . ' <code>' . esc_html($createdAt) . '</code></p>';
        }

        // Automation settings.
        $qgSchedule = (string) $this->settings->get('quality_gate.automation.schedule', 'off');
        $qgHour = (int) $this->settings->get('quality_gate.automation.hour_utc', 4);
        $qgHour = max(0, min(23, $qgHour));
        $qgWeekday = (int) $this->settings->get('quality_gate.automation.weekday_utc', 1);
        $qgWeekday = max(0, min(6, $qgWeekday));

        $nextTick = wp_next_scheduled(\WPSN\Modules\Diagnostics\QualityGateAutomation::CRON_TICK);
        $nextTickUtc = $nextTick ? gmdate('c', (int) $nextTick) : '';
        $wpCronDisabled = defined('DISABLE_WP_CRON') && DISABLE_WP_CRON;

        echo '<div class="wpsn-card" style="max-width:1200px; margin:10px 0 14px 0;">';
        echo '<h2 style="margin:0 0 10px 0;">' . esc_html__('Automation', 'wp-seo-next') . '</h2>';
        echo '<p class="description" style="margin:0 0 10px 0;">' . esc_html__('Optional scheduled Quality Gate runs (uses WP-Cron hourly tick).', 'wp-seo-next') . '</p>';
        if ($wpCronDisabled) {
            echo '<p class="description" style="margin:0 0 10px 0;"><strong>' . esc_html__('Warning:', 'wp-seo-next') . '</strong> ' . esc_html__('DISABLE_WP_CRON is enabled. Scheduled runs require a real cron hitting wp-cron.php.', 'wp-seo-next') . '</p>';
        }
        if ($nextTickUtc !== '') {
            echo '<p class="description" style="margin:0 0 10px 0;">' . esc_html__('Next tick (UTC):', 'wp-seo-next') . ' <code>' . esc_html($nextTickUtc) . '</code></p>';
        }

        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
        echo '<input type="hidden" name="action" value="wpsn_quality_gate_save_settings" />';
        wp_nonce_field('wpsn_quality_gate_save_settings');

        echo '<label style="margin-right:12px;">' . esc_html__('Schedule:', 'wp-seo-next') . ' ';
        echo '<select name="qg_schedule">';
        foreach (['off' => __('Off', 'wp-seo-next'), 'daily' => __('Daily', 'wp-seo-next'), 'weekly' => __('Weekly', 'wp-seo-next')] as $k => $lbl) {
            echo '<option value="' . esc_attr($k) . '" ' . selected($qgSchedule, $k, false) . '>' . esc_html($lbl) . '</option>';
        }
        echo '</select></label>';

        echo '<label style="margin-right:12px;">' . esc_html__('Hour (UTC):', 'wp-seo-next') . ' ';
        echo '<input type="number" min="0" max="23" name="qg_hour_utc" value="' . esc_attr((string) $qgHour) . '" style="width:80px;" />';
        echo '</label>';

        echo '<label style="margin-right:12px;">' . esc_html__('Weekday (UTC):', 'wp-seo-next') . ' ';
        echo '<select name="qg_weekday_utc">';
        $wd = [0 => __('Sun', 'wp-seo-next'), 1 => __('Mon', 'wp-seo-next'), 2 => __('Tue', 'wp-seo-next'), 3 => __('Wed', 'wp-seo-next'), 4 => __('Thu', 'wp-seo-next'), 5 => __('Fri', 'wp-seo-next'), 6 => __('Sat', 'wp-seo-next')];
        foreach ($wd as $k => $lbl) {
            echo '<option value="' . esc_attr((string) $k) . '" ' . selected($qgWeekday, (int) $k, false) . '>' . esc_html($lbl) . '</option>';
        }
        echo '</select></label>';

        submit_button(__('Save', 'wp-seo-next'), 'secondary', 'submit', false);
        echo ' <a class="button button-secondary" href="' . esc_url(admin_url('tools.php?page=wpsn-notifications')) . '">' . esc_html__('Notifications Center', 'wp-seo-next') . '</a>';
        echo '</form>';
        echo '</div>';

        echo '<div class="wpsn-card" id="wpsn-release-smoke" style="max-width:1200px; margin:16px 0 14px 0;">';
        echo '<div style="display:flex;justify-content:space-between;gap:10px;align-items:flex-start;flex-wrap:wrap;">';
        echo '<div>';
        echo '<h2 style="margin:0 0 6px 0;">' . esc_html__('Release smoke matrix', 'wp-seo-next') . '</h2>';
        echo '<p class="description" style="margin:0;max-width:900px;">' . esc_html__('Release-focused regression matrix: frontend cases (home/single/term/product), robots.txt, sitemap index, queue/bulk/undo, revisions rollback, import/export rollback, and Admin Lists Quick Edit readiness.', 'wp-seo-next') . '</p>';
        echo '</div>';
        echo '<div class="wpsn-actions">';
        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:inline;">';
        echo '<input type="hidden" name="action" value="wpsn_release_smoke_run" />';
        wp_nonce_field('wpsn_release_smoke_run');
        submit_button(__('Run now', 'wp-seo-next'), 'secondary', 'submit', false);
        echo '</form> ';
        echo '<a class="button button-secondary" href="' . esc_url($releaseExportUrl) . '">' . esc_html__('Export latest JSON', 'wp-seo-next') . '</a> ';
        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:inline;">';
        echo '<input type="hidden" name="action" value="wpsn_release_smoke_clear" />';
        wp_nonce_field('wpsn_release_smoke_clear');
        submit_button(__('Clear history', 'wp-seo-next'), 'secondary', 'submit', false, ['onclick' => "return confirm('" . esc_js(__('Clear saved release smoke reports?', 'wp-seo-next')) . "');"]);
        echo '</form>';
        echo '</div>';
        echo '</div>';

        if (!empty($releasePinned)) {
            $pinnedCreated = isset($releasePinned['created_at_utc']) ? (string) $releasePinned['created_at_utc'] : '';
            $pinnedSev = isset($releasePinned['severity']) ? (string) $releasePinned['severity'] : 'info';
            $pinnedCounts = isset($releasePinned['counts']) && is_array($releasePinned['counts']) ? $releasePinned['counts'] : [];
            $pinnedExportUrl = $this->releaseExportUrl((string) ($releasePinned['id'] ?? ''));
            $comparePinnedToLatestUrl = ($releasePinnedId !== '' && !empty($releaseLast['id']) && (string) $releaseLast['id'] !== $releasePinnedId)
                ? $this->releaseComparePageUrl($releasePinnedId, (string) $releaseLast['id'])
                : '';
            $unpinUrl = $this->releaseUnpinUrl();

            echo '<div style="margin:12px 0 10px 0;padding:10px;border:1px solid #bfdbfe;background:#eff6ff;border-radius:4px;">';
            echo '<p style="margin:0 0 6px 0;">' . $this->releaseComparePill('unchanged') . ' <strong>' . esc_html__('Pinned release baseline (UTC):', 'wp-seo-next') . '</strong> <code>' . esc_html($pinnedCreated) . '</code></p>';
            echo '<p class="description" style="margin:0;">' . esc_html(sprintf(__('Severity %1$s · OK %2$d · Info %3$d · Warn %4$d · Critical %5$d', 'wp-seo-next'), strtoupper($pinnedSev), (int) ($pinnedCounts['ok'] ?? 0), (int) ($pinnedCounts['info'] ?? 0), (int) ($pinnedCounts['warn'] ?? 0), (int) ($pinnedCounts['critical'] ?? 0))) . '</p>';
            echo '<p style="margin:8px 0 0 0;">';
            if ($pinnedExportUrl !== '') {
                echo '<a class="button button-small" href="' . esc_url($pinnedExportUrl) . '">' . esc_html__('Export pinned JSON', 'wp-seo-next') . '</a> ';
            }
            if ($comparePinnedToLatestUrl !== '') {
                echo '<a class="button button-small" href="' . esc_url($comparePinnedToLatestUrl) . '">' . esc_html__('Compare pinned vs latest', 'wp-seo-next') . '</a> ';
            }
            if ($unpinUrl !== '') {
                echo '<a class="button button-small" href="' . esc_url($unpinUrl) . '">' . esc_html__('Unpin baseline', 'wp-seo-next') . '</a>';
            }
            echo '</p>';
            echo '</div>';
        }

        if (empty($releaseLast)) {
            echo '<p style="margin:12px 0 0 0;">' . esc_html__('No release smoke report saved yet. Run the matrix to capture a release-ready regression snapshot.', 'wp-seo-next') . '</p>';
        } else {
            $releaseCreated = isset($releaseLast['created_at_utc']) ? (string) $releaseLast['created_at_utc'] : '';
            $releaseSev = isset($releaseLast['severity']) ? (string) $releaseLast['severity'] : 'info';
            $releaseCounts = isset($releaseLast['counts']) && is_array($releaseLast['counts']) ? $releaseLast['counts'] : [];
            echo '<p style="margin:12px 0 10px 0;">' . $this->statusPill($releaseSev) . ' <strong>' . esc_html__('Last release run (UTC):', 'wp-seo-next') . '</strong> <code>' . esc_html($releaseCreated) . '</code>';
            echo ' · ' . esc_html(sprintf(__('OK %1$d · Info %2$d · Warn %3$d · Critical %4$d', 'wp-seo-next'), (int) ($releaseCounts['ok'] ?? 0), (int) ($releaseCounts['info'] ?? 0), (int) ($releaseCounts['warn'] ?? 0), (int) ($releaseCounts['critical'] ?? 0)));
            if ($releasePinnedId !== '' && (string) ($releaseLast['id'] ?? '') === $releasePinnedId) {
                echo ' · ' . wp_kses_post($this->releaseComparePill('unchanged')) . ' ' . esc_html__('Pinned baseline', 'wp-seo-next');
            }
            echo '</p>';

            $releaseChecks = isset($releaseLast['checks']) && is_array($releaseLast['checks']) ? $releaseLast['checks'] : [];
            if (!empty($releaseChecks)) {
                echo '<table class="widefat striped" style="margin-top:8px;">';
                echo '<thead><tr><th style="width:220px;">' . esc_html__('Check', 'wp-seo-next') . '</th><th style="width:120px;">' . esc_html__('Status', 'wp-seo-next') . '</th><th>' . esc_html__('Summary', 'wp-seo-next') . '</th></tr></thead><tbody>';
                foreach ($releaseChecks as $key => $check) {
                    if (!is_array($check)) {
                        continue;
                    }
                    $labelMap = [
                        'frontend_matrix' => __('Frontend matrix', 'wp-seo-next'),
                        'robots_txt' => __('robots.txt', 'wp-seo-next'),
                        'sitemap_index' => __('Sitemap index', 'wp-seo-next'),
                        'queue_bulk_undo' => __('Queue / Bulk / Undo', 'wp-seo-next'),
                        'revisions_rollback' => __('Revisions rollback', 'wp-seo-next'),
                        'import_export_rollback' => __('Import/Export rollback', 'wp-seo-next'),
                        'admin_lists_quick_edit' => __('Admin Lists Quick Edit', 'wp-seo-next'),
                    ];
                    $label = isset($labelMap[$key]) ? $labelMap[$key] : (string) $key;
                    $sev = isset($check['severity']) ? (string) $check['severity'] : 'info';
                    $sum = isset($check['summary']) ? (string) $check['summary'] : '';
                    echo '<tr>';
                    echo '<td><strong>' . esc_html($label) . '</strong></td>';
                    echo '<td>' . $this->statusPill($sev) . '</td>';
                    echo '<td>' . esc_html($sum) . '</td>';
                    echo '</tr>';

                    if ($key === 'frontend_matrix') {
                        $caseRows = isset($check['details']['cases']) && is_array($check['details']['cases']) ? $check['details']['cases'] : [];
                        if (!empty($caseRows)) {
                            echo '<tr><td></td><td colspan="2">';
                            echo '<table style="width:100%; border-collapse:collapse;">';
                            foreach ($caseRows as $slot => $row) {
                                if (!is_array($row)) {
                                    continue;
                                }
                                $csev = isset($row['severity']) ? (string) $row['severity'] : 'info';
                                $csum = isset($row['summary']) ? (string) $row['summary'] : '';
                                echo '<tr>';
                                echo '<td style="padding:4px 10px 4px 0; width:100px;"><strong>' . esc_html(ucfirst((string) $slot)) . '</strong></td>';
                                echo '<td style="padding:4px 10px 4px 0; width:120px;">' . $this->statusPill($csev) . '</td>';
                                echo '<td style="padding:4px 0;">' . esc_html($csum) . '</td>';
                                echo '</tr>';
                            }
                            echo '</table>';
                            echo '</td></tr>';
                        }
                    }
                }
                echo '</tbody></table>';
            }
        }

        if (!empty($releaseHistory)) {
            echo '<h3 style="margin:16px 0 8px 0;">' . esc_html__('Recent release smoke runs', 'wp-seo-next') . '</h3>';
            echo '<table class="widefat striped">';
            echo '<thead><tr><th>' . esc_html__('Started (UTC)', 'wp-seo-next') . '</th><th>' . esc_html__('Severity', 'wp-seo-next') . '</th><th>' . esc_html__('Counts', 'wp-seo-next') . '</th><th>' . esc_html__('Baseline', 'wp-seo-next') . '</th><th>' . esc_html__('Actions', 'wp-seo-next') . '</th></tr></thead><tbody>';
            $latestReleaseId = !empty($releaseHistory[0]['id']) ? (string) $releaseHistory[0]['id'] : '';
            $previousReleaseId = !empty($releaseHistory[1]['id']) ? (string) $releaseHistory[1]['id'] : '';
            foreach ($releaseHistory as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $rowId = isset($row['id']) ? (string) $row['id'] : '';
                $rowTs = isset($row['created_at_utc']) ? (string) $row['created_at_utc'] : '';
                $rowSev = isset($row['severity']) ? (string) $row['severity'] : 'info';
                $rowCounts = isset($row['counts']) && is_array($row['counts']) ? $row['counts'] : [];
                $rowExport = $this->releaseExportUrl($rowId);
                $rowPinUrl = $this->releasePinUrl($rowId);
                $isPinnedRow = ($releasePinnedId !== '' && $releasePinnedId === $rowId);
                $compareTargetId = '';
                if ($releasePinnedId !== '' && $rowId !== $releasePinnedId) {
                    $compareTargetId = $releasePinnedId;
                } elseif ($rowId !== '' && $rowId !== $latestReleaseId && $latestReleaseId !== '') {
                    $compareTargetId = $latestReleaseId;
                } elseif ($rowId !== '' && $rowId === $latestReleaseId && $previousReleaseId !== '') {
                    $compareTargetId = $previousReleaseId;
                }
                if ($releasePinnedId !== '' && $rowId !== $releasePinnedId) {
                    $compareUrl = $this->releaseComparePageUrl($releasePinnedId, $rowId);
                } else {
                    $compareUrl = $compareTargetId !== '' ? $this->releaseComparePageUrl($rowId, $compareTargetId) : '';
                }
                echo '<tr>';
                echo '<td><code>' . esc_html($rowTs) . '</code></td>';
                echo '<td>' . $this->statusPill($rowSev) . '</td>';
                echo '<td>' . esc_html(sprintf(__('OK %1$d · Info %2$d · Warn %3$d · Critical %4$d', 'wp-seo-next'), (int) ($rowCounts['ok'] ?? 0), (int) ($rowCounts['info'] ?? 0), (int) ($rowCounts['warn'] ?? 0), (int) ($rowCounts['critical'] ?? 0))) . '</td>';
                echo '<td>' . ($isPinnedRow ? $this->releaseComparePill('unchanged') . ' ' . esc_html__('Pinned', 'wp-seo-next') : '<span class="description">—</span>') . '</td>';
                echo '<td><a class="button button-small" href="' . esc_url($rowExport) . '">' . esc_html__('JSON', 'wp-seo-next') . '</a>';
                if ($compareUrl !== '') {
                    echo ' <a class="button button-small" href="' . esc_url($compareUrl) . '">' . esc_html__('Compare', 'wp-seo-next') . '</a>';
                }
                if (!$isPinnedRow && $rowPinUrl !== '') {
                    echo ' <a class="button button-small" href="' . esc_url($rowPinUrl) . '">' . esc_html__('Pin as baseline', 'wp-seo-next') . '</a>';
                }
                echo '</td>';
                echo '</tr>';
            }
            echo '</tbody></table>';
        }

        if (count($releaseCompareRuns) >= 2) {
            $this->renderReleaseSmokeComparePanel($releaseCompareRuns, $releaseCompareBaseId, $releaseCompareCandidateId, $releaseCompareDiff, $releasePinnedId);
        }

        if (!empty($releaseLast)) {
            echo '<details style="margin-top:12px;"><summary>' . esc_html__('Show last release smoke JSON', 'wp-seo-next') . '</summary>';
            $encodedRelease = wp_json_encode($releaseLast, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            echo '<pre style="background:#fff;border:1px solid #ccd0d4;padding:12px;max-height:520px;overflow:auto;">' . esc_html((string) $encodedRelease) . '</pre>';
            echo '</details>';
        }
        echo '</div>';

        $checks = isset($res['checks']) && is_array($res['checks']) ? $res['checks'] : [];

        echo '<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(320px, 1fr)); gap:12px; max-width:1200px;">';

        // Sitemap card.
        $this->renderCheckCard('sitemap', __('Sitemap', 'wp-seo-next'), $checks, [
            ['label' => __('Open', 'wp-seo-next'), 'url' => home_url('/sitemap_index.xml'), 'external' => true],
            ['label' => __('Sitemaps Center', 'wp-seo-next'), 'url' => admin_url('tools.php?page=wpsn-sitemaps')],
            ['label' => __('Flush rewrites', 'wp-seo-next'), 'url' => wp_nonce_url(admin_url('admin-post.php?action=wpsn_sitemaps_flush_rewrites'), 'wpsn_sitemaps_flush_rewrites')],
        ]);

        // Robots card.
        $this->renderCheckCard('robots', __('Robots', 'wp-seo-next'), $checks, [
            ['label' => __('Open', 'wp-seo-next'), 'url' => home_url('/robots.txt'), 'external' => true],
            ['label' => __('Robots Center', 'wp-seo-next'), 'url' => admin_url('tools.php?page=wpsn-robots')],
        ]);

        // Head card.
        $this->renderCheckCard('head', __('Head output', 'wp-seo-next'), $checks, [
            ['label' => __('Head snapshots', 'wp-seo-next'), 'url' => admin_url('tools.php?page=wpsn-snapshots')],
            ['label' => __('Settings', 'wp-seo-next'), 'url' => admin_url('admin.php?page=wp-seo-next')],
        ]);

        // Frontend contract card.
        $this->renderCheckCard('frontend', __('Frontend', 'wp-seo-next'), $checks, [
            ['label' => __('Open', 'wp-seo-next'), 'url' => home_url('/'), 'external' => true],
            ['label' => __('Diagnostics', 'wp-seo-next'), 'url' => admin_url('tools.php?page=wpsn-diagnostics')],
        ]);

        // Queue card.
        $runQueueUrl = wp_nonce_url(admin_url('admin-post.php?action=wpsn_quality_gate_run_queue&max=1'), 'wpsn_quality_gate_run_queue');
        $this->renderCheckCard('queue', __('Queue', 'wp-seo-next'), $checks, [
            ['label' => __('Run worker (1 job)', 'wp-seo-next'), 'url' => $runQueueUrl],
            ['label' => __('Queue Center', 'wp-seo-next'), 'url' => admin_url('tools.php?page=wpsn-queue')],
            ['label' => __('Diagnostics', 'wp-seo-next'), 'url' => admin_url('tools.php?page=wpsn-diagnostics')],
        ]);

        // Fatals card.
        $this->renderCheckCard('fatals', __('Fatal errors', 'wp-seo-next'), $checks, [
            ['label' => __('Open fatal log', 'wp-seo-next'), 'url' => admin_url('tools.php?page=wpsn-diagnostics#wpsn-fatal-log')],
            ['label' => __('Diagnostics', 'wp-seo-next'), 'url' => admin_url('tools.php?page=wpsn-diagnostics')],
        ]);

        echo '</div>';

        // Raw details.
        if (!empty($checks)) {
            echo '<h2 style="margin-top:18px;">' . esc_html__('Details', 'wp-seo-next') . '</h2>';
            echo '<details><summary>' . esc_html__('Show raw JSON (for support)', 'wp-seo-next') . '</summary>';
            $encoded = wp_json_encode($res, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            echo '<pre style="background:#fff;border:1px solid #ccd0d4;padding:12px;max-height:520px;overflow:auto;">' . esc_html((string) $encoded) . '</pre>';
            echo '</details>';
        }

        echo '</div>';
    }

    /**
     * @param array<string,mixed> $summary
     */
    private function renderReleaseCompactOverviewCard(array $summary, string $compareUrl, string $exportUrl): void
    {
        echo '<div class="wpsn-card" style="max-width:1200px; margin:0 0 14px 0;">';
        echo '<div style="display:flex;justify-content:space-between;gap:12px;align-items:flex-start;flex-wrap:wrap;">';
        echo '<div>';
        echo '<h2 style="margin:0 0 6px 0;">' . esc_html__('Release overview', 'wp-seo-next') . '</h2>';
        echo '<p class="description" style="margin:0;max-width:900px;">' . esc_html__('Compact regression badges from the latest release smoke snapshot. Uses the pinned baseline when available, otherwise falls back to the previous saved run.', 'wp-seo-next') . '</p>';
        if (!empty($summary['badges']) && is_array($summary['badges'])) {
            echo ReleaseSmokeBadgeHtml::render($summary['badges']);
        }
        if (!empty($summary['summary'])) {
            echo '<p class="description" style="margin:8px 0 0 0;">' . esc_html((string) $summary['summary']) . '</p>';
        }
        if (!empty($summary['meta'])) {
            echo '<p class="description" style="margin:6px 0 0 0;">' . esc_html((string) $summary['meta']) . '</p>';
        }
        echo '</div>';
        echo '<div class="wpsn-actions">';
        if ($compareUrl !== '') {
            echo '<a class="button button-secondary" href="' . esc_url($compareUrl) . '">' . esc_html__('Open compare view', 'wp-seo-next') . '</a> ';
        }
        if ($exportUrl !== '') {
            echo '<a class="button button-secondary" href="' . esc_url($exportUrl) . '">' . esc_html__('Latest JSON', 'wp-seo-next') . '</a> ';
        }
        echo '<a class="button button-secondary" href="#wpsn-release-smoke">' . esc_html__('Jump to matrix', 'wp-seo-next') . '</a>';
        echo '</div>';
        echo '</div>';
        echo '</div>';
    }

    /**
     * @param array<int,array<string,mixed>> $history
     * @param array<string,mixed> $diff
     */
    private function renderReleaseSmokeComparePanel(array $history, string $baseId, string $candidateId, array $diff, string $pinnedId = ''): void
    {
        echo '<div class="wpsn-card" id="wpsn-release-smoke-compare" style="max-width:1200px; margin:16px 0 0 0;">';
        echo '<h3 style="margin:0 0 8px 0;">' . esc_html__('Compare two release smoke runs', 'wp-seo-next') . '</h3>';
        echo '<p class="description" style="margin:0 0 12px 0;">' . esc_html__('Pick any two saved runs to see what regressed, improved, or simply changed between release snapshots.', 'wp-seo-next') . '</p>';

        echo '<form method="get" action="' . esc_url(admin_url('tools.php')) . '" style="display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end;">';
        echo '<input type="hidden" name="page" value="' . esc_attr(self::PAGE_SLUG) . '" />';
        echo '<label><span class="description" style="display:block;margin-bottom:4px;">' . esc_html__('Baseline', 'wp-seo-next') . '</span><select name="release_compare_base">';
        foreach ($history as $row) {
            if (!is_array($row)) {
                continue;
            }
            $rowId = isset($row['id']) ? (string) $row['id'] : '';
            if ($rowId === '') {
                continue;
            }
            $label = isset($row['created_at_utc']) ? (string) $row['created_at_utc'] : $rowId;
            $severity = isset($row['severity']) ? (string) $row['severity'] : 'info';
            if ($pinnedId !== '' && $rowId === $pinnedId) {
                $label = __('Pinned baseline', 'wp-seo-next') . ' · ' . $label;
            }
            echo '<option value="' . esc_attr($rowId) . '" ' . selected($baseId, $rowId, false) . '>' . esc_html($label . ' · ' . strtoupper($severity)) . '</option>';
        }
        echo '</select></label>';
        echo '<label><span class="description" style="display:block;margin-bottom:4px;">' . esc_html__('Candidate', 'wp-seo-next') . '</span><select name="release_compare_candidate">';
        foreach ($history as $row) {
            if (!is_array($row)) {
                continue;
            }
            $rowId = isset($row['id']) ? (string) $row['id'] : '';
            if ($rowId === '') {
                continue;
            }
            $label = isset($row['created_at_utc']) ? (string) $row['created_at_utc'] : $rowId;
            $severity = isset($row['severity']) ? (string) $row['severity'] : 'info';
            if ($pinnedId !== '' && $rowId === $pinnedId) {
                $label = __('Pinned baseline', 'wp-seo-next') . ' · ' . $label;
            }
            echo '<option value="' . esc_attr($rowId) . '" ' . selected($candidateId, $rowId, false) . '>' . esc_html($label . ' · ' . strtoupper($severity)) . '</option>';
        }
        echo '</select></label>';
        submit_button(__('Compare runs', 'wp-seo-next'), 'secondary', 'submit', false);
        echo '</form>';

        if (empty($diff)) {
            echo '<p class="description" style="margin:10px 0 0 0;">' . esc_html__('Choose two different saved runs to render a regression diff.', 'wp-seo-next') . '</p>';
            echo '</div>';
            return;
        }

        $summary = isset($diff['summary']) && is_array($diff['summary']) ? $diff['summary'] : [];
        $baseline = isset($diff['baseline']) && is_array($diff['baseline']) ? $diff['baseline'] : [];
        $candidate = isset($diff['candidate']) && is_array($diff['candidate']) ? $diff['candidate'] : [];
        $countDeltas = isset($summary['count_deltas']) && is_array($summary['count_deltas']) ? $summary['count_deltas'] : [];
        $compareExportUrl = $this->releaseCompareExportUrl($baseId, $candidateId);

        echo '<div style="margin-top:12px;padding:10px;border:1px solid #dcdcde;border-radius:4px;background:#f8f9fa;">';
        echo '<p style="margin:0 0 6px 0;">' . $this->releaseComparePill((string) ($summary['severity_status'] ?? 'changed')) . ' <strong>' . esc_html__('Baseline:', 'wp-seo-next') . '</strong> <code>' . esc_html((string) ($baseline['created_at_utc'] ?? '')) . '</code> → <strong>' . esc_html__('Candidate:', 'wp-seo-next') . '</strong> <code>' . esc_html((string) ($candidate['created_at_utc'] ?? '')) . '</code></p>';
        echo '<p class="description" style="margin:0;">' . esc_html(sprintf(__('Regressions: %1$d · Improvements: %2$d · Changed: %3$d · Unchanged: %4$d', 'wp-seo-next'), (int) ($summary['regressions'] ?? 0), (int) ($summary['improvements'] ?? 0), (int) ($summary['changed'] ?? 0), (int) ($summary['unchanged'] ?? 0))) . '</p>';
        echo '<p class="description" style="margin:6px 0 0 0;">' . esc_html(sprintf(__('Counts Δ → OK %1$+d · Info %2$+d · Warn %3$+d · Critical %4$+d', 'wp-seo-next'), (int) ($countDeltas['ok'] ?? 0), (int) ($countDeltas['info'] ?? 0), (int) ($countDeltas['warn'] ?? 0), (int) ($countDeltas['critical'] ?? 0))) . '</p>';
        if ($compareExportUrl !== '') {
            echo '<p style="margin:8px 0 0 0;"><a class="button button-small" href="' . esc_url($compareExportUrl) . '">' . esc_html__('Export compare JSON', 'wp-seo-next') . '</a></p>';
        }
        echo '</div>';

        $checkRows = isset($diff['checks']) && is_array($diff['checks']) ? $diff['checks'] : [];
        if (!empty($checkRows)) {
            echo '<table class="widefat striped" style="margin-top:12px;">';
            echo '<thead><tr><th style="width:220px;">' . esc_html__('Check', 'wp-seo-next') . '</th><th style="width:180px;">' . esc_html__('Baseline', 'wp-seo-next') . '</th><th style="width:180px;">' . esc_html__('Candidate', 'wp-seo-next') . '</th><th>' . esc_html__('Diff', 'wp-seo-next') . '</th></tr></thead><tbody>';
            foreach ($checkRows as $row) {
                if (!is_array($row)) {
                    continue;
                }
                $rowStatus = isset($row['status']) ? (string) $row['status'] : 'changed';
                $rowLabel = isset($row['label']) ? (string) $row['label'] : (string) ($row['key'] ?? '');
                $baseRow = isset($row['baseline']) && is_array($row['baseline']) ? $row['baseline'] : [];
                $candidateRow = isset($row['candidate']) && is_array($row['candidate']) ? $row['candidate'] : [];
                echo '<tr>';
                echo '<td><strong>' . esc_html($rowLabel) . '</strong></td>';
                echo '<td>' . (!empty($baseRow) ? $this->statusPill((string) ($baseRow['severity'] ?? 'info')) . ' ' . esc_html((string) ($baseRow['summary'] ?? '')) : '<span class="description">—</span>') . '</td>';
                echo '<td>' . (!empty($candidateRow) ? $this->statusPill((string) ($candidateRow['severity'] ?? 'info')) . ' ' . esc_html((string) ($candidateRow['summary'] ?? '')) : '<span class="description">—</span>') . '</td>';
                echo '<td>' . $this->releaseComparePill($rowStatus) . '</td>';
                echo '</tr>';

                $caseDiffs = isset($row['case_diffs']) && is_array($row['case_diffs']) ? $row['case_diffs'] : [];
                if (!empty($caseDiffs)) {
                    echo '<tr><td></td><td colspan="3">';
                    echo '<table style="width:100%; border-collapse:collapse;">';
                    foreach ($caseDiffs as $caseRow) {
                        if (!is_array($caseRow)) {
                            continue;
                        }
                        $caseBase = isset($caseRow['baseline']) && is_array($caseRow['baseline']) ? $caseRow['baseline'] : [];
                        $caseCandidate = isset($caseRow['candidate']) && is_array($caseRow['candidate']) ? $caseRow['candidate'] : [];
                        echo '<tr>';
                        echo '<td style="padding:4px 10px 4px 0; width:120px;"><strong>' . esc_html((string) ($caseRow['label'] ?? '')) . '</strong></td>';
                        echo '<td style="padding:4px 10px 4px 0; width:220px;">' . (!empty($caseBase) ? $this->statusPill((string) ($caseBase['severity'] ?? 'info')) . ' ' . esc_html((string) ($caseBase['summary'] ?? '')) : '<span class="description">—</span>') . '</td>';
                        echo '<td style="padding:4px 10px 4px 0; width:220px;">' . (!empty($caseCandidate) ? $this->statusPill((string) ($caseCandidate['severity'] ?? 'info')) . ' ' . esc_html((string) ($caseCandidate['summary'] ?? '')) : '<span class="description">—</span>') . '</td>';
                        echo '<td style="padding:4px 0;">' . $this->releaseComparePill((string) ($caseRow['status'] ?? 'changed')) . '</td>';
                        echo '</tr>';
                    }
                    echo '</table>';
                    echo '</td></tr>';
                }
            }
            echo '</tbody></table>';
        }

        echo '</div>';
    }

    private function releaseComparePill(string $status): string
    {
        $status = sanitize_key($status);
        $map = [
            'improved' => __('Improved', 'wp-seo-next'),
            'regressed' => __('Regressed', 'wp-seo-next'),
            'changed' => __('Changed', 'wp-seo-next'),
            'unchanged' => __('Unchanged', 'wp-seo-next'),
            'added' => __('Added', 'wp-seo-next'),
            'removed' => __('Removed', 'wp-seo-next'),
        ];
        if (!isset($map[$status])) {
            $status = 'changed';
        }

        return '<span class="wpsn-compare-pill ' . esc_attr($status) . '">' . esc_html($map[$status]) . '</span>';
    }

    private function releaseComparePageUrl(string $baseId, string $candidateId): string
    {
        $baseId = trim($baseId);
        $candidateId = trim($candidateId);
        if ($baseId === '' || $candidateId === '' || $baseId === $candidateId) {
            return '';
        }

        return add_query_arg([
            'page' => self::PAGE_SLUG,
            'release_compare_base' => $baseId,
            'release_compare_candidate' => $candidateId,
        ], admin_url('tools.php')) . '#wpsn-release-smoke-compare';
    }

    /**
     * @param array<int,array<string,mixed>> $history
     * @param array<string,mixed> $pinned
     * @return array<int,array<string,mixed>>
     */
    private function releaseCompareRuns(array $history, array $pinned = []): array
    {
        $out = [];
        $seen = [];
        $pinnedId = !empty($pinned['id']) ? (string) $pinned['id'] : '';

        if ($pinnedId !== '') {
            $pinned['is_pinned'] = true;
            $out[] = $pinned;
            $seen[$pinnedId] = true;
        }

        foreach ($history as $row) {
            if (!is_array($row) || empty($row['id'])) {
                continue;
            }
            $rowId = (string) $row['id'];
            if (isset($seen[$rowId])) {
                continue;
            }
            if ($pinnedId !== '' && $rowId === $pinnedId) {
                $row['is_pinned'] = true;
            }
            $out[] = $row;
            $seen[$rowId] = true;
        }

        return $out;
    }

    private function releaseExportUrl(string $runId): string
    {
        $runId = trim($runId);
        if ($runId === '') {
            return '';
        }

        return wp_nonce_url(
            admin_url('admin-post.php?action=wpsn_release_smoke_export&run_id=' . rawurlencode($runId)),
            'wpsn_release_smoke_export'
        );
    }

    private function releasePinUrl(string $runId): string
    {
        $runId = trim($runId);
        if ($runId === '') {
            return '';
        }

        return wp_nonce_url(
            admin_url('admin-post.php?action=wpsn_release_smoke_pin&run_id=' . rawurlencode($runId)),
            'wpsn_release_smoke_pin_' . $runId
        );
    }

    private function releaseUnpinUrl(): string
    {
        return wp_nonce_url(
            admin_url('admin-post.php?action=wpsn_release_smoke_unpin'),
            'wpsn_release_smoke_unpin'
        );
    }

    private function releaseCompareExportUrl(string $baseId, string $candidateId): string
    {
        $baseId = trim($baseId);
        $candidateId = trim($candidateId);
        if ($baseId === '' || $candidateId === '' || $baseId === $candidateId) {
            return '';
        }

        $url = add_query_arg([
            'action' => 'wpsn_release_smoke_compare_export',
            'baseline_run' => $baseId,
            'candidate_run' => $candidateId,
        ], admin_url('admin-post.php'));

        return function_exists('wp_nonce_url')
            ? wp_nonce_url($url, 'wpsn_release_smoke_compare_export')
            : $url;
    }

    /**
     * @param array<string,mixed> $checks
     * @param array<int,array{label:string,url:string,external?:bool}> $actions
     */
    private function renderCheckCard(string $key, string $title, array $checks, array $actions): void
    {
        $c = isset($checks[$key]) && is_array($checks[$key]) ? $checks[$key] : [];
        $sev = isset($c['severity']) ? (string) $c['severity'] : 'info';
        $summary = isset($c['summary']) ? (string) $c['summary'] : '';

        echo '<div class="wpsn-card">';
        echo '<h3 style="margin:0 0 6px 0;">' . $this->statusPill($sev) . esc_html($title) . '</h3>';
        if ($summary !== '') {
            echo '<p style="margin:6px 0 10px 0;">' . esc_html($summary) . '</p>';
        }
        echo '<div class="wpsn-actions">';
        foreach ($actions as $a) {
            $label = (string) ($a['label'] ?? '');
            $url = (string) ($a['url'] ?? '');
            $external = !empty($a['external']);
            if ($label === '' || $url === '') {
                continue;
            }
            $attrs = $external ? ' target="_blank" rel="noopener"' : '';
            echo '<a class="button button-secondary" href="' . esc_url($url) . '"' . $attrs . '>' . esc_html($label) . '</a>';
        }
        echo '</div>';

        // Compact extra info.
        $details = isset($c['details']) && is_array($c['details']) ? $c['details'] : [];
        if ($key === 'sitemap') {
            $rewriteOk = !empty($details['rewrite_rules_present']);
            echo '<p class="description" style="margin-top:10px;">' . esc_html__('Rewrite rules:', 'wp-seo-next') . ' <strong>' . esc_html($rewriteOk ? __('present', 'wp-seo-next') : __('missing', 'wp-seo-next')) . '</strong></p>';
        }
        if ($key === 'queue') {
            $counts = isset($details['counts']) && is_array($details['counts']) ? $details['counts'] : [];
            $p = (int) ($counts['pending'] ?? 0);
            $f = (int) ($counts['failed'] ?? 0);
            $paused = !empty($details['paused']);
            $pauseLabel = $paused ? __('PAUSED', 'wp-seo-next') : __('RUNNING', 'wp-seo-next');
            echo '<p class="description" style="margin-top:10px;">' . esc_html(sprintf(__('Pending: %d · Failed: %d · %s', 'wp-seo-next'), $p, $f, $pauseLabel)) . '</p>';
        }

        echo '</div>';
    }
}
