<?php

namespace WPSN\Modules\Stability\Admin;

use WPSN\Admin\SettingsRepository;
use WPSN\Core\Capabilities;
use WPSN\Core\Container;
use WPSN\Modules\Stability\HeadDiffService;
use WPSN\Modules\Stability\HeadSnapshotService;
use WPSN\Modules\Stability\ConfigSnapshotService;
use WPSN\Modules\Stability\SnapshotRepository;
use WPSN\Modules\Revisions\RevisionsInstaller;
use WPSN\Modules\Revisions\RevisionsRepository;

defined('ABSPATH') || exit;

final class SnapshotsAdmin
{
    private Container $container;

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

    public function register(): void
    {
        add_action('admin_menu', [$this, 'addMenu']);
        add_action('admin_post_wpsn_snapshots_create_config', [$this, 'handleCreateConfig']);
        add_action('admin_post_wpsn_snapshots_restore_config', [$this, 'handleRestoreConfig']);
        add_action('admin_post_wpsn_snapshots_restore_config_partial', [$this, 'handleRestoreConfigPartial']);
        add_action('admin_post_wpsn_snapshots_create_head', [$this, 'handleCreateHead']);
        add_action('admin_post_wpsn_snapshots_set_head_baseline', [$this, 'handleSetHeadBaseline']);
        add_action('admin_post_wpsn_snapshots_delete', [$this, 'handleDeleteSnapshot']);
    }

    public function addMenu(): void
    {
        add_management_page(
            __('SEO Snapshots', 'wp-seo-next'),
            __('SEO Snapshots', 'wp-seo-next'),
            Capabilities::CAP_TOOLS,
            'wpsn-snapshots',
            [$this, 'render']
        );
    }

    public function render(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('Permission denied.', 'wp-seo-next'));
        }

        $tab = isset($_GET['tab']) ? sanitize_key((string) wp_unslash($_GET['tab'])) : 'config';
        if (!in_array($tab, ['config', 'head'], true)) {
            $tab = 'config';
        }

        echo '<div class="wrap">';
        echo '<h1>' . esc_html__('SEO Snapshots', 'wp-seo-next') . '</h1>';
        echo '<p class="description">' . esc_html__('Create snapshots of WP SEO Next configuration and generated head output. Use them to audit changes and to rollback safely.', 'wp-seo-next') . '</p>';

        if (!empty($_GET['wpsn_notice'])) {
            $n = sanitize_key((string) wp_unslash($_GET['wpsn_notice']));
            if ($n === 'created') {
                echo '<div class="notice notice-success"><p>' . esc_html__('Snapshot created.', 'wp-seo-next') . '</p></div>';
            } elseif ($n === 'restored') {
                echo '<div class="notice notice-success"><p>' . esc_html__('Snapshot restored.', 'wp-seo-next') . '</p></div>';
            } elseif ($n === 'deleted') {
                echo '<div class="notice notice-success"><p>' . esc_html__('Snapshot deleted.', 'wp-seo-next') . '</p></div>';
            } elseif ($n === 'baseline_set') {
                echo '<div class="notice notice-success"><p>' . esc_html__('Baseline updated.', 'wp-seo-next') . '</p></div>';
            } elseif ($n === 'error') {
                echo '<div class="notice notice-error"><p>' . esc_html__('Action failed. Check permissions and snapshot ID.', 'wp-seo-next') . '</p></div>';
            }
        }

        // Tabs.
        $baseUrl = admin_url('tools.php?page=wpsn-snapshots');
        echo '<h2 class="nav-tab-wrapper">';
        $this->tabLink($baseUrl, 'config', __('Config snapshots', 'wp-seo-next'), $tab === 'config');
        $this->tabLink($baseUrl, 'head', __('Head output snapshots', 'wp-seo-next'), $tab === 'head');
        echo '</h2>';

        if ($tab === 'config') {
            $this->renderConfigTab();
        } else {
            $this->renderHeadTab();
        }

        echo '</div>';
    }

    private function tabLink(string $baseUrl, string $tab, string $label, bool $active): void
    {
        $url = add_query_arg(['tab' => $tab], $baseUrl);
        $cls = $active ? 'nav-tab nav-tab-active' : 'nav-tab';
        echo '<a class="' . esc_attr($cls) . '" href="' . esc_url($url) . '">' . esc_html($label) . '</a>';
    }

    private function renderConfigTab(): void
    {
        $repo = new SnapshotRepository();

        echo '<h2>' . esc_html__('Config snapshots', 'wp-seo-next') . '</h2>';
        echo '<p class="description">' . esc_html__('Snapshots store WP SEO Next settings (no secrets). You can review grouped diffs and restore only selected parts (e.g. Templates/Robots/Modules/Providers).', 'wp-seo-next') . '</p>';

        // Create form.
        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="margin: 12px 0;">';
        echo '<input type="hidden" name="action" value="wpsn_snapshots_create_config" />';
        wp_nonce_field('wpsn_snapshots_create_config', 'wpsn_nonce');
        echo '<label>' . esc_html__('Label', 'wp-seo-next') . ' <input type="text" name="label" class="regular-text" placeholder="' . esc_attr__('e.g. Before template change', 'wp-seo-next') . '" /></label> ';
        echo '<button type="submit" class="button button-primary">' . esc_html__('Create config snapshot', 'wp-seo-next') . '</button>';
        echo '</form>';

        $diffId = isset($_GET['diff']) ? (int) $_GET['diff'] : 0;
        if ($diffId > 0) {
            $this->renderConfigDiff($diffId);
        }

        $rows = $repo->list(SnapshotRepository::TYPE_CONFIG, 20, 0);

        echo '<h3>' . esc_html__('Recent snapshots', 'wp-seo-next') . '</h3>';
        if (empty($rows)) {
            echo '<p>' . esc_html__('No config snapshots yet.', 'wp-seo-next') . '</p>';
            return;
        }

        echo '<table class="widefat striped"><thead><tr>';
        echo '<th>' . esc_html__('ID', 'wp-seo-next') . '</th>';
        echo '<th>' . esc_html__('Created (UTC)', 'wp-seo-next') . '</th>';
        echo '<th>' . esc_html__('Label', 'wp-seo-next') . '</th>';
        echo '<th>' . esc_html__('Plugin version', 'wp-seo-next') . '</th>';
        echo '<th>' . esc_html__('Actions', 'wp-seo-next') . '</th>';
        echo '</tr></thead><tbody>';

        foreach ($rows as $r) {
            $id = (int) ($r['id'] ?? 0);
            $label = (string) ($r['label'] ?? '');
            $created = (string) ($r['created_at'] ?? '');
            $ver = (string) ($r['plugin_version'] ?? '');

            $diffUrl = add_query_arg(['tab' => 'config', 'diff' => $id], admin_url('tools.php?page=wpsn-snapshots'));
            $restoreUrl = wp_nonce_url(admin_url('admin-post.php?action=wpsn_snapshots_restore_config&snapshot_id=' . $id), 'wpsn_snapshots_restore_config_' . $id, 'wpsn_nonce');
            $deleteUrl = wp_nonce_url(admin_url('admin-post.php?action=wpsn_snapshots_delete&snapshot_id=' . $id), 'wpsn_snapshots_delete_' . $id, 'wpsn_nonce');

            echo '<tr>';
            echo '<td>' . esc_html((string) $id) . '</td>';
            echo '<td>' . esc_html($created) . '</td>';
            echo '<td>' . esc_html($label) . '</td>';
            echo '<td><code>' . esc_html($ver) . '</code></td>';
            echo '<td>';
            echo '<a class="button" href="' . esc_url($diffUrl) . '">' . esc_html__('Diff vs current', 'wp-seo-next') . '</a> ';
            echo '<a class="button" href="' . esc_url($restoreUrl) . '" onclick="return confirm(\'' . esc_js(__('Restore this snapshot (FULL)? This will overwrite current WP SEO Next settings.', 'wp-seo-next')) . '\');">' . esc_html__('Restore (full)', 'wp-seo-next') . '</a> ';
            echo '<a class="button" href="' . esc_url($deleteUrl) . '" onclick="return confirm(\'' . esc_js(__('Delete this snapshot?', 'wp-seo-next')) . '\');">' . esc_html__('Delete', 'wp-seo-next') . '</a>';
            echo '</td>';
            echo '</tr>';
        }

        echo '</tbody></table>';
    }

    private function renderHeadTab(): void
    {
        $repo = new SnapshotRepository();
        $head = new HeadSnapshotService($this->container);
        $baselineId = $head->getBaselineId();

        echo '<h2>' . esc_html__('Head output snapshots', 'wp-seo-next') . '</h2>';
        echo '<p class="description">' . esc_html__('Snapshots store the generated head HTML for a few representative contexts (home, a post, a term, and optionally a WooCommerce product).', 'wp-seo-next') . '</p>';

        // If a notice exists, show details here too.
        $notice = get_option(HeadDiffService::OPTION_NOTICE);
        if (is_array($notice) && !empty($notice['changed'])) {
            $from = isset($notice['from_version']) ? (string) $notice['from_version'] : '';
            $to = isset($notice['to_version']) ? (string) $notice['to_version'] : '';
            echo '<div class="notice notice-warning"><p><strong>' . esc_html__('Update diff detected', 'wp-seo-next') . '</strong><br />';
            echo esc_html(sprintf(__('Baseline vs current differs after update (%s → %s).', 'wp-seo-next'), $from !== '' ? $from : '?', $to !== '' ? $to : (string) WPSN_VERSION));
            $changed = isset($notice['changed']) && is_array($notice['changed']) ? $notice['changed'] : [];
            if (!empty($changed)) {
                echo '<br /><span class="description">' . esc_html__('Changed cases:', 'wp-seo-next') . '</span></p>';
                echo '<ul style="margin:0 0 0 22px; list-style:disc;">';
                $i = 0;
                foreach ($changed as $c) {
                    if (!is_array($c)) {
                        continue;
                    }
                    $i++;
                    if ($i > 5) {
                        break;
                    }
                    $label = isset($c['label']) ? (string) $c['label'] : (isset($c['key']) ? (string) $c['key'] : '');
                    $summary = isset($c['diff_summary']) && is_array($c['diff_summary']) ? $c['diff_summary'] : null;
                    echo '<li><strong>' . esc_html($label) . '</strong>';
                    if (is_array($summary) && !empty($summary['sample']) && is_array($summary['sample'])) {
                        $sample = array_slice($summary['sample'], 0, 8);
                        echo '<br /><span class="description">' . esc_html(implode(', ', array_map('strval', $sample))) . '</span>';
                    }
                    echo '</li>';
                }
                echo '</ul>';

                $baselineIdNotice = isset($notice['baseline_snapshot_id']) ? (int) $notice['baseline_snapshot_id'] : 0;
                if ($baselineIdNotice > 0) {
                    $diffUrl = add_query_arg(['tab' => 'head', 'diff' => $baselineIdNotice], admin_url('tools.php?page=wpsn-snapshots'));
                    echo '<p style="margin-top:8px;"><a class="button" href="' . esc_url($diffUrl) . '">' . esc_html__('Review baseline diff', 'wp-seo-next') . '</a></p>';
                }
            } else {
                echo '</p>';
            }
            echo '</div>';
        }

        // Create form.
        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="margin: 12px 0;">';
        echo '<input type="hidden" name="action" value="wpsn_snapshots_create_head" />';
        wp_nonce_field('wpsn_snapshots_create_head', 'wpsn_nonce');
        echo '<label>' . esc_html__('Label', 'wp-seo-next') . ' <input type="text" name="label" class="regular-text" placeholder="' . esc_attr__('e.g. Baseline before update', 'wp-seo-next') . '" /></label> ';
        echo '<button type="submit" class="button button-primary">' . esc_html__('Create head snapshot', 'wp-seo-next') . '</button>';
        echo '</form>';

        $diffId = isset($_GET['diff']) ? (int) $_GET['diff'] : 0;
        if ($diffId > 0) {
            $this->renderHeadDiff($diffId);
        }

        $rows = $repo->list(SnapshotRepository::TYPE_HEAD, 20, 0);

        echo '<h3>' . esc_html__('Recent head snapshots', 'wp-seo-next') . '</h3>';
        if (empty($rows)) {
            echo '<p>' . esc_html__('No head snapshots yet.', 'wp-seo-next') . '</p>';
            return;
        }

        echo '<table class="widefat striped"><thead><tr>';
        echo '<th>' . esc_html__('ID', 'wp-seo-next') . '</th>';
        echo '<th>' . esc_html__('Created (UTC)', 'wp-seo-next') . '</th>';
        echo '<th>' . esc_html__('Label', 'wp-seo-next') . '</th>';
        echo '<th>' . esc_html__('Plugin version', 'wp-seo-next') . '</th>';
        echo '<th>' . esc_html__('Baseline', 'wp-seo-next') . '</th>';
        echo '<th>' . esc_html__('Actions', 'wp-seo-next') . '</th>';
        echo '</tr></thead><tbody>';

        foreach ($rows as $r) {
            $id = (int) ($r['id'] ?? 0);
            $label = (string) ($r['label'] ?? '');
            $created = (string) ($r['created_at'] ?? '');
            $ver = (string) ($r['plugin_version'] ?? '');

            $isBaseline = $baselineId > 0 && $id === (int) $baselineId;

            $diffUrl = add_query_arg(['tab' => 'head', 'diff' => $id], admin_url('tools.php?page=wpsn-snapshots'));
            $setBaseUrl = wp_nonce_url(admin_url('admin-post.php?action=wpsn_snapshots_set_head_baseline&snapshot_id=' . $id), 'wpsn_snapshots_set_head_baseline_' . $id, 'wpsn_nonce');
            $deleteUrl = wp_nonce_url(admin_url('admin-post.php?action=wpsn_snapshots_delete&snapshot_id=' . $id), 'wpsn_snapshots_delete_' . $id, 'wpsn_nonce');

            echo '<tr>';
            echo '<td>' . esc_html((string) $id) . '</td>';
            echo '<td>' . esc_html($created) . '</td>';
            echo '<td>' . esc_html($label) . '</td>';
            echo '<td><code>' . esc_html($ver) . '</code></td>';
            echo '<td>' . ($isBaseline ? '<strong>' . esc_html__('Yes', 'wp-seo-next') . '</strong>' : '—') . '</td>';
            echo '<td>';
            echo '<a class="button" href="' . esc_url($diffUrl) . '">' . esc_html__('Diff vs current', 'wp-seo-next') . '</a> ';
            echo '<a class="button" href="' . esc_url($setBaseUrl) . '">' . esc_html__('Set baseline', 'wp-seo-next') . '</a> ';
            echo '<a class="button" href="' . esc_url($deleteUrl) . '" onclick="return confirm(\'' . esc_js(__('Delete this snapshot?', 'wp-seo-next')) . '\');">' . esc_html__('Delete', 'wp-seo-next') . '</a>';
            echo '</td>';
            echo '</tr>';
        }

        echo '</tbody></table>';
    }

    public function handleCreateConfig(): void
    {
        if (!Capabilities::canManageSettings()) {
            wp_die(esc_html__('Permission denied.', 'wp-seo-next'));
        }

        check_admin_referer('wpsn_snapshots_create_config', 'wpsn_nonce');

        $label = isset($_POST['label']) ? (string) wp_unslash($_POST['label']) : '';
        $label = sanitize_text_field($label);

        $payload = get_option(SettingsRepository::OPTION_NAME, []);
        if (!is_array($payload)) {
            $payload = [];
        }

        $repo = new SnapshotRepository();
        $repo->create(SnapshotRepository::TYPE_CONFIG, $label, $payload);

        $url = add_query_arg(['page' => 'wpsn-snapshots', 'tab' => 'config', 'wpsn_notice' => 'created'], admin_url('tools.php'));
        wp_safe_redirect($url);
        exit;
    }

    public function handleRestoreConfig(): void
    {
        if (!Capabilities::canManageSettings()) {
            wp_die(esc_html__('Permission denied.', 'wp-seo-next'));
        }

        $id = isset($_GET['snapshot_id']) ? (int) $_GET['snapshot_id'] : 0;
        check_admin_referer('wpsn_snapshots_restore_config_' . $id, 'wpsn_nonce');

        $repo = new SnapshotRepository();
        $row = $repo->get($id);
        if (!is_array($row) || (string) ($row['type'] ?? '') !== SnapshotRepository::TYPE_CONFIG) {
            $url = add_query_arg(['page' => 'wpsn-snapshots', 'tab' => 'config', 'wpsn_notice' => 'error'], admin_url('tools.php'));
            wp_safe_redirect($url);
            exit;
        }

        $rawPayload = isset($row['payload_decoded']) && is_array($row['payload_decoded']) ? $row['payload_decoded'] : [];
        $payload = ConfigSnapshotService::extractSettings($rawPayload);

        // Safety: create a backup snapshot before overwriting.
        try {
            ConfigSnapshotService::createAutoSnapshot(
                sprintf('Auto backup: before FULL restore (snapshot #%d)', $id),
                [
                    'reason' => 'snapshots_full_restore',
                    'source_snapshot_id' => $id,
                ]
            );
        } catch (\Throwable $e) {
            // Never block restore.
        }
        

        // Unified revisions log (Block C): record settings restore so it can be rolled back.
        try {
            $logger = $this->container->get('logger');
            if ($this->container->get('settings')->moduleEnabled('revisions', true)) {
                RevisionsInstaller::maybeInstall();
                $revRepo = new RevisionsRepository($logger);
                $optName = SettingsRepository::OPTION_NAME;
                $current = get_option($optName, []);

                $revRepo->insert([
                    'object_type' => 'option',
                    'object_id' => 0,
                    'object_subtype' => $optName,
                    'action' => 'restore',
                    'source' => 'snapshots',
                    'actor_user_id' => (int) get_current_user_id(),
                    'before' => ['option' => ['name' => $optName, 'value' => $current]],
                    'after'  => ['option' => ['name' => $optName, 'value' => $payload]],
                    'meta' => [
                        'snapshot_id' => $id,
                    ],
                ]);
            }
        } catch (\Throwable $e) {
            // Never block restore.
        }

        update_option(SettingsRepository::OPTION_NAME, $payload, false);

        // Flush runtime cache.
        $this->container->get('settings')->flush();

        /**
         * Action fired after restoring settings from a snapshot.
         *
         * @since 0.66.0
         *
         * @param int $snapshotId
         */
        do_action('wpsn/stability/config_snapshot_restored', $id);

        $url = add_query_arg(['page' => 'wpsn-snapshots', 'tab' => 'config', 'wpsn_notice' => 'restored'], admin_url('tools.php'));
        wp_safe_redirect($url);
        exit;
    }

    public function handleRestoreConfigPartial(): void
    {
        if (!Capabilities::canManageSettings()) {
            wp_die(esc_html__('Permission denied.', 'wp-seo-next'));
        }

        $id = isset($_POST['snapshot_id']) ? (int) $_POST['snapshot_id'] : 0;
        check_admin_referer('wpsn_snapshots_restore_config_partial_' . $id, 'wpsn_nonce');

        $rawGroups = isset($_POST['groups']) ? (array) wp_unslash($_POST['groups']) : [];
        $groups = [];
        foreach ($rawGroups as $g) {
            $g = sanitize_key((string) $g);
            if ($g !== '' && ConfigSnapshotService::isKnownGroup($g)) {
                $groups[] = $g;
            }
        }
        $groups = array_values(array_unique($groups));

        if ($id <= 0 || empty($groups)) {
            $url = add_query_arg(['page' => 'wpsn-snapshots', 'tab' => 'config', 'wpsn_notice' => 'error'], admin_url('tools.php'));
            wp_safe_redirect($url);
            exit;
        }

        $repo = new SnapshotRepository();
        $row = $repo->get($id);
        if (!is_array($row) || (string) ($row['type'] ?? '') !== SnapshotRepository::TYPE_CONFIG) {
            $url = add_query_arg(['page' => 'wpsn-snapshots', 'tab' => 'config', 'wpsn_notice' => 'error'], admin_url('tools.php'));
            wp_safe_redirect($url);
            exit;
        }

        $rawPayload = isset($row['payload_decoded']) && is_array($row['payload_decoded']) ? $row['payload_decoded'] : [];
        $snapSettings = ConfigSnapshotService::extractSettings($rawPayload);

        $current = get_option(SettingsRepository::OPTION_NAME, []);
        if (!is_array($current)) {
            $current = [];
        }

        // Safety: create a backup snapshot before overwriting.
        try {
            ConfigSnapshotService::createAutoSnapshot(
                sprintf('Auto backup: before PARTIAL restore (snapshot #%d)', $id),
                [
                    'reason' => 'snapshots_partial_restore',
                    'source_snapshot_id' => $id,
                    'groups' => $groups,
                ]
            );
        } catch (\Throwable $e) {
            // Never block restore.
        }

        $merged = ConfigSnapshotService::applyGroups($current, $snapSettings, $groups);

        // Unified revisions log (Block C): record settings restore so it can be rolled back.
        try {
            $logger = $this->container->get('logger');
            if ($this->container->get('settings')->moduleEnabled('revisions', true)) {
                RevisionsInstaller::maybeInstall();
                $revRepo = new RevisionsRepository($logger);
                $optName = SettingsRepository::OPTION_NAME;

                $revRepo->insert([
                    'object_type' => 'option',
                    'object_id' => 0,
                    'object_subtype' => $optName,
                    'action' => 'restore_partial',
                    'source' => 'snapshots',
                    'actor_user_id' => (int) get_current_user_id(),
                    'before' => ['option' => ['name' => $optName, 'value' => $current]],
                    'after'  => ['option' => ['name' => $optName, 'value' => $merged]],
                    'meta' => [
                        'snapshot_id' => $id,
                        'groups' => $groups,
                    ],
                ]);
            }
        } catch (\Throwable $e) {
            // Never block restore.
        }

        update_option(SettingsRepository::OPTION_NAME, $merged, false);

        $this->container->get('settings')->flush();

        do_action('wpsn/stability/config_snapshot_restored', $id);

        $url = add_query_arg([
            'page' => 'wpsn-snapshots',
            'tab' => 'config',
            'diff' => $id,
            'wpsn_notice' => 'restored',
        ], admin_url('tools.php'));
        wp_safe_redirect($url);
        exit;
    }

    public function handleCreateHead(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('Permission denied.', 'wp-seo-next'));
        }

        check_admin_referer('wpsn_snapshots_create_head', 'wpsn_nonce');

        $label = isset($_POST['label']) ? (string) wp_unslash($_POST['label']) : '';
        $label = sanitize_text_field($label);

        $head = new HeadSnapshotService($this->container);
        $cases = $head->buildDefaultCases();
        $payload = $head->buildSnapshotPayload($cases);

        $repo = new SnapshotRepository();
        $id = $repo->create(SnapshotRepository::TYPE_HEAD, $label, $payload);

        // If no baseline set, set this as baseline automatically.
        if ($head->getBaselineId() <= 0 && $id > 0) {
            $head->setBaselineId($id);
        }

        // New snapshot typically becomes the new reference point.
        delete_option(HeadDiffService::OPTION_NOTICE);

        $url = add_query_arg(['page' => 'wpsn-snapshots', 'tab' => 'head', 'wpsn_notice' => 'created'], admin_url('tools.php'));
        wp_safe_redirect($url);
        exit;
    }

    public function handleSetHeadBaseline(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('Permission denied.', 'wp-seo-next'));
        }

        $id = isset($_GET['snapshot_id']) ? (int) $_GET['snapshot_id'] : 0;
        check_admin_referer('wpsn_snapshots_set_head_baseline_' . $id, 'wpsn_nonce');

        $repo = new SnapshotRepository();
        $row = $repo->get($id);
        if (!is_array($row) || (string) ($row['type'] ?? '') !== SnapshotRepository::TYPE_HEAD) {
            $url = add_query_arg(['page' => 'wpsn-snapshots', 'tab' => 'head', 'wpsn_notice' => 'error'], admin_url('tools.php'));
            wp_safe_redirect($url);
            exit;
        }

        $head = new HeadSnapshotService($this->container);
        $head->setBaselineId($id);

        delete_option(HeadDiffService::OPTION_NOTICE);

        $url = add_query_arg(['page' => 'wpsn-snapshots', 'tab' => 'head', 'wpsn_notice' => 'baseline_set'], admin_url('tools.php'));
        wp_safe_redirect($url);
        exit;
    }

    public function handleDeleteSnapshot(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('Permission denied.', 'wp-seo-next'));
        }

        $id = isset($_GET['snapshot_id']) ? (int) $_GET['snapshot_id'] : 0;
        check_admin_referer('wpsn_snapshots_delete_' . $id, 'wpsn_nonce');

        $repo = new SnapshotRepository();
        $row = $repo->get($id);
        if (!is_array($row)) {
            $url = add_query_arg(['page' => 'wpsn-snapshots', 'tab' => 'config', 'wpsn_notice' => 'error'], admin_url('tools.php'));
            wp_safe_redirect($url);
            exit;
        }

        // If deleting baseline head snapshot, clear baseline pointer.
        if ((string) ($row['type'] ?? '') === SnapshotRepository::TYPE_HEAD) {
            $head = new HeadSnapshotService($this->container);
            if ($head->getBaselineId() === (int) $id) {
                $head->setBaselineId(0);
            }
        }

        $repo->delete($id);

        $tab = isset($_GET['tab']) ? sanitize_key((string) wp_unslash($_GET['tab'])) : 'config';
        if (!in_array($tab, ['config', 'head'], true)) {
            $tab = 'config';
        }

        $url = add_query_arg(['page' => 'wpsn-snapshots', 'tab' => $tab, 'wpsn_notice' => 'deleted'], admin_url('tools.php'));
        wp_safe_redirect($url);
        exit;
    }

    private function renderConfigDiff(int $snapshotId): void
    {
        $repo = new SnapshotRepository();
        $row = $repo->get($snapshotId);
        if (!is_array($row) || (string) ($row['type'] ?? '') !== SnapshotRepository::TYPE_CONFIG) {
            return;
        }

        $rawPayload = isset($row['payload_decoded']) && is_array($row['payload_decoded']) ? $row['payload_decoded'] : [];
        $snap = ConfigSnapshotService::extractSettings($rawPayload);
        $current = get_option(SettingsRepository::OPTION_NAME, []);
        if (!is_array($current)) {
            $current = [];
        }

        $a = $this->flatten($snap);
        $b = $this->flatten($current);
        $grouped = ConfigSnapshotService::groupChanges($a, $b);

        echo '<h3>' . esc_html(sprintf(__('Diff: snapshot #%d vs current', 'wp-seo-next'), $snapshotId)) . '</h3>';

        if (empty($grouped)) {
            echo '<p>' . esc_html__('No differences detected.', 'wp-seo-next') . '</p>';
            return;
        }

        // Selective restore form (restore by groups).
        $groupsDef = ConfigSnapshotService::groups();
        echo '<div class="notice notice-info" style="padding:10px 12px;">';
        echo '<p style="margin:0 0 8px 0;"><strong>' . esc_html__('Selective restore', 'wp-seo-next') . '</strong><br />';
        echo '<span class="description">' . esc_html__('Restore only the selected parts from this snapshot. A safety backup snapshot is created automatically before applying.', 'wp-seo-next') . '</span></p>';

        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
        echo '<input type="hidden" name="action" value="wpsn_snapshots_restore_config_partial" />';
        echo '<input type="hidden" name="snapshot_id" value="' . esc_attr((string) $snapshotId) . '" />';
        wp_nonce_field('wpsn_snapshots_restore_config_partial_' . $snapshotId, 'wpsn_nonce');

        echo '<div style="display:flex;flex-wrap:wrap;gap:10px 18px;margin:8px 0;">';
        foreach ($grouped as $gid => $items) {
            $label = $gid;
            if ($gid !== 'other' && isset($groupsDef[$gid]['label'])) {
                $label = (string) $groupsDef[$gid]['label'];
            } elseif ($gid === 'other') {
                $label = __('Other', 'wp-seo-next');
            }

            $count = is_array($items) ? count($items) : 0;
            echo '<label style="display:inline-flex;align-items:center;gap:6px;">';
            if ($gid === 'other') {
                echo '<input type="checkbox" disabled />';
            } else {
                echo '<input type="checkbox" name="groups[]" value="' . esc_attr((string) $gid) . '" />';
            }
            echo '<span>' . esc_html($label) . ' <span class="description">(' . esc_html((string) $count) . ')</span></span>';
            echo '</label>';
        }
        echo '</div>';

        echo '<button type="submit" class="button button-primary" onclick="return confirm(\'' . esc_js(__('Restore selected groups from this snapshot?', 'wp-seo-next')) . '\');">' . esc_html__('Restore selected groups', 'wp-seo-next') . '</button>';
        echo '</form>';
        echo '</div>';

        // Grouped diff tables.
        foreach ($grouped as $gid => $items) {
            $label = $gid;
            if ($gid !== 'other' && isset($groupsDef[$gid]['label'])) {
                $label = (string) $groupsDef[$gid]['label'];
            } elseif ($gid === 'other') {
                $label = __('Other', 'wp-seo-next');
            }

            $count = is_array($items) ? count($items) : 0;
            echo '<h4 style="margin-top:18px;">' . esc_html($label) . ' <span class="description">(' . esc_html((string) $count) . ')</span></h4>';
            echo '<table class="widefat striped"><thead><tr>';
            echo '<th>' . esc_html__('Setting key', 'wp-seo-next') . '</th>';
            echo '<th>' . esc_html__('Snapshot', 'wp-seo-next') . '</th>';
            echo '<th>' . esc_html__('Current', 'wp-seo-next') . '</th>';
            echo '</tr></thead><tbody>';

            if (is_array($items)) {
                foreach ($items as $c) {
                    if (!is_array($c)) {
                        continue;
                    }
                    echo '<tr>';
                    echo '<td><code>' . esc_html((string) ($c['key'] ?? '')) . '</code></td>';
                    echo '<td><code>' . esc_html((string) ($c['snapshot'] ?? '')) . '</code></td>';
                    echo '<td><code>' . esc_html((string) ($c['current'] ?? '')) . '</code></td>';
                    echo '</tr>';
                }
            }

            echo '</tbody></table>';
        }
    }

    private function renderHeadDiff(int $snapshotId): void
    {
        $repo = new SnapshotRepository();
        $row = $repo->get($snapshotId);
        if (!is_array($row) || (string) ($row['type'] ?? '') !== SnapshotRepository::TYPE_HEAD) {
            return;
        }

        $payload = isset($row['payload_decoded']) && is_array($row['payload_decoded']) ? $row['payload_decoded'] : [];
        $cases = isset($payload['cases']) && is_array($payload['cases']) ? $payload['cases'] : [];
        if (empty($cases)) {
            return;
        }

        $head = new HeadSnapshotService($this->container);
        $currentPayload = $head->buildSnapshotPayload($cases);
        $currentCases = isset($currentPayload['cases']) && is_array($currentPayload['cases']) ? $currentPayload['cases'] : [];

        $currentByKey = [];
        foreach ($currentCases as $cc) {
            if (!is_array($cc)) {
                continue;
            }
            $k = isset($cc['key']) ? (string) $cc['key'] : '';
            if ($k !== '') {
                $currentByKey[$k] = $cc;
            }
        }

        echo '<h3>' . esc_html(sprintf(__('Diff: head snapshot #%d vs current', 'wp-seo-next'), $snapshotId)) . '</h3>';

        foreach ($cases as $case) {
            if (!is_array($case)) {
                continue;
            }
            $k = isset($case['key']) ? (string) $case['key'] : '';
            if ($k === '' || !isset($currentByKey[$k])) {
                continue;
            }
            $cc = $currentByKey[$k];

            $label = isset($case['label']) ? (string) $case['label'] : $k;

            $oldHtmlHash = isset($case['head_hash']) ? (string) $case['head_hash'] : '';
            $newHtmlHash = isset($cc['head_hash']) ? (string) $cc['head_hash'] : '';

            $oldStructHash = isset($case['struct_hash']) ? (string) $case['struct_hash'] : '';
            $newStructHash = isset($cc['struct_hash']) ? (string) $cc['struct_hash'] : '';

            $oldStruct = isset($case['head_struct']) && is_array($case['head_struct']) ? $case['head_struct'] : null;
            $newStruct = isset($cc['head_struct']) && is_array($cc['head_struct']) ? $cc['head_struct'] : null;

            $hashChanged = false;
            if ($oldStructHash !== '' && $newStructHash !== '') {
                $hashChanged = !hash_equals($oldStructHash, $newStructHash);
            } elseif ($oldHtmlHash !== '' && $newHtmlHash !== '') {
                $hashChanged = !hash_equals($oldHtmlHash, $newHtmlHash);
            }

            $rows = [];
            if (is_array($oldStruct) && is_array($newStruct)) {
                // Prefer structured diff (stable, tag-level).
                $rows = $this->diffHeadStruct($oldStruct, $newStruct);
            } else {
                // Back-compat fallback: resolved fields diff.
                $old = isset($case['resolved']) && is_array($case['resolved']) ? $case['resolved'] : [];
                $new = isset($cc['resolved']) && is_array($cc['resolved']) ? $cc['resolved'] : [];
                $fields = ['title', 'description', 'canonical', 'robots', 'og_title', 'og_description', 'og_image', 'twitter_title', 'twitter_description', 'twitter_image'];
                foreach ($fields as $f) {
                    $ov = $old[$f] ?? '';
                    $nv = $new[$f] ?? '';

                    if (is_array($ov)) {
                        $ov = wp_json_encode($ov);
                    }
                    if (is_array($nv)) {
                        $nv = wp_json_encode($nv);
                    }

                    $ov = is_string($ov) ? $ov : (string) $ov;
                    $nv = is_string($nv) ? $nv : (string) $nv;

                    if ($ov !== $nv) {
                        $rows[] = [
                            'field' => $f,
                            'snapshot' => $ov,
                            'current' => $nv,
                        ];
                    }
                }
            }

            echo '<h4>' . esc_html($label) . '</h4>';
            echo '<p><code>' . esc_html($k) . '</code> ' . ($hashChanged ? '<span style="color:#b32d2e;font-weight:bold;">' . esc_html__('CHANGED', 'wp-seo-next') . '</span>' : '<span style="color:#007017;font-weight:bold;">' . esc_html__('OK', 'wp-seo-next') . '</span>') . '</p>';

            if ($oldStructHash !== '' || $newStructHash !== '') {
                echo '<p class="description">' . esc_html__('Struct hash:', 'wp-seo-next') . ' <code>' . esc_html($oldStructHash) . '</code> → <code>' . esc_html($newStructHash) . '</code></p>';
            }
            echo '<p class="description">' . esc_html__('HTML hash:', 'wp-seo-next') . ' <code>' . esc_html($oldHtmlHash) . '</code> → <code>' . esc_html($newHtmlHash) . '</code></p>';

            if (empty($rows)) {
                echo '<p>' . esc_html__('No tag-level changes detected for this case.', 'wp-seo-next') . '</p>';
                continue;
            }

            echo '<table class="widefat striped" style="margin-bottom: 18px;"><thead><tr>';
            echo '<th>' . esc_html__('Field', 'wp-seo-next') . '</th>';
            echo '<th>' . esc_html__('Snapshot', 'wp-seo-next') . '</th>';
            echo '<th>' . esc_html__('Current', 'wp-seo-next') . '</th>';
            echo '</tr></thead><tbody>';

            foreach ($rows as $r) {
                echo '<tr>';
                echo '<td><code>' . esc_html((string) $r['field']) . '</code></td>';
                echo '<td><code>' . esc_html((string) $r['snapshot']) . '</code></td>';
                echo '<td><code>' . esc_html((string) $r['current']) . '</code></td>';
                echo '</tr>';
            }

            echo '</tbody></table>';

            // Debug: show head struct / resolved JSON in a collapsible block.
            echo '<details style="margin: 0 0 18px 0;">';
            echo '<summary>' . esc_html__('Debug details', 'wp-seo-next') . '</summary>';
            echo '<div style="margin-top:8px;">';
            if (is_array($oldStruct) || is_array($newStruct)) {
                echo '<p><strong>' . esc_html__('Head struct (snapshot / current)', 'wp-seo-next') . '</strong></p>';
                echo '<div style="display:flex; gap:12px;">';
                echo '<pre style="flex:1;max-height:220px;overflow:auto;background:#fff;border:1px solid #ccd0d4;padding:8px;">' . esc_html(wp_json_encode(is_array($oldStruct) ? $oldStruct : [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)) . '</pre>';
                echo '<pre style="flex:1;max-height:220px;overflow:auto;background:#fff;border:1px solid #ccd0d4;padding:8px;">' . esc_html(wp_json_encode(is_array($newStruct) ? $newStruct : [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)) . '</pre>';
                echo '</div>';
            }
            $oldResolved = isset($case['resolved']) && is_array($case['resolved']) ? $case['resolved'] : [];
            $newResolved = isset($cc['resolved']) && is_array($cc['resolved']) ? $cc['resolved'] : [];
            echo '<p><strong>' . esc_html__('Resolved fields (snapshot / current)', 'wp-seo-next') . '</strong></p>';
            echo '<div style="display:flex; gap:12px;">';
            echo '<pre style="flex:1;max-height:220px;overflow:auto;background:#fff;border:1px solid #ccd0d4;padding:8px;">' . esc_html(wp_json_encode($oldResolved, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)) . '</pre>';
            echo '<pre style="flex:1;max-height:220px;overflow:auto;background:#fff;border:1px solid #ccd0d4;padding:8px;">' . esc_html(wp_json_encode($newResolved, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)) . '</pre>';
            echo '</div>';
            echo '</div>';
            echo '</details>';
        }
    }

    /**
     * Diff stable head struct payloads.
     *
     * @param array<string,mixed> $old
     * @param array<string,mixed> $new
     * @return array<int,array{field:string,snapshot:string,current:string}>
     */
    private function diffHeadStruct(array $old, array $new): array
    {
        $rows = [];

        $oldTitle = isset($old['title']) && is_string($old['title']) ? (string) $old['title'] : '';
        $newTitle = isset($new['title']) && is_string($new['title']) ? (string) $new['title'] : '';
        if ($oldTitle !== $newTitle) {
            $rows[] = ['field' => 'title', 'snapshot' => $oldTitle, 'current' => $newTitle];
        }

        $oldSchemaHash = isset($old['schema_hash']) && is_string($old['schema_hash']) ? (string) $old['schema_hash'] : '';
        $newSchemaHash = isset($new['schema_hash']) && is_string($new['schema_hash']) ? (string) $new['schema_hash'] : '';
        if ($oldSchemaHash !== $newSchemaHash) {
            $rows[] = ['field' => 'schema_hash', 'snapshot' => $oldSchemaHash, 'current' => $newSchemaHash];
        }

        $oldTypes = isset($old['schema_types']) && is_array($old['schema_types']) ? $old['schema_types'] : [];
        $newTypes = isset($new['schema_types']) && is_array($new['schema_types']) ? $new['schema_types'] : [];
        $oldTypesS = wp_json_encode($oldTypes, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        $newTypesS = wp_json_encode($newTypes, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        if ((string) $oldTypesS !== (string) $newTypesS) {
            $rows[] = ['field' => 'schema_types', 'snapshot' => (string) $oldTypesS, 'current' => (string) $newTypesS];
        }

        $oldMeta = isset($old['meta']) && is_array($old['meta']) ? $old['meta'] : [];
        $newMeta = isset($new['meta']) && is_array($new['meta']) ? $new['meta'] : [];
        $rows = array_merge($rows, $this->diffMap($oldMeta, $newMeta, 'meta.'));

        $oldLink = isset($old['link']) && is_array($old['link']) ? $old['link'] : [];
        $newLink = isset($new['link']) && is_array($new['link']) ? $new['link'] : [];
        $rows = array_merge($rows, $this->diffMap($oldLink, $newLink, 'link.'));

        return $rows;
    }

    /**
     * @param array<string,mixed> $old
     * @param array<string,mixed> $new
     * @return array<int,array{field:string,snapshot:string,current:string}>
     */
    private function diffMap(array $old, array $new, string $prefix): array
    {
        $rows = [];
        $keys = array_unique(array_merge(array_keys($old), array_keys($new)));
        sort($keys);

        foreach ($keys as $k) {
            $ov = $old[$k] ?? '';
            $nv = $new[$k] ?? '';

            if (is_array($ov) || is_object($ov)) {
                $ov = wp_json_encode($ov, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            }
            if (is_array($nv) || is_object($nv)) {
                $nv = wp_json_encode($nv, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            }

            $ov = is_string($ov) ? $ov : (string) $ov;
            $nv = is_string($nv) ? $nv : (string) $nv;

            if ($ov !== $nv) {
                $rows[] = [
                    'field' => $prefix . (string) $k,
                    'snapshot' => $ov,
                    'current' => $nv,
                ];
            }
        }

        return $rows;
    }

    /**
     * Flatten a nested settings array into dot-notation strings for simple diff display.
     *
     * @param array<string,mixed> $arr
     * @param string $prefix
     * @return array<string,string>
     */
    private function flatten(array $arr, string $prefix = ''): array
    {
        $out = [];
        foreach ($arr as $k => $v) {
            $k = is_int($k) ? (string) $k : sanitize_key((string) $k);
            if ($k === '') {
                continue;
            }
            $path = $prefix !== '' ? $prefix . '.' . $k : $k;

            if (is_array($v)) {
                $out = array_merge($out, $this->flatten($v, $path));
            } elseif (is_bool($v)) {
                $out[$path] = $v ? 'true' : 'false';
            } elseif ($v === null) {
                $out[$path] = '';
            } else {
                $out[$path] = (string) $v;
            }
        }
        return $out;
    }
}
