<?php
/**
 * Plugin Name: GrowthOS
 * Description: AI Chat & SEO/GEO for WordPress, powered by the GrowthOS SaaS platform.
 * Version: 0.2.44
 * Author: GUANGERGAOZHI
 * Requires at least: 6.0
 * Requires PHP: 7.4
 */

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

final class Guangergaozhi_Ai_Growth_Plugin {
    private const VERSION = '0.2.44';
    private const PLUGIN_SLUG = 'guangergaozhi-ai-growth';
    private const OPTION_KEY = 'gza_ai_growth_options';
    private const EVENT_QUEUE_OPTION = 'gza_ai_growth_event_queue';
    private const CONTENT_MANIFEST_OPTION = 'gza_ai_growth_content_manifest';
    private const DB_VERSION_OPTION = 'gza_ai_growth_db_version';
    private const DB_VERSION = '2026-05-26-01';
    private const CHAT_CONFIG_CACHE_OPTION = 'gza_ai_growth_chat_config_cache';
    private const CRON_VERSION_OPTION = 'gza_ai_growth_cron_version';
    private const CRON_VERSION = '2026-05-25-01';
    private const NONCE_ACTION = 'gza_ai_growth_save';
    private const TRANSIENT_PREFIX = 'gza_ai_overlay_';
    private const UPDATE_TRANSIENT_PREFIX = 'gza_ai_growth_update_manifest_';
    private const FRONTEND_TIMEOUT = 2;
    private const CONFIG_TIMEOUT = 4;
    private const ADMIN_TIMEOUT = 8;
    private const JOB_TIMEOUT = 5;
    private const CHAT_TIMEOUT = 60;
    private const SYNC_TIMEOUT = 30;
    private const FAILURE_COOLDOWN_SECONDS = 300;
    private const SYNC_PAGE_SIZE = 80;
    private const SYNC_MAX_ITEMS = 5000;
    private const SYNC_CONTENT_MAX_CHARS = 120000;
    private const CLOUD_SYNC_BATCH_SIZE = 180;
    private const EVENT_QUEUE_MAX_ITEMS = 200;

    private static ?Guangergaozhi_Ai_Growth_Plugin $instance = null;
    private ?array $current_seo_overlay = null;

    public static function instance(): Guangergaozhi_Ai_Growth_Plugin {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    private function __construct() {
        add_action('admin_menu', [$this, 'register_admin_page']);
        add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
        add_action('admin_post_gza_ai_growth_save', [$this, 'handle_admin_post']);
        add_action('rest_api_init', [$this, 'register_rest_routes']);
        add_action('init', [$this, 'maybe_schedule_job_runner']);
        add_action('gza_ai_growth_process_jobs', [$this, 'process_cloud_jobs']);
        add_action('gza_ai_growth_incremental_sync', [$this, 'process_incremental_sync']);
        add_action('gza_ai_growth_flush_events', [$this, 'flush_queued_events']);
        add_filter('cron_schedules', [$this, 'register_cron_schedules']);
        add_action('wp', [$this, 'prepare_seo_overlay_output']);
        add_action('wp_head', [$this, 'render_seo_overlay'], 1);
        add_action('wp_footer', [$this, 'render_chat_widget']);
        add_filter('pre_get_document_title', [$this, 'filter_document_title']);
        add_filter('site_transient_update_plugins', [$this, 'check_for_plugin_update']);
        add_filter('plugins_api', [$this, 'plugin_information'], 20, 3);
        add_filter('http_request_host_is_external', [$this, 'allow_configured_update_package_host'], 10, 3);
        add_filter('http_allowed_safe_ports', [$this, 'allow_configured_update_package_port'], 10, 3);
    }

    public function register_rest_routes(): void {
        register_rest_route('gza-ai-growth/v1', '/chat/config', [
            'methods' => 'GET',
            'callback' => [$this, 'rest_chat_config'],
            'permission_callback' => '__return_true',
        ]);
        register_rest_route('gza-ai-growth/v1', '/chat/message', [
            'methods' => 'POST',
            'callback' => [$this, 'rest_chat_message'],
            'permission_callback' => '__return_true',
        ]);
        register_rest_route('gza-ai-growth/v1', '/chat/messages', [
            'methods' => 'GET',
            'callback' => [$this, 'rest_chat_messages'],
            'permission_callback' => '__return_true',
        ]);
        register_rest_route('gza-ai-growth/v1', '/chat/presence', [
            'methods' => 'GET',
            'callback' => [$this, 'rest_chat_presence'],
            'permission_callback' => '__return_true',
        ]);
        register_rest_route('gza-ai-growth/v1', '/events', [
            'methods' => 'POST',
            'callback' => [$this, 'rest_track_events'],
            'permission_callback' => '__return_true',
        ]);
        register_rest_route('gza-ai-growth/v1', '/jobs/wake', [
            'methods' => 'POST',
            'callback' => [$this, 'rest_wake_jobs'],
            'permission_callback' => '__return_true',
        ]);
    }

    public function register_admin_page(): void {
        add_menu_page(
            'GrowthOS',
            'GrowthOS',
            'manage_options',
            'gza-ai-growth',
            [$this, 'render_admin_page'],
            $this->admin_menu_icon(),
            56
        );
    }

    public function enqueue_admin_assets(string $hook): void {
        if ($hook !== 'toplevel_page_gza-ai-growth') {
            return;
        }

        wp_enqueue_media();
    }

    private function admin_menu_icon(): string {
        $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill="black" d="M10 2.1a7.9 7.9 0 1 0 7.9 7.9h-3.2a4.7 4.7 0 1 1-1.25-3.18l-2.2 2.2h6.6V2.45l-2.17 2.17A7.86 7.86 0 0 0 10 2.1Z"/><path fill="black" d="M8.25 12.95 9.35 7.5h1.9l-1.1 5.45h-1.9Zm3.05 0L12.4 7.5h1.9l-1.1 5.45h-1.9Z"/></svg>';
        return 'data:image/svg+xml;base64,' . base64_encode($svg);
    }

    public function render_admin_page(): void {
        if (!current_user_can('manage_options')) {
            return;
        }

        $this->render_modern_admin_page();
        return;

        $options = $this->get_options();
        $status = isset($_GET['gza_status']) ? sanitize_text_field(wp_unslash($_GET['gza_status'])) : '';
        ?>
        <div class="wrap">
            <h1>GrowthOS</h1>
            <p>Connect this WordPress site to the cloud SaaS API for AI customer service and SEO/GEO optimization.</p>

            <?php if ($status): ?>
                <div class="notice notice-info is-dismissible"><p><?php echo esc_html($status); ?></p></div>
            <?php endif; ?>

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

                <table class="form-table" role="presentation">
                    <tr>
                        <th scope="row"><label for="gza_api_base">Cloud API Base URL</label></th>
                        <td>
                            <input class="regular-text" id="gza_api_base" name="api_base" type="url" value="<?php echo esc_attr($options['api_base']); ?>" placeholder="https://www.tui161.com" />
                        </td>
                    </tr>
                    <tr>
                        <th scope="row"><label for="gza_license_code">License Code</label></th>
                        <td>
                            <input class="regular-text" id="gza_license_code" name="license_code" type="text" value="" autocomplete="off" placeholder="Paste license code for activation" />
                            <p class="description">The license code is only used during activation. The plugin stores the returned site token.</p>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">Connection</th>
                        <td>
                            <p><strong>Plugin Version:</strong> <?php echo esc_html(self::VERSION); ?></p>
                            <p><strong>Cloud API:</strong> <?php echo esc_html($options['api_base'] ?: '-'); ?></p>
                            <p><strong>Tenant:</strong> <?php echo esc_html($options['tenant_id'] ?: '-'); ?></p>
                            <p><strong>Site:</strong> <?php echo esc_html($options['site_id'] ?: '-'); ?></p>
                            <p><strong>Token:</strong> <?php echo $options['site_token'] ? 'Saved' : 'Not activated'; ?></p>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">Diagnostics</th>
                        <td>
                            <p><strong>Last Status Check:</strong> <?php echo esc_html($options['last_status_checked_at'] ?: '-'); ?></p>
                            <p><strong>Last Sync:</strong> <?php echo esc_html($options['last_sync_at'] ?: '-'); ?><?php echo $options['last_sync_count'] !== '' ? ' / ' . esc_html((string) $options['last_sync_count']) . ' items' : ''; ?></p>
                            <p><strong>Last Cloud Job:</strong> <?php echo esc_html($options['last_job_at'] ?: '-'); ?><?php echo $options['last_job_count'] !== '' ? ' / ' . esc_html((string) $options['last_job_count']) . ' processed' : ''; ?></p>
                            <p><strong>Last Update Check:</strong> <?php echo esc_html($options['last_update_checked_at'] ?: '-'); ?><?php echo $options['last_update_version'] ? ' / latest ' . esc_html($options['last_update_version']) : ''; ?></p>
                            <p><strong>Cloud Cooldown:</strong> <?php echo $this->is_cloud_temporarily_unavailable($options) ? 'Until ' . esc_html(date_i18n('Y-m-d H:i:s', (int) $options['cloud_unavailable_until'])) : 'No'; ?></p>
                            <p><strong>Last Error:</strong> <?php echo esc_html($options['last_error'] ?: '-'); ?></p>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">Features</th>
                        <td>
                            <label>
                                <input type="checkbox" name="inject_seo" value="1" <?php checked($options['inject_seo'], true); ?> />
                                Inject approved SEO/GEO metadata and structured data
                            </label>
                            <br />
                            <label>
                                <input type="checkbox" name="chat_widget" value="1" <?php checked($options['chat_widget'], true); ?> />
                                Enable AI chat widget when the cloud widget endpoint is available
                            </label>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">Chat Theme</th>
                        <td>
                            <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:12px;max-width:980px;">
                                <?php foreach ($this->chat_theme_presets() as $theme_key => $theme): ?>
                                    <label style="display:flex;gap:10px;align-items:flex-start;border:1px solid <?php echo $options['chat_theme'] === $theme_key ? '#14b8a6' : '#d8e2ef'; ?>;border-radius:12px;padding:12px;background:<?php echo $options['chat_theme'] === $theme_key ? '#f0fdfa' : '#fff'; ?>;cursor:pointer;">
                                        <input type="radio" name="chat_theme" value="<?php echo esc_attr($theme_key); ?>" <?php checked($options['chat_theme'], $theme_key); ?> style="margin-top:2px;" />
                                        <span style="display:grid;gap:7px;min-width:0;">
                                            <strong><?php echo esc_html($theme['label']); ?></strong>
                                            <small style="color:#64748b;line-height:1.45;"><?php echo esc_html($theme['description']); ?></small>
                                            <span style="display:flex;gap:5px;align-items:center;">
                                                <i style="width:22px;height:22px;border-radius:999px;background:<?php echo esc_attr($theme['primary']); ?>;display:inline-block;border:1px solid rgba(15,23,42,.12);"></i>
                                                <i style="width:22px;height:22px;border-radius:999px;background:<?php echo esc_attr($theme['panel']); ?>;display:inline-block;border:1px solid rgba(15,23,42,.12);"></i>
                                                <i style="width:22px;height:22px;border-radius:999px;background:<?php echo esc_attr($theme['panel_soft']); ?>;display:inline-block;border:1px solid rgba(15,23,42,.12);"></i>
                                                <i style="width:22px;height:22px;border-radius:999px;background:<?php echo esc_attr($theme['text']); ?>;display:inline-block;border:1px solid rgba(15,23,42,.12);"></i>
                                            </span>
                                        </span>
                                    </label>
                                <?php endforeach; ?>
                                <label style="display:flex;gap:10px;align-items:flex-start;border:1px solid <?php echo $options['chat_theme'] === 'custom' ? '#14b8a6' : '#d8e2ef'; ?>;border-radius:12px;padding:12px;background:<?php echo $options['chat_theme'] === 'custom' ? '#f0fdfa' : '#fff'; ?>;cursor:pointer;">
                                    <input type="radio" name="chat_theme" value="custom" <?php checked($options['chat_theme'], 'custom'); ?> style="margin-top:2px;" />
                                    <span style="display:grid;gap:7px;min-width:0;">
                                        <strong>Custom / 自定义</strong>
                                        <small style="color:#64748b;line-height:1.45;">Use the color pickers below for brand-specific sites.</small>
                                        <span style="display:flex;gap:5px;align-items:center;">
                                            <i style="width:22px;height:22px;border-radius:999px;background:<?php echo esc_attr($options['chat_primary']); ?>;display:inline-block;border:1px solid rgba(15,23,42,.12);"></i>
                                            <i style="width:22px;height:22px;border-radius:999px;background:<?php echo esc_attr($options['chat_panel']); ?>;display:inline-block;border:1px solid rgba(15,23,42,.12);"></i>
                                            <i style="width:22px;height:22px;border-radius:999px;background:<?php echo esc_attr($options['chat_text']); ?>;display:inline-block;border:1px solid rgba(15,23,42,.12);"></i>
                                        </span>
                                    </span>
                                </label>
                            </div>
                            <p class="description">Theme colors are controlled by this WordPress plugin only. SaaS no longer overrides the widget theme.</p>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">Custom Colors</th>
                        <td>
                            <label>Primary <input name="chat_primary" type="color" value="<?php echo esc_attr($options['chat_primary']); ?>" /></label>
                            <label style="margin-left:16px;">Panel <input name="chat_panel" type="color" value="<?php echo esc_attr($options['chat_panel']); ?>" /></label>
                            <label style="margin-left:16px;">Text <input name="chat_text" type="color" value="<?php echo esc_attr($options['chat_text']); ?>" /></label>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row"><label for="gza_chat_avatar_url">Chat Avatar</label></th>
                        <td>
                            <div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
                                <span id="gza_chat_avatar_preview" style="width:48px;height:48px;border-radius:50%;display:grid;place-items:center;overflow:hidden;background:#111827;color:#fff;font-weight:700;">
                                    <?php if (!empty($options['chat_avatar_url'])): ?>
                                        <img src="<?php echo esc_url($options['chat_avatar_url']); ?>" alt="" style="width:100%;height:100%;object-fit:cover;" />
                                    <?php else: ?>
                                        AI
                                    <?php endif; ?>
                                </span>
                                <input class="regular-text" id="gza_chat_avatar_url" name="chat_avatar_url" type="url" value="<?php echo esc_attr($options['chat_avatar_url']); ?>" placeholder="https://example.com/avatar.jpg" />
                                <button class="button" id="gza_chat_avatar_upload" type="button">Upload / Select</button>
                                <button class="button" id="gza_chat_avatar_clear" type="button">Clear</button>
                            </div>
                            <p class="description">Upload from the WordPress media library or paste an image URL. Square images work best.</p>
                        </td>
                    </tr>
                </table>

                <p class="submit">
                    <button class="button button-primary" name="gza_action" value="save" type="submit">Save Settings</button>
                    <button class="button" name="gza_action" value="activate" type="submit">Activate / Reconnect</button>
                    <button class="button" name="gza_action" value="status" type="submit">Check Status</button>
                    <button class="button" name="gza_action" value="sync" type="submit">Sync Content</button>
                    <button class="button" name="gza_action" value="jobs" type="submit">Process Cloud Jobs</button>
                    <button class="button" name="gza_action" value="update_check" type="submit">Check Plugin Update</button>
                </p>
            </form>
            <script>
            (function(){
                var upload=document.getElementById('gza_chat_avatar_upload');
                var clear=document.getElementById('gza_chat_avatar_clear');
                var input=document.getElementById('gza_chat_avatar_url');
                var preview=document.getElementById('gza_chat_avatar_preview');
                function render(value){preview.innerHTML=value?'<img src="'+value.replace(/"/g,'&quot;')+'" alt="" style="width:100%;height:100%;object-fit:cover;" />':'AI';}
                if(input){input.addEventListener('input',function(){render(input.value.trim());});}
                if(clear){clear.addEventListener('click',function(){input.value='';render('');});}
                if(upload){upload.addEventListener('click',function(){
                    if(!window.wp||!wp.media){return;}
                    var frame=wp.media({title:'Select chat avatar',button:{text:'Use this avatar'},multiple:false,library:{type:'image'}});
                    frame.on('select',function(){
                        var attachment=frame.state().get('selection').first().toJSON();
                        input.value=attachment.url||'';
                        render(input.value);
                    });
                    frame.open();
                });}
            })();
            </script>
        </div>
        <?php
    }

    private function render_modern_admin_page(): void {
        $options = $this->get_options();
        $status = isset($_GET['gza_status']) ? sanitize_text_field(wp_unslash($_GET['gza_status'])) : '';
        $lang = isset($_GET['gza_lang']) && sanitize_text_field(wp_unslash($_GET['gza_lang'])) === 'en' ? 'en' : 'zh';
        $is_en = $lang === 'en';
        $theme_presets = $this->chat_theme_presets();
        $theme_key = $this->normalize_chat_theme((string) ($options['chat_theme'] ?? 'teal'));
        $theme_label = $theme_key === 'custom' ? ($is_en ? 'Custom' : '自定义') : (string) ($theme_presets[$theme_key]['label'] ?? 'Default Teal');
        $theme_colors = $this->chat_theme_colors($options);
        $is_activated = !empty($options['site_token']);
        $is_cooldown = $this->is_cloud_temporarily_unavailable($options);
        $base_url = admin_url('admin.php?page=gza-ai-growth');
        $zh_url = add_query_arg('gza_lang', 'zh', $base_url);
        $en_url = add_query_arg('gza_lang', 'en', $base_url);
        $logo_url = plugin_dir_url(__FILE__) . 'assets/growthos-logo.png';
        ?>
        <div class="wrap gza-admin" data-lang="<?php echo esc_attr($lang); ?>">
            <style>
                .gza-admin{--gza-admin-primary:#0f766e;--gza-admin-ink:#0f172a;--gza-admin-muted:#64748b;--gza-admin-line:#dbe6f2;--gza-admin-soft:#f6fafc;--gza-admin-card:#fff;max-width:1220px}
                .gza-admin *{box-sizing:border-box}.gza-admin a{text-decoration:none}
                .gza-admin-hero{position:relative;overflow:hidden;border:1px solid var(--gza-admin-line);border-radius:24px;background:linear-gradient(135deg,#fff 0%,#f8fffd 54%,#e9fbf7 100%);padding:28px;box-shadow:0 22px 60px rgba(15,23,42,.08);margin:18px 0}
                .gza-admin-hero:after{content:"";position:absolute;right:34px;top:28px;width:190px;height:110px;border-radius:34px;background:radial-gradient(circle at 72% 38%,rgba(20,184,166,.36),transparent 34%),linear-gradient(135deg,rgba(20,184,166,.14),rgba(37,99,235,.08));border:1px solid rgba(20,184,166,.2);transform:rotate(-3deg)}
                .gza-admin-title{position:relative;z-index:1;display:flex;gap:16px;align-items:flex-start;max-width:760px}.gza-admin-logo{width:54px;height:54px;border-radius:16px;background:#07111f;display:grid;place-items:center;overflow:hidden;box-shadow:0 16px 34px rgba(15,23,42,.22)}.gza-admin-logo img{width:100%;height:100%;object-fit:cover;display:block}
                .gza-admin-title h1{margin:0;color:var(--gza-admin-ink);font-size:30px;line-height:1.15;font-weight:760;letter-spacing:0}.gza-admin-title p{margin:8px 0 0;color:var(--gza-admin-muted);font-size:14px;line-height:1.7;max-width:680px}
                .gza-lang{position:absolute;z-index:2;right:24px;top:22px;display:flex;gap:6px;border:1px solid #dbe6f2;border-radius:999px;background:rgba(255,255,255,.8);padding:4px;backdrop-filter:blur(10px)}.gza-lang a{border-radius:999px;padding:6px 10px;color:#64748b;font-weight:700;font-size:12px}.gza-lang a.active{background:#0f766e;color:#fff}
                .gza-admin-badges{position:relative;z-index:1;display:flex;flex-wrap:wrap;gap:8px;margin-top:18px}.gza-pill{display:inline-flex;align-items:center;gap:7px;border:1px solid #dbe6f2;border-radius:999px;background:#fff;padding:7px 11px;color:#334155;font-size:12px;font-weight:650}.gza-pill.ok{border-color:rgba(20,184,166,.24);background:#ecfdf5;color:#047857}.gza-pill.warn{border-color:rgba(245,158,11,.25);background:#fffbeb;color:#b45309}.gza-pill.off{background:#f8fafc;color:#64748b}
                .gza-notice{border:1px solid rgba(20,184,166,.24);background:#f0fdfa;color:#0f766e;border-radius:14px;padding:12px 14px;margin:14px 0;font-weight:650}.gza-admin-grid{display:grid;grid-template-columns:minmax(0,1.05fr) minmax(360px,.95fr);gap:18px;align-items:start}
                .gza-card{background:var(--gza-admin-card);border:1px solid var(--gza-admin-line);border-radius:20px;box-shadow:0 16px 42px rgba(15,23,42,.06);overflow:hidden}.gza-card-head{display:flex;justify-content:space-between;gap:14px;align-items:flex-start;padding:20px 22px;border-bottom:1px solid #edf2f7;background:linear-gradient(180deg,#fff,#fbfdff)}.gza-card-head h2{margin:0;color:var(--gza-admin-ink);font-size:17px;line-height:1.35}.gza-card-head p{margin:5px 0 0;color:var(--gza-admin-muted);font-size:13px;line-height:1.55}.gza-card-body{padding:20px 22px}.gza-section{display:grid;gap:16px}
                .gza-field{display:grid;gap:7px}.gza-field label,.gza-field-title{color:#334155;font-size:12px;font-weight:750;text-transform:uppercase;letter-spacing:.04em}.gza-field input[type="text"],.gza-field input[type="url"],.gza-field input[type="color"]{border:1px solid #dbe6f2;border-radius:12px;box-shadow:none}.gza-field input[type="text"],.gza-field input[type="url"]{min-height:42px;padding:0 12px;max-width:100%;width:100%}.gza-field .description{margin:0;color:var(--gza-admin-muted)}.gza-inline{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
                .gza-switches{display:grid;gap:10px}.gza-switch{display:flex;align-items:center;justify-content:space-between;gap:12px;border:1px solid #e2eaf3;border-radius:14px;background:#fbfdff;padding:12px 14px;color:#334155}.gza-switch strong{display:block;color:#0f172a}.gza-switch small{display:block;color:#64748b;margin-top:2px;line-height:1.45}.gza-switch input{width:18px;height:18px}
                .gza-metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin:0 0 18px}.gza-metric{border:1px solid var(--gza-admin-line);border-radius:18px;background:#fff;padding:15px;box-shadow:0 10px 26px rgba(15,23,42,.05)}.gza-metric span{display:block;color:#64748b;font-size:12px;font-weight:700}.gza-metric strong{display:block;margin-top:8px;color:#0f172a;font-size:18px;line-height:1.25;word-break:break-word}.gza-metric small{display:block;margin-top:5px;color:#94a3b8}
                .gza-theme-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px}.gza-theme{position:relative;display:flex;gap:10px;align-items:flex-start;border:1px solid #dbe6f2;border-radius:16px;background:#fff;padding:13px;cursor:pointer;transition:border-color .16s ease,box-shadow .16s ease,transform .16s ease}.gza-theme:hover{border-color:#99f6e4;box-shadow:0 12px 28px rgba(15,118,110,.08);transform:translateY(-1px)}.gza-theme.is-active{border-color:#14b8a6;background:#f0fdfa;box-shadow:0 14px 30px rgba(20,184,166,.12)}.gza-theme input{margin-top:3px}.gza-theme strong{display:block;color:#0f172a}.gza-theme small{display:block;color:#64748b;line-height:1.45;margin-top:4px}.gza-swatches{display:flex;gap:5px;margin-top:8px}.gza-swatches i{width:22px;height:22px;border-radius:999px;border:1px solid rgba(15,23,42,.12);display:inline-block}
                .gza-preview{border-radius:22px;border:1px solid <?php echo esc_attr($theme_colors['border']); ?>;background:<?php echo esc_attr($theme_colors['panel']); ?>;color:<?php echo esc_attr($theme_colors['text']); ?>;padding:16px;min-height:300px;box-shadow:0 20px 50px rgba(2,6,23,.13)}.gza-preview-top{display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid <?php echo esc_attr($theme_colors['border']); ?>;padding-bottom:12px}.gza-preview-person{display:flex;gap:10px;align-items:center}.gza-preview-avatar{width:40px;height:40px;border-radius:50%;overflow:hidden;background:<?php echo esc_attr($theme_colors['panel_soft']); ?>;display:grid;place-items:center;font-weight:800}.gza-preview-avatar img{width:100%;height:100%;object-fit:cover}.gza-preview-person strong{display:block}.gza-preview-person small{display:block;color:<?php echo esc_attr($theme_colors['muted']); ?>;margin-top:2px}.gza-preview-close{width:32px;height:32px;border-radius:50%;display:grid;place-items:center;background:<?php echo esc_attr($theme_colors['panel_soft']); ?>;color:<?php echo esc_attr($theme_colors['text']); ?>}
                .gza-preview-chat{display:grid;gap:10px;margin:28px 0}.gza-bubble{max-width:82%;border-radius:16px;padding:10px 12px;line-height:1.5;background:<?php echo esc_attr($theme_colors['panel_soft']); ?>}.gza-bubble.user{justify-self:end;background:linear-gradient(135deg,<?php echo esc_attr($theme_colors['primary']); ?>,<?php echo esc_attr($theme_colors['accent']); ?>);color:#fff}.gza-preview-form{display:grid;grid-template-columns:1fr 42px;gap:10px;margin-top:34px}.gza-preview-input{height:42px;border-radius:999px;border:1px solid <?php echo esc_attr($theme_colors['border']); ?>;background:<?php echo esc_attr($theme_colors['input']); ?>;color:<?php echo esc_attr($theme_colors['muted']); ?>;display:flex;align-items:center;padding:0 14px}.gza-preview-send{height:42px;border-radius:999px;background:<?php echo esc_attr($theme_colors['primary']); ?>;color:#fff;display:grid;place-items:center}
                .gza-actions{position:sticky;bottom:0;display:flex;flex-wrap:wrap;gap:9px;align-items:center;padding:14px 0 2px;background:linear-gradient(180deg,rgba(246,250,252,0),#f6fafc 28%)}.gza-actions .button{border-radius:10px;min-height:38px}.gza-actions .button-primary{background:#0f766e;border-color:#0f766e}
                .gza-admin{max-width:none;margin:0 20px 0 0;padding-top:8px;color:#10213f}.gza-admin-hero{min-height:188px;border-radius:28px;padding:32px 34px;background:linear-gradient(112deg,#fff 0%,#fff 58%,#eefcf9 100%);box-shadow:0 18px 55px rgba(15,23,42,.07);margin:18px 0}.gza-admin-hero:before{content:"";position:absolute;right:-20px;bottom:-34px;width:470px;height:190px;background:linear-gradient(145deg,rgba(15,118,110,.04),rgba(20,184,166,.16));clip-path:polygon(18% 100%,100% 30%,100% 100%);pointer-events:none}.gza-admin-hero:after{right:42px;top:60px;width:260px;height:92px;border-radius:48px;background:linear-gradient(135deg,rgba(15,118,110,.08),rgba(20,184,166,.03));border:1px solid rgba(20,184,166,.18);box-shadow:0 18px 45px rgba(15,118,110,.08)}
                .gza-admin-title{align-items:center;max-width:800px}.gza-admin-logo{width:64px;height:64px;border-radius:20px}.gza-admin-title h1{display:flex;align-items:center;gap:12px;font-size:26px;font-weight:760;line-height:1.2}.gza-admin-title h1:after{content:"\5DF2\8FDE\63A5";display:inline-flex;border:1px solid rgba(20,184,166,.34);background:#ecfdf5;color:#047857;border-radius:999px;padding:4px 9px;font-size:12px;font-weight:750}.gza-admin-title p{font-size:13px;line-height:1.72;color:#64748b}.gza-lang{right:26px;top:30px;padding:6px;border-radius:999px;background:#fff;box-shadow:0 10px 28px rgba(15,23,42,.12)}.gza-lang a{min-width:48px;text-align:center;padding:9px 12px}.gza-admin-badges{margin-left:80px;margin-top:18px}.gza-pill{min-height:28px;padding:6px 11px}
                .gza-metrics{grid-template-columns:repeat(4,minmax(0,1fr));gap:16px;margin:0 0 14px}.gza-metric{position:relative;display:grid;grid-template-columns:42px minmax(0,1fr);gap:8px 12px;align-items:center;min-height:104px;border-radius:18px;padding:18px 20px;background:#fff}.gza-metric:before{content:"";grid-row:1 / span 3;width:34px;height:34px;border-radius:12px;border:1px solid rgba(15,118,110,.18);background:linear-gradient(135deg,#ecfdf5,#e0f2fe)}.gza-metric span,.gza-metric strong,.gza-metric small{grid-column:2;min-width:0}.gza-metric span{font-size:13px;color:#52617a}.gza-metric strong{margin-top:2px;font-size:17px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;word-break:normal}.gza-metric small{color:#7b8aa5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
                .gza-admin-grid{grid-template-columns:minmax(0,1.05fr) minmax(420px,.95fr);gap:16px}.gza-section{gap:14px}.gza-card{border-radius:18px;background:rgba(255,255,255,.96);box-shadow:0 18px 50px rgba(15,23,42,.07)}.gza-card-head{position:relative;padding:20px 22px 16px 58px;background:#fff;border-bottom:0}.gza-card-head:before{content:"";position:absolute;left:22px;top:22px;width:26px;height:26px;border-radius:9px;background:linear-gradient(135deg,#e6fffb,#f0fdfa);border:1px solid rgba(20,184,166,.18)}.gza-card-head h2{font-size:18px;font-weight:760}.gza-card-head p{font-size:13px;color:#697895}.gza-card-body{padding:18px 22px 22px}
                .gza-field input[type="text"],.gza-field input[type="url"]{height:42px;border-radius:10px;background:#fff}.gza-switch{border-radius:12px;background:#fff}.gza-switch input{appearance:none;-webkit-appearance:none;position:relative;width:44px;height:24px;border-radius:999px;border:0;background:#cbd5e1;box-shadow:inset 0 0 0 1px rgba(15,23,42,.08);cursor:pointer;transition:background .16s ease}.gza-switch input:before{content:"";position:absolute;left:3px;top:3px;width:18px;height:18px;border-radius:50%;background:#fff;box-shadow:0 2px 6px rgba(15,23,42,.2);transition:transform .16s ease}.gza-switch input:checked{background:#0f9f91}.gza-switch input:checked:before{transform:translateX(20px)}
                .gza-theme-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.gza-theme{min-height:78px;border-radius:14px;padding:12px}.gza-theme strong{font-size:13px}.gza-theme small{font-size:12px}.gza-swatches i{width:18px;height:18px}.gza-preview{min-height:250px;border-radius:18px;box-shadow:0 15px 38px rgba(15,23,42,.10)}
                .gza-metric:before,.gza-card-head:before{background-color:#e6fffb;background-position:center;background-repeat:no-repeat;background-size:19px 19px}.gza-metric:nth-child(1):before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%230f9f91' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z'/%3E%3Cpath d='M14 2v6h6'/%3E%3Cpath d='M9 15h6'/%3E%3C/svg%3E")}.gza-metric:nth-child(2):before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%230f9f91' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='3' width='16' height='18' rx='2'/%3E%3Cpath d='M8 7h8M8 17h8'/%3E%3C/svg%3E")}.gza-metric:nth-child(3):before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%230f9f91' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m12 3 8 4-8 4-8-4 8-4Z'/%3E%3Cpath d='m4 12 8 4 8-4'/%3E%3Cpath d='m4 17 8 4 8-4'/%3E%3C/svg%3E")}.gza-metric:nth-child(4):before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%230f9f91' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v18'/%3E%3Cpath d='M6 8h12'/%3E%3Cpath d='M7 16h10'/%3E%3C/svg%3E")}
                .gza-admin-grid>.gza-section:first-child .gza-card:nth-child(1) .gza-card-head:before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%230f9f91' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M17.5 19H8a5 5 0 1 1 1.1-9.9A6 6 0 0 1 21 10.5 4.5 4.5 0 0 1 17.5 19Z'/%3E%3C/svg%3E")}.gza-admin-grid>.gza-section:first-child .gza-card:nth-child(2) .gza-card-head:before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%230f9f91' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13 2 4 14h7l-1 8 10-13h-7l0-7Z'/%3E%3C/svg%3E")}.gza-admin-grid>.gza-section:first-child .gza-card:nth-child(3) .gza-card-head:before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%230f9f91' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='13.5' cy='6.5' r='2.5'/%3E%3Ccircle cx='6.5' cy='12.5' r='2.5'/%3E%3Ccircle cx='16.5' cy='17.5' r='2.5'/%3E%3C/svg%3E")}.gza-admin-grid>.gza-section:nth-child(2) .gza-card:nth-child(1) .gza-card-head:before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%230f9f91' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12Z'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3C/svg%3E")}.gza-admin-grid>.gza-section:nth-child(2) .gza-card:nth-child(2) .gza-card-head:before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%230f9f91' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 21a8 8 0 0 0-16 0'/%3E%3Ccircle cx='12' cy='7' r='4'/%3E%3C/svg%3E")}.gza-admin-grid>.gza-section:nth-child(2) .gza-card:nth-child(3) .gza-card-head:before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%230f9f91' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3C/svg%3E")}
                .gza-actions{z-index:5;margin-top:12px;padding:12px 14px;background:rgba(255,255,255,.9);border:1px solid #dbe6f2;border-radius:18px;box-shadow:0 -8px 30px rgba(15,23,42,.04);backdrop-filter:blur(10px)}.gza-actions:after{content:"\8BBE\7F6E\5DF2\81EA\52A8\4FDD\5B58\4E8E\672C\5730\FF0C\53EF\540C\6B65\5230\4E91\7AEF\3002";margin-left:auto;color:#64748b;font-size:12px}.gza-admin[data-lang="en"] .gza-actions:after{content:"Settings are saved locally and can be synced to cloud."}.gza-actions .button{min-width:92px;border-color:#b6c7dc;color:#2563eb;background:#fff}.gza-actions .button-primary{background:#0f9f91;border-color:#0f9f91;color:#fff}
                @media (max-width:1080px){.gza-admin-grid{grid-template-columns:1fr}.gza-metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.gza-admin-hero:after{opacity:.35}}@media (max-width:640px){.gza-admin-hero{padding:20px}.gza-metrics{grid-template-columns:1fr}.gza-card-head{display:block}.gza-theme-grid{grid-template-columns:1fr}.gza-lang{position:relative;right:auto;top:auto;width:max-content;margin:0 0 16px auto}}
            </style>
            <section class="gza-admin-hero">
                <nav class="gza-lang"><a class="<?php echo $lang === 'zh' ? 'active' : ''; ?>" href="<?php echo esc_url($zh_url); ?>">中文</a><a class="<?php echo $lang === 'en' ? 'active' : ''; ?>" href="<?php echo esc_url($en_url); ?>">EN</a></nav>
                <div class="gza-admin-title"><span class="gza-admin-logo"><img src="<?php echo esc_url($logo_url); ?>" alt="GrowthOS" /></span><div><h1>GrowthOS</h1><p><?php echo esc_html($is_en ? 'Connect this WordPress site to AI customer service, SEO/GEO content sync, approved metadata injection and SaaS publishing workflows.' : '连接 WordPress 站点与 GrowthOS 云端，启用 AI 客服、SEO/GEO 内容同步、已审核元数据注入和云端发布流程。'); ?></p></div></div>
                <div class="gza-admin-badges">
                    <span class="gza-pill <?php echo $is_activated ? 'ok' : 'warn'; ?>"><?php echo esc_html($is_activated ? ($is_en ? 'Connected' : '已连接') : ($is_en ? 'Not activated' : '未激活')); ?></span>
                    <span class="gza-pill off">Plugin <?php echo esc_html(self::VERSION); ?></span>
                    <span class="gza-pill <?php echo $options['chat_widget'] ? 'ok' : 'off'; ?>">AI Chat <?php echo esc_html($options['chat_widget'] ? ($is_en ? 'Enabled' : '已启用') : ($is_en ? 'Disabled' : '未启用')); ?></span>
                    <span class="gza-pill <?php echo $options['inject_seo'] ? 'ok' : 'off'; ?>">SEO/GEO <?php echo esc_html($options['inject_seo'] ? ($is_en ? 'Enabled' : '已启用') : ($is_en ? 'Disabled' : '未启用')); ?></span>
                    <span class="gza-pill <?php echo $is_cooldown ? 'warn' : 'ok'; ?>"><?php echo esc_html($is_cooldown ? ($is_en ? 'Cloud cooldown' : '云端冷却中') : ($is_en ? 'Cloud ready' : '云端正常')); ?></span>
                </div>
            </section>
            <?php if ($status): ?><div class="gza-notice"><?php echo esc_html($status); ?></div><?php endif; ?>
            <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
                <?php wp_nonce_field(self::NONCE_ACTION); ?>
                <input type="hidden" name="action" value="gza_ai_growth_save" />
                <input type="hidden" name="gza_lang" value="<?php echo esc_attr($lang); ?>" />
                <div class="gza-metrics">
                    <div class="gza-metric"><span>Tenant</span><strong><?php echo esc_html($options['tenant_id'] ?: '-'); ?></strong><small><?php echo esc_html($is_en ? 'Current SaaS tenant' : '当前 SaaS 租户'); ?></small></div>
                    <div class="gza-metric"><span>Site</span><strong><?php echo esc_html($options['site_id'] ?: '-'); ?></strong><small><?php echo esc_html($is_en ? 'Bound website identity' : '已绑定站点身份'); ?></small></div>
                    <div class="gza-metric"><span><?php echo esc_html($is_en ? 'Synced Content' : '同步内容'); ?></span><strong><?php echo esc_html((string) ($options['last_sync_count'] !== '' ? $options['last_sync_count'] : '-')); ?></strong><small><?php echo esc_html($options['last_sync_at'] ?: ($is_en ? 'No sync yet' : '暂无同步')); ?></small></div>
                    <div class="gza-metric"><span><?php echo esc_html($is_en ? 'Latest Version' : '最新版本'); ?></span><strong><?php echo esc_html($options['last_update_version'] ?: self::VERSION); ?></strong><small><?php echo esc_html($options['last_update_checked_at'] ?: ($is_en ? 'Not checked' : '未检查')); ?></small></div>
                </div>
                <div class="gza-admin-grid">
                    <div class="gza-section">
                        <section class="gza-card"><div class="gza-card-head"><div><h2><?php echo esc_html($is_en ? 'Cloud Connection' : '云端连接'); ?></h2><p><?php echo esc_html($is_en ? 'Activation, API endpoint and WordPress-to-SaaS connectivity.' : '管理激活、API 地址，以及 WordPress 与 SaaS 的连接状态。'); ?></p></div><span class="gza-pill <?php echo $is_activated ? 'ok' : 'warn'; ?>"><?php echo esc_html($is_activated ? ($is_en ? 'Token saved' : 'Token 已保存') : ($is_en ? 'Needs activation' : '需要激活')); ?></span></div><div class="gza-card-body gza-section"><div class="gza-field"><label for="gza_api_base">Cloud API Base URL</label><input id="gza_api_base" name="api_base" type="url" value="<?php echo esc_attr($options['api_base']); ?>" placeholder="https://www.tui161.com" /></div><div class="gza-field"><label for="gza_license_code"><?php echo esc_html($is_en ? 'License Code' : '授权码'); ?></label><input id="gza_license_code" name="license_code" type="text" value="" autocomplete="off" placeholder="<?php echo esc_attr($is_en ? 'Paste license code for activation' : '粘贴授权码用于激活或重新连接'); ?>" /><p class="description"><?php echo esc_html($is_en ? 'Only used during activation. The plugin stores the returned site token.' : '授权码只在激活时使用，插件会保存云端返回的站点 Token。'); ?></p></div></div></section>
                        <section class="gza-card"><div class="gza-card-head"><div><h2><?php echo esc_html($is_en ? 'Capabilities' : '功能开关'); ?></h2><p><?php echo esc_html($is_en ? 'Choose which SaaS workflows are active on this WordPress site.' : '选择当前 WordPress 站点启用哪些 SaaS 工作流。'); ?></p></div></div><div class="gza-card-body"><div class="gza-switches"><label class="gza-switch"><span><strong>SEO/GEO metadata injection</strong><small><?php echo esc_html($is_en ? 'Render approved titles, descriptions, AI summaries, FAQ and JSON-LD overlays.' : '渲染已审核标题、描述、AI 摘要、FAQ 与 JSON-LD 结构化数据。'); ?></small></span><input type="checkbox" name="inject_seo" value="1" <?php checked($options['inject_seo'], true); ?> /></label><label class="gza-switch"><span><strong>AI customer service widget</strong><small><?php echo esc_html($is_en ? 'Show the front-end chat widget when SaaS chat config is available.' : '在前台展示 AI 客服聊天窗，并读取 SaaS 话术与开关配置。'); ?></small></span><input type="checkbox" name="chat_widget" value="1" <?php checked($options['chat_widget'], true); ?> /></label></div></div></section>
                        <section class="gza-card"><div class="gza-card-head"><div><h2><?php echo esc_html($is_en ? 'Widget Theme' : '聊天窗主题'); ?></h2><p><?php echo esc_html($is_en ? 'Visual theme is owned by this WordPress plugin. SaaS does not override these colors.' : '聊天窗视觉主题只由 WordPress 插件控制，SaaS 不覆盖这里的颜色。'); ?></p></div><span class="gza-pill ok"><?php echo esc_html($theme_label); ?></span></div><div class="gza-card-body gza-section"><div class="gza-theme-grid">
                            <?php foreach ($theme_presets as $preset_key => $theme): ?><label class="gza-theme <?php echo $theme_key === $preset_key ? 'is-active' : ''; ?>"><input type="radio" name="chat_theme" value="<?php echo esc_attr($preset_key); ?>" <?php checked($theme_key, $preset_key); ?> /><span><strong><?php echo esc_html($theme['label']); ?></strong><small><?php echo esc_html($theme['description']); ?></small><span class="gza-swatches"><i style="background:<?php echo esc_attr($theme['primary']); ?>"></i><i style="background:<?php echo esc_attr($theme['panel']); ?>"></i><i style="background:<?php echo esc_attr($theme['panel_soft']); ?>"></i><i style="background:<?php echo esc_attr($theme['text']); ?>"></i></span></span></label><?php endforeach; ?>
                            <label class="gza-theme <?php echo $theme_key === 'custom' ? 'is-active' : ''; ?>"><input type="radio" name="chat_theme" value="custom" <?php checked($theme_key, 'custom'); ?> /><span><strong><?php echo esc_html($is_en ? 'Custom' : '自定义'); ?></strong><small><?php echo esc_html($is_en ? 'Use the color pickers below for brand-specific sites.' : '使用下方色彩选择器匹配客户站点品牌。'); ?></small><span class="gza-swatches"><i style="background:<?php echo esc_attr($options['chat_primary']); ?>"></i><i style="background:<?php echo esc_attr($options['chat_panel']); ?>"></i><i style="background:<?php echo esc_attr($options['chat_text']); ?>"></i></span></span></label>
                        </div><div class="gza-inline"><span class="gza-field-title"><?php echo esc_html($is_en ? 'Custom Colors' : '自定义色彩'); ?></span><label>Primary <input name="chat_primary" type="color" value="<?php echo esc_attr($options['chat_primary']); ?>" /></label><label>Panel <input name="chat_panel" type="color" value="<?php echo esc_attr($options['chat_panel']); ?>" /></label><label>Text <input name="chat_text" type="color" value="<?php echo esc_attr($options['chat_text']); ?>" /></label></div></div></section>
                    </div>
                    <div class="gza-section">
                        <section class="gza-card"><div class="gza-card-head"><div><h2><?php echo esc_html($is_en ? 'Live Preview' : '实时预览'); ?></h2><p><?php echo esc_html($is_en ? 'Preview of the selected widget style and avatar.' : '预览当前主题与头像在聊天窗中的大致效果。'); ?></p></div></div><div class="gza-card-body"><div class="gza-preview"><div class="gza-preview-top"><div class="gza-preview-person"><span class="gza-preview-avatar" id="gza_chat_avatar_preview"><?php if (!empty($options['chat_avatar_url'])): ?><img src="<?php echo esc_url($options['chat_avatar_url']); ?>" alt="" /><?php else: ?>AI<?php endif; ?></span><span><strong><?php echo esc_html($is_en ? 'AI Customer Service' : 'AI 客服顾问'); ?></strong><small><?php echo esc_html($is_en ? 'Online' : '在线'); ?></small></span></div><span class="gza-preview-close">×</span></div><div class="gza-preview-chat"><div class="gza-bubble"><?php echo esc_html($is_en ? 'Hello, how can we help with your project?' : '您好，我们可以怎样帮助您的项目？'); ?></div><div class="gza-bubble user"><?php echo esc_html($is_en ? 'I want to learn more about your services.' : '我想了解一下你们的服务。'); ?></div></div><div class="gza-preview-form"><span class="gza-preview-input"><?php echo esc_html($is_en ? 'Type your question...' : '请输入您的问题...'); ?></span><span class="gza-preview-send">↗</span></div></div></div></section>
                        <section class="gza-card"><div class="gza-card-head"><div><h2><?php echo esc_html($is_en ? 'Avatar' : '头像'); ?></h2><p><?php echo esc_html($is_en ? 'Use a consultant photo or brand avatar. Square images work best.' : '可使用顾问照片或品牌头像，建议上传方形图片。'); ?></p></div></div><div class="gza-card-body gza-section"><div class="gza-field"><label for="gza_chat_avatar_url">Chat Avatar URL</label><input id="gza_chat_avatar_url" name="chat_avatar_url" type="url" value="<?php echo esc_attr($options['chat_avatar_url']); ?>" placeholder="https://example.com/avatar.jpg" /></div><div class="gza-inline"><button class="button" id="gza_chat_avatar_upload" type="button"><?php echo esc_html($is_en ? 'Upload / Select' : '上传 / 选择'); ?></button><button class="button" id="gza_chat_avatar_clear" type="button"><?php echo esc_html($is_en ? 'Clear' : '清除'); ?></button></div></div></section>
                        <section class="gza-card"><div class="gza-card-head"><div><h2><?php echo esc_html($is_en ? 'Diagnostics' : '诊断信息'); ?></h2><p><?php echo esc_html($is_en ? 'Operational details for sync, cloud jobs and update checks.' : '查看同步、云端任务、更新检查与最近错误。'); ?></p></div></div><div class="gza-card-body"><div class="gza-section"><div><strong>Cloud API:</strong> <?php echo esc_html($options['api_base'] ?: '-'); ?></div><div><strong><?php echo esc_html($is_en ? 'Last status' : '上次状态检查'); ?>:</strong> <?php echo esc_html($options['last_status_checked_at'] ?: '-'); ?></div><div><strong><?php echo esc_html($is_en ? 'Last cloud job' : '上次云端任务'); ?>:</strong> <?php echo esc_html($options['last_job_at'] ?: '-'); ?><?php echo $options['last_job_count'] !== '' ? ' / ' . esc_html((string) $options['last_job_count']) . ' processed' : ''; ?></div><div><strong><?php echo esc_html($is_en ? 'Cloud cooldown' : '云端冷却'); ?>:</strong> <?php echo $is_cooldown ? 'Until ' . esc_html(date_i18n('Y-m-d H:i:s', (int) $options['cloud_unavailable_until'])) : 'No'; ?></div><div><strong><?php echo esc_html($is_en ? 'Last error' : '最近错误'); ?>:</strong> <?php echo esc_html($options['last_error'] ?: '-'); ?></div></div></div></section>
                    </div>
                </div>
                <div class="gza-actions"><button class="button button-primary" name="gza_action" value="save" type="submit"><?php echo esc_html($is_en ? 'Save Settings' : '保存设置'); ?></button><button class="button" name="gza_action" value="activate" type="submit"><?php echo esc_html($is_en ? 'Activate / Reconnect' : '激活 / 重连'); ?></button><button class="button" name="gza_action" value="status" type="submit"><?php echo esc_html($is_en ? 'Check Status' : '检查状态'); ?></button><button class="button" name="gza_action" value="sync" type="submit"><?php echo esc_html($is_en ? 'Sync Content' : '同步内容'); ?></button><button class="button" name="gza_action" value="jobs" type="submit"><?php echo esc_html($is_en ? 'Process Cloud Jobs' : '处理云端任务'); ?></button><button class="button" name="gza_action" value="update_check" type="submit"><?php echo esc_html($is_en ? 'Check Plugin Update' : '检查插件更新'); ?></button></div>
            </form>
            <script>
            (function(){var upload=document.getElementById('gza_chat_avatar_upload');var clear=document.getElementById('gza_chat_avatar_clear');var input=document.getElementById('gza_chat_avatar_url');var preview=document.getElementById('gza_chat_avatar_preview');function render(value){preview.innerHTML=value?'<img src="'+value.replace(/"/g,'&quot;')+'" alt="" style="width:100%;height:100%;object-fit:cover;" />':'AI';}if(input){input.addEventListener('input',function(){render(input.value.trim());});}if(clear){clear.addEventListener('click',function(){input.value='';render('');});}if(upload){upload.addEventListener('click',function(){if(!window.wp||!wp.media){return;}var frame=wp.media({title:'Select chat avatar',button:{text:'Use this avatar'},multiple:false,library:{type:'image'}});frame.on('select',function(){var attachment=frame.state().get('selection').first().toJSON();input.value=attachment.url||'';render(input.value);});frame.open();});}})();
            </script>
        </div>
        <?php
    }

    public function handle_admin_post(): void {
        if (!current_user_can('manage_options')) {
            wp_die('Permission denied');
        }

        check_admin_referer(self::NONCE_ACTION);

        $options = $this->get_options();
        $options['api_base'] = $this->normalize_api_base($this->post_text('api_base'));
        $options['inject_seo'] = isset($_POST['inject_seo']);
        $options['chat_widget'] = isset($_POST['chat_widget']);
        $options['chat_theme'] = $this->normalize_chat_theme($this->post_text('chat_theme'));
        $options['chat_primary'] = $this->normalize_color($this->post_text('chat_primary'), '#0f766e');
        $options['chat_panel'] = $this->normalize_color($this->post_text('chat_panel'), '#07111f');
        $options['chat_text'] = $this->normalize_color($this->post_text('chat_text'), '#e5edf7');
        $options['chat_avatar_url'] = esc_url_raw($this->post_text('chat_avatar_url'));
        $action = $this->post_text('gza_action') ?: 'save';
        $lang = $this->post_text('gza_lang') === 'en' ? 'en' : 'zh';
        $message = 'Settings saved.';

        if ($action === 'activate') {
            $license_code = $this->post_text('license_code');
            $result = $this->activate_site($options, $license_code);
            $options = $result['options'];
            $message = $result['message'];
        } elseif ($action === 'status') {
            $message = $this->check_status($options);
        } elseif ($action === 'sync') {
            $message = $this->sync_content($options);
        } elseif ($action === 'jobs') {
            $message = $this->run_cloud_jobs($options, 5);
        } elseif ($action === 'update_check') {
            $message = $this->check_plugin_update($options);
        }

        update_option(self::OPTION_KEY, $options, false);
        wp_safe_redirect(add_query_arg([
            'gza_status' => rawurlencode($message),
            'gza_lang' => $lang,
        ], admin_url('admin.php?page=gza-ai-growth')));
        exit;
    }

    public function prepare_seo_overlay_output(): void {
        $options = $this->get_options();
        if (!$options['inject_seo']) {
            return;
        }

        $overlay = $this->get_current_overlay($options);
        if (!$overlay) {
            return;
        }

        $this->current_seo_overlay = $overlay;

        if (!empty($overlay['canonical'])) {
            remove_action('wp_head', 'rel_canonical');
            add_filter('wpseo_canonical', '__return_false', 99);
            add_filter('rank_math/frontend/canonical', '__return_false', 99);
            add_filter('aioseo_canonical_url', '__return_false', 99);
        }

        if (!empty($overlay['robots'])) {
            remove_action('wp_head', 'wp_robots', 1);
            add_filter('wpseo_robots', '__return_false', 99);
        }
    }

    public function filter_document_title($title) {
        $options = $this->get_options();
        if (!$options['inject_seo']) {
            return $title;
        }

        $overlay = $this->current_seo_overlay ?: $this->get_current_overlay($options);
        return !empty($overlay['title']) ? sanitize_text_field($overlay['title']) : $title;
    }

    public function render_seo_overlay(): void {
        $options = $this->get_options();
        if (!$options['inject_seo']) {
            return;
        }

        $overlay = $this->current_seo_overlay ?: $this->get_current_overlay($options);
        if (!$overlay) {
            return;
        }

        if (!empty($overlay['description'])) {
            printf("\n<meta name=\"description\" content=\"%s\" />\n", esc_attr($overlay['description']));
        }

        if (!empty($overlay['robots'])) {
            printf("<meta name=\"robots\" content=\"%s\" />\n", esc_attr($overlay['robots']));
        }

        if (!empty($overlay['canonical'])) {
            printf("<link rel=\"canonical\" href=\"%s\" />\n", esc_url($overlay['canonical']));
        }

        if (!empty($overlay['aiSummary'])) {
            printf("<meta name=\"ai-summary\" content=\"%s\" />\n", esc_attr($overlay['aiSummary']));
        }

        $open_graph = is_array($overlay['openGraph'] ?? null) ? $overlay['openGraph'] : [];
        foreach ([
            'type' => 'og:type',
            'title' => 'og:title',
            'description' => 'og:description',
            'url' => 'og:url',
            'siteName' => 'og:site_name',
            'locale' => 'og:locale',
            'image' => 'og:image',
        ] as $key => $property) {
            if (!empty($open_graph[$key])) {
                printf("<meta property=\"%s\" content=\"%s\" />\n", esc_attr($property), esc_attr($open_graph[$key]));
            }
        }

        $twitter = is_array($overlay['twitter'] ?? null) ? $overlay['twitter'] : [];
        foreach ([
            'card' => 'twitter:card',
            'title' => 'twitter:title',
            'description' => 'twitter:description',
            'image' => 'twitter:image',
        ] as $key => $name) {
            if (!empty($twitter[$key])) {
                printf("<meta name=\"%s\" content=\"%s\" />\n", esc_attr($name), esc_attr($twitter[$key]));
            }
        }

        if (!empty($overlay['schemaGraph']) && is_array($overlay['schemaGraph'])) {
            echo '<script type="application/ld+json">' . wp_json_encode($overlay['schemaGraph'], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "</script>\n";
            return;
        }

        if (!empty($overlay['schemaJsonLd']) && is_array($overlay['schemaJsonLd'])) {
            foreach ($overlay['schemaJsonLd'] as $schema) {
                if (is_array($schema)) {
                    echo '<script type="application/ld+json">' . wp_json_encode($schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "</script>\n";
                }
            }
        }

        if (!empty($overlay['faq']) && is_array($overlay['faq'])) {
            $faq_schema = [
                '@context' => 'https://schema.org',
                '@type' => 'FAQPage',
                'mainEntity' => array_values(array_filter(array_map(function ($item) {
                    if (empty($item['question']) || empty($item['answer'])) {
                        return null;
                    }

                    return [
                        '@type' => 'Question',
                        'name' => wp_strip_all_tags((string) $item['question']),
                        'acceptedAnswer' => [
                            '@type' => 'Answer',
                            'text' => wp_strip_all_tags((string) $item['answer']),
                        ],
                    ];
                }, $overlay['faq']))),
            ];

            if (!empty($faq_schema['mainEntity'])) {
                echo '<script type="application/ld+json">' . wp_json_encode($faq_schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "</script>\n";
            }
        }
    }

    public function render_chat_widget(): void {
        $options = $this->get_options();
        if (!$options['chat_widget'] || empty($options['site_token'])) {
            return;
        }

        $rest_base = esc_url_raw(rest_url('gza-ai-growth/v1'));
        $cached_config = get_option(self::CHAT_CONFIG_CACHE_OPTION, []);
        $cached_config = is_array($cached_config) ? $cached_config : [];
        $colors = $this->chat_theme_colors($options);
        $assistant_name = sanitize_text_field((string) ($cached_config['assistantName'] ?? '广而告之 AI 客服'));
        $welcome_message = sanitize_textarea_field((string) ($cached_config['welcomeMessage'] ?? ''));
        $avatar_url = esc_url_raw((string) ($options['chat_avatar_url'] ?? ''));
        $resolved_locale = sanitize_text_field((string) ($cached_config['locale'] ?? str_replace('_', '-', determine_locale())));
        $supported_locales = isset($cached_config['supportedLocales']) && is_array($cached_config['supportedLocales'])
            ? array_values(array_filter(array_map('sanitize_text_field', $cached_config['supportedLocales'])))
            : $this->default_supported_locales();
        ?>
        <div id="gza-ai-chat" class="gza-ai-chat" data-gza-welcome="<?php echo esc_attr($welcome_message); ?>" data-gza-locale="<?php echo esc_attr($resolved_locale); ?>" data-gza-supported-locales="<?php echo esc_attr(wp_json_encode($supported_locales)); ?>" data-gza-avatar="<?php echo esc_attr($avatar_url); ?>" style="--gza-primary:<?php echo esc_attr($colors['primary']); ?>;--gza-accent:<?php echo esc_attr($colors['accent']); ?>;--gza-panel:<?php echo esc_attr($colors['panel']); ?>;--gza-panel-soft:<?php echo esc_attr($colors['panel_soft']); ?>;--gza-input:<?php echo esc_attr($colors['input']); ?>;--gza-input-text:<?php echo esc_attr($colors['input_text']); ?>;--gza-border:<?php echo esc_attr($colors['border']); ?>;--gza-text:<?php echo esc_attr($colors['text']); ?>;--gza-muted:<?php echo esc_attr($colors['muted']); ?>;">
            <button class="gza-ai-chat__trigger" type="button" aria-label="打开 AI 客服">
                <span class="gza-ai-chat__trigger-pulse" aria-hidden="true"></span>
                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/></svg>
            </button>
            <section class="gza-ai-chat__panel" aria-label="AI 客服" hidden>
                <header>
                    <div class="gza-ai-chat__identity">
                        <span class="gza-ai-chat__avatar">
                            <?php if ($avatar_url): ?>
                                <img alt="" src="<?php echo esc_url($avatar_url); ?>" />
                            <?php else: ?>
                                <svg viewBox="0 0 40 40" aria-hidden="true"><defs><linearGradient id="gzaDefaultAvatar" x1="8" y1="4" x2="32" y2="36"><stop stop-color="#e0f2fe"/><stop offset="1" stop-color="#14b8a6"/></linearGradient></defs><rect width="40" height="40" rx="20" fill="url(#gzaDefaultAvatar)"/><circle cx="20" cy="15" r="6.2" fill="#ffffff" opacity=".96"/><path d="M9.5 32.2c2-6 5.5-9 10.5-9s8.5 3 10.5 9" fill="#ffffff" opacity=".96"/></svg>
                            <?php endif; ?>
                        </span>
                        <div>
                            <strong data-gza-name><?php echo esc_html($assistant_name); ?></strong>
                            <small>在线</small>
                        </div>
                    </div>
                    <button class="gza-ai-chat__close" type="button" data-gza-close aria-label="关闭"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="2.4"/></svg></button>
                </header>
                <div class="gza-ai-chat__messages" data-gza-messages></div>
                <form class="gza-ai-chat__form" data-gza-form>
                    <input data-gza-input maxlength="1200" placeholder="请输入您的问题..." />
                    <button type="submit" aria-label="发送" title="发送"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 14-7-4.2 14-2.9-5.7L5 12Z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="2"/><path d="m11.8 13.3 3.1-3.1" fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="2"/></svg></button>
                </form>
            </section>
        </div>
        <style>
            .gza-ai-chat{position:fixed!important;right:22px!important;bottom:22px!important;z-index:99999!important;font-family:Inter,Arial,sans-serif!important;color:var(--gza-text)!important;isolation:isolate!important}
            .gza-ai-chat,.gza-ai-chat *{box-sizing:border-box!important}
            .gza-ai-chat button,.gza-ai-chat input{font-family:Inter,Arial,sans-serif!important}
            .gza-ai-chat__trigger{all:unset!important;position:relative!important;width:58px!important;height:58px!important;min-width:58px!important;min-height:58px!important;max-width:58px!important;max-height:58px!important;padding:0!important;border:1px solid rgba(255,255,255,.18)!important;border-radius:999px!important;background:linear-gradient(135deg,var(--gza-primary),var(--gza-accent))!important;color:#fff!important;display:grid!important;place-items:center!important;box-shadow:0 20px 48px rgba(2,6,23,.28)!important;cursor:pointer!important;transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease!important}
            .gza-ai-chat__trigger:hover,.gza-ai-chat__trigger:focus-visible{border-color:rgba(125,211,252,.5)!important;box-shadow:0 24px 58px rgba(2,6,23,.34),0 0 0 6px color-mix(in srgb,var(--gza-primary) 16%,transparent)!important;outline:none!important;transform:translateY(-2px)}
            .gza-ai-chat__trigger svg{width:25px;height:25px;position:relative;z-index:1}
            .gza-ai-chat__trigger-pulse{position:absolute;inset:-7px;border-radius:999px;background:color-mix(in srgb,var(--gza-primary) 18%,transparent);animation:gzaPulse 2.4s ease-out infinite}
            .gza-ai-chat.is-open .gza-ai-chat__trigger{transform:translateY(0) scale(.96);box-shadow:0 16px 38px rgba(2,6,23,.28)}
            .gza-ai-chat.is-open .gza-ai-chat__trigger-pulse{animation:none;opacity:0}
            .gza-ai-chat__panel{position:absolute!important;right:0!important;bottom:76px!important;display:grid!important;grid-template-rows:auto 1fr auto!important;width:min(392px,calc(100vw - 32px))!important;height:min(620px,calc(100dvh - 120px))!important;max-height:calc(100vh - 92px)!important;border:1px solid var(--gza-border)!important;border-radius:22px!important;background:color-mix(in srgb,var(--gza-panel) 94%,transparent)!important;color:var(--gza-text)!important;box-shadow:0 30px 80px rgba(2,6,23,.28)!important;backdrop-filter:blur(18px)!important;overflow:hidden!important;transform-origin:92% 100%;animation:gzaPanelIn .24s cubic-bezier(.2,.8,.2,1) both!important}
            .gza-ai-chat__panel[hidden]{display:none!important}
            @keyframes gzaPanelIn{from{opacity:0;transform:translateY(14px) scale(.97)}to{opacity:1;transform:translateY(0) scale(1)}}
            @keyframes gzaPulse{0%{opacity:.65;transform:scale(.75)}70%{opacity:0;transform:scale(1.25)}100%{opacity:0;transform:scale(1.25)}}
            .gza-ai-chat__panel header{display:flex!important;justify-content:space-between!important;gap:14px!important;align-items:center!important;min-height:74px!important;padding:16px!important;border-bottom:1px solid var(--gza-border)!important;background:color-mix(in srgb,var(--gza-panel-soft) 38%,transparent)!important}
            .gza-ai-chat__identity{display:flex;align-items:center;gap:10px;min-width:0}.gza-ai-chat__identity strong,.gza-ai-chat__identity small{display:block}.gza-ai-chat__identity strong{font-size:15px;line-height:1.2}.gza-ai-chat__identity small{color:var(--gza-muted);font-size:12px;margin-top:3px}
            .gza-ai-chat__avatar{width:40px;height:40px;border-radius:50%;display:grid;place-items:center;overflow:hidden;background:rgba(20,184,166,.18);color:#ccfbf1;font-weight:800;font-size:12px;box-shadow:0 0 0 1px rgba(255,255,255,.08),0 10px 24px rgba(2,6,23,.24)}
            .gza-ai-chat__avatar img,.gza-ai-chat__avatar svg{width:100%;height:100%;object-fit:cover;object-position:center;display:block}
            .gza-ai-chat__close{all:unset!important;flex:0 0 34px!important;width:34px!important;height:34px!important;min-width:34px!important;min-height:34px!important;max-width:34px!important;max-height:34px!important;aspect-ratio:1/1!important;padding:0!important;border:0!important;border-radius:50%!important;background:color-mix(in srgb,var(--gza-muted) 16%,transparent)!important;color:var(--gza-text)!important;display:grid!important;place-items:center!important;text-align:center!important;cursor:pointer!important;transition:background .16s ease,transform .16s ease!important}
            .gza-ai-chat__close svg{width:18px;height:18px}
            .gza-ai-chat__close:hover,.gza-ai-chat__close:focus-visible{background:rgba(148,163,184,.24)!important;outline:none!important;transform:scale(1.04)}
            .gza-ai-chat__messages{min-height:0;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:10px;scrollbar-width:thin;scrollbar-gutter:stable;scrollbar-color:rgba(148,163,184,.38) transparent}
            .gza-ai-chat__messages::-webkit-scrollbar{width:6px}.gza-ai-chat__messages::-webkit-scrollbar-thumb{background:rgba(148,163,184,.34);border-radius:999px}
            .gza-ai-chat__msg{max-width:86%;border-radius:16px;padding:11px 13px;line-height:1.58;font-size:14px;background:color-mix(in srgb,var(--gza-panel-soft) 88%,transparent);white-space:pre-wrap;word-break:break-word;animation:gzaMessageIn .18s ease both}
            .gza-ai-chat__msg.user{align-self:flex-end;background:linear-gradient(135deg,var(--gza-primary),var(--gza-accent));color:#fff}.gza-ai-chat__msg.assistant{align-self:flex-start;color:var(--gza-text)}
            .gza-ai-chat__msg.loading{display:inline-flex;align-items:center;gap:8px;background:linear-gradient(90deg,color-mix(in srgb,var(--gza-panel-soft) 86%,transparent),color-mix(in srgb,var(--gza-primary) 22%,var(--gza-panel-soft)),color-mix(in srgb,var(--gza-panel-soft) 86%,transparent));background-size:180% 100%;animation:gzaMessageIn .18s ease both,gzaLoadingGlow 1.45s ease-in-out infinite}
            .gza-ai-chat__msg.loading::before{content:"";width:15px;height:15px;border-radius:50%;border:2px solid rgba(226,232,240,.35);border-top-color:#fff;animation:gzaSpin .9s linear infinite;flex:0 0 15px}
            @keyframes gzaSpin{to{transform:rotate(360deg)}}@keyframes gzaMessageIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes gzaLoadingGlow{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
            .gza-ai-chat__form{display:grid!important;grid-template-columns:minmax(0,1fr)42px!important;align-items:center!important;gap:10px!important;padding:14px 16px 16px!important;border-top:1px solid var(--gza-border)!important;background:color-mix(in srgb,var(--gza-panel-soft) 32%,transparent)!important}
            .gza-ai-chat__form input{width:100%!important;min-width:0!important;height:42px!important;min-height:42px!important;max-height:42px!important;margin:0!important;border:1px solid var(--gza-border)!important;border-radius:999px!important;background:var(--gza-input)!important;color:var(--gza-input-text)!important;padding:0 16px!important;outline:none!important;box-shadow:none!important;font:inherit!important;font-size:14px!important;line-height:42px!important}
            .gza-ai-chat__form input:focus{border-color:color-mix(in srgb,var(--gza-primary) 68%,#fff);box-shadow:0 0 0 3px color-mix(in srgb,var(--gza-primary) 18%,transparent)}
            .gza-ai-chat__form button{all:unset!important;width:42px!important;height:42px!important;min-width:42px!important;min-height:42px!important;max-width:42px!important;max-height:42px!important;margin:0!important;border:0!important;border-radius:999px!important;background:var(--gza-primary)!important;color:#fff!important;padding:0!important;font-weight:800!important;display:grid!important;place-items:center!important;cursor:pointer!important;box-shadow:0 10px 24px color-mix(in srgb,var(--gza-primary) 24%,transparent)!important;text-align:center!important;white-space:nowrap!important;transition:transform .16s ease,filter .16s ease,box-shadow .16s ease!important}
            .gza-ai-chat__form button svg{width:17px;height:17px}
            .gza-ai-chat__form button:hover,.gza-ai-chat__form button:focus-visible{filter:brightness(1.06);outline:none!important;transform:translateY(-1px)}
            @media (prefers-reduced-motion:reduce){.gza-ai-chat__trigger,.gza-ai-chat__trigger-pulse,.gza-ai-chat__panel,.gza-ai-chat__msg,.gza-ai-chat__msg.loading,.gza-ai-chat__msg.loading::before{animation:none!important;transition:none!important}}
            @media (max-width:520px){.gza-ai-chat{right:12px!important;bottom:12px!important}.gza-ai-chat__panel{position:fixed!important;left:12px!important;right:12px!important;bottom:84px!important;width:auto!important;max-width:none!important;height:min(560px,calc(100vh - 112px))!important}.gza-ai-chat__form{grid-template-columns:minmax(0,1fr)42px!important}.gza-ai-chat__form button{width:42px!important;min-width:42px!important;max-width:42px!important}}
        </style>
        <script>
        (function(){
            var root=document.getElementById('gza-ai-chat'); if(!root) return;
            var restBase=<?php echo wp_json_encode($rest_base); ?>;
            var trigger=root.querySelector('.gza-ai-chat__trigger');
            var panel=root.querySelector('.gza-ai-chat__panel');
            var close=root.querySelector('[data-gza-close]');
            var form=root.querySelector('[data-gza-form]');
            var input=root.querySelector('[data-gza-input]');
            var messages=root.querySelector('[data-gza-messages]');
            var name=root.querySelector('[data-gza-name]');
            var status=root.querySelector('.gza-ai-chat__identity small');
            var submit=form.querySelector('button[type="submit"]');
            var conversationKey='gza_ai_conversation_id';
            var visitorKey='gza_ai_visitor_id';
            var lastMessageKey='gza_ai_last_message_at';
            var conversationId=localStorage.getItem(conversationKey)||'';
            var visitorId=localStorage.getItem(visitorKey)||''; if(!visitorId){visitorId='v_'+Date.now().toString(36)+'_'+Math.random().toString(36).slice(2,10);localStorage.setItem(visitorKey,visitorId);}
            var seenMessages={}; var lastMessageAt=localStorage.getItem(lastMessageKey)||localStorage.getItem('gza_ai_last_message_at')||''; var presenceTimer=null; var messagePollTimer=null; var operatorOnline=false; var presenceIntervalMs=30000; var messageIntervalMs=6000;
            var defaultLocales=['zh-CN','en-US','de-DE','fr-FR','es-ES','pt-BR','ar','ja-JP','ko-KR','ru-RU','it-IT','nl-NL','tr-TR','pl-PL','vi-VN','th-TH','id-ID','ms-MY','hi-IN','cs-CZ','hu-HU','ro-RO','sv-SE','da-DK','fi-FI','nb-NO','el-GR','he-IL','uk-UA'];
            var uiCopy={
              'zh-CN':{open:'打开 AI 客服',panel:'AI 客服',close:'关闭',status:'在线',placeholder:'请输入您的问题...',send:'发送',loading:'思考中',error:'抱歉，暂时无法回复，请稍后再试。',network:'网络异常，请稍后再试。'},
              'en-US':{open:'Open AI chat',panel:'AI customer service chat',close:'Close',status:'Online',placeholder:'Type your question...',send:'Send',loading:'Thinking',error:'Sorry, please try again later.',network:'Network error. Please try again later.'},
              'de-DE':{open:'KI-Chat öffnen',panel:'KI-Kundenservice-Chat',close:'Schließen',status:'Online',placeholder:'Geben Sie Ihre Frage ein...',send:'Senden',loading:'Denke nach',error:'Entschuldigung, bitte versuchen Sie es später erneut.',network:'Netzwerkfehler. Bitte versuchen Sie es später erneut.'},
              'fr-FR':{open:'Ouvrir le chat IA',panel:'Chat service client IA',close:'Fermer',status:'En ligne',placeholder:'Saisissez votre question...',send:'Envoyer',loading:'Réflexion',error:'Désolé, veuillez réessayer plus tard.',network:'Erreur réseau. Veuillez réessayer plus tard.'},
              'es-ES':{open:'Abrir chat de IA',panel:'Chat de atención al cliente IA',close:'Cerrar',status:'En línea',placeholder:'Escribe tu pregunta...',send:'Enviar',loading:'Pensando',error:'Lo siento, inténtalo de nuevo más tarde.',network:'Error de red. Inténtalo de nuevo más tarde.'},
              'pt-BR':{open:'Abrir chat de IA',panel:'Chat de atendimento por IA',close:'Fechar',status:'Online',placeholder:'Digite sua pergunta...',send:'Enviar',loading:'Pensando',error:'Desculpe, tente novamente mais tarde.',network:'Erro de rede. Tente novamente mais tarde.'},
              'ar':{open:'فتح دردشة الذكاء الاصطناعي',panel:'دردشة خدمة العملاء بالذكاء الاصطناعي',close:'إغلاق',status:'متصل',placeholder:'اكتب سؤالك...',send:'إرسال',loading:'جار التفكير',error:'عذرا، يرجى المحاولة لاحقا.',network:'خطأ في الشبكة. يرجى المحاولة لاحقا.'},
              'ja-JP':{open:'AIチャットを開く',panel:'AIカスタマーサービスチャット',close:'閉じる',status:'オンライン',placeholder:'質問を入力してください...',send:'送信',loading:'考えています',error:'申し訳ありません。後でもう一度お試しください。',network:'ネットワークエラーです。後でもう一度お試しください。'},
              'ko-KR':{open:'AI 채팅 열기',panel:'AI 고객 서비스 채팅',close:'닫기',status:'온라인',placeholder:'질문을 입력하세요...',send:'보내기',loading:'생각 중',error:'죄송합니다. 나중에 다시 시도해 주세요.',network:'네트워크 오류입니다. 나중에 다시 시도해 주세요.'},
              'ru-RU':{open:'Открыть AI-чат',panel:'AI-чат поддержки',close:'Закрыть',status:'Онлайн',placeholder:'Введите ваш вопрос...',send:'Отправить',loading:'Думаю',error:'Извините, попробуйте позже.',network:'Ошибка сети. Попробуйте позже.'},
              'it-IT':{open:'Apri chat AI',panel:'Chat assistenza AI',close:'Chiudi',status:'Online',placeholder:'Scrivi la tua domanda...',send:'Invia',loading:'Sto pensando',error:'Spiacenti, riprova più tardi.',network:'Errore di rete. Riprova più tardi.'},
              'nl-NL':{open:'AI-chat openen',panel:'AI-klantenservicechat',close:'Sluiten',status:'Online',placeholder:'Typ uw vraag...',send:'Versturen',loading:'Aan het nadenken',error:'Sorry, probeer het later opnieuw.',network:'Netwerkfout. Probeer het later opnieuw.'},
              'tr-TR':{open:'AI sohbetini aç',panel:'AI müşteri hizmetleri sohbeti',close:'Kapat',status:'Çevrimiçi',placeholder:'Sorunuzu yazın...',send:'Gönder',loading:'Düşünüyor',error:'Üzgünüz, lütfen daha sonra tekrar deneyin.',network:'Ağ hatası. Lütfen daha sonra tekrar deneyin.'},
              'pl-PL':{open:'Otwórz czat AI',panel:'Czat obsługi AI',close:'Zamknij',status:'Online',placeholder:'Wpisz pytanie...',send:'Wyślij',loading:'Myślę',error:'Przepraszamy, spróbuj ponownie później.',network:'Błąd sieci. Spróbuj ponownie później.'},
              'vi-VN':{open:'Mở chat AI',panel:'Chat chăm sóc khách hàng AI',close:'Đóng',status:'Trực tuyến',placeholder:'Nhập câu hỏi của bạn...',send:'Gửi',loading:'Đang suy nghĩ',error:'Xin lỗi, vui lòng thử lại sau.',network:'Lỗi mạng. Vui lòng thử lại sau.'},
              'th-TH':{open:'เปิดแชต AI',panel:'แชตบริการลูกค้า AI',close:'ปิด',status:'ออนไลน์',placeholder:'พิมพ์คำถามของคุณ...',send:'ส่ง',loading:'กำลังคิด',error:'ขออภัย โปรดลองใหม่ภายหลัง',network:'เกิดข้อผิดพลาดเครือข่าย โปรดลองใหม่ภายหลัง'},
              'id-ID':{open:'Buka chat AI',panel:'Chat layanan pelanggan AI',close:'Tutup',status:'Online',placeholder:'Ketik pertanyaan Anda...',send:'Kirim',loading:'Berpikir',error:'Maaf, silakan coba lagi nanti.',network:'Kesalahan jaringan. Silakan coba lagi nanti.'},
              'ms-MY':{open:'Buka sembang AI',panel:'Sembang khidmat pelanggan AI',close:'Tutup',status:'Dalam talian',placeholder:'Taip soalan anda...',send:'Hantar',loading:'Sedang berfikir',error:'Maaf, sila cuba lagi kemudian.',network:'Ralat rangkaian. Sila cuba lagi kemudian.'},
              'hi-IN':{open:'AI चैट खोलें',panel:'AI ग्राहक सेवा चैट',close:'बंद करें',status:'ऑनलाइन',placeholder:'अपना सवाल लिखें...',send:'भेजें',loading:'सोच रहा है',error:'क्षमा करें, कृपया बाद में फिर कोशिश करें।',network:'नेटवर्क त्रुटि। कृपया बाद में फिर कोशिश करें।'},
              'cs-CZ':{open:'Otevřít AI chat',panel:'AI zákaznický chat',close:'Zavřít',status:'Online',placeholder:'Napište svůj dotaz...',send:'Odeslat',loading:'Přemýšlím',error:'Omlouváme se, zkuste to prosím později.',network:'Chyba sítě. Zkuste to prosím později.'},
              'hu-HU':{open:'AI chat megnyitása',panel:'AI ügyfélszolgálati chat',close:'Bezárás',status:'Online',placeholder:'Írja be a kérdését...',send:'Küldés',loading:'Gondolkodom',error:'Sajnáljuk, próbálja újra később.',network:'Hálózati hiba. Próbálja újra később.'},
              'ro-RO':{open:'Deschide chatul AI',panel:'Chat suport clienți AI',close:'Închide',status:'Online',placeholder:'Scrie întrebarea...',send:'Trimite',loading:'Mă gândesc',error:'Ne pare rău, încercați din nou mai târziu.',network:'Eroare de rețea. Încercați din nou mai târziu.'},
              'sv-SE':{open:'Öppna AI-chatt',panel:'AI-kundtjänstchatt',close:'Stäng',status:'Online',placeholder:'Skriv din fråga...',send:'Skicka',loading:'Tänker',error:'Förlåt, försök igen senare.',network:'Nätverksfel. Försök igen senare.'},
              'da-DK':{open:'Åbn AI-chat',panel:'AI-kundeservicechat',close:'Luk',status:'Online',placeholder:'Skriv dit spørgsmål...',send:'Send',loading:'Tænker',error:'Beklager, prøv igen senere.',network:'Netværksfejl. Prøv igen senere.'},
              'fi-FI':{open:'Avaa AI-chat',panel:'AI-asiakaspalveluchat',close:'Sulje',status:'Online',placeholder:'Kirjoita kysymyksesi...',send:'Lähetä',loading:'Ajattelen',error:'Pahoittelut, yritä myöhemmin uudelleen.',network:'Verkkovirhe. Yritä myöhemmin uudelleen.'},
              'nb-NO':{open:'Åpne AI-chat',panel:'AI-kundeservicechat',close:'Lukk',status:'Online',placeholder:'Skriv spørsmålet ditt...',send:'Send',loading:'Tenker',error:'Beklager, prøv igjen senere.',network:'Nettverksfeil. Prøv igjen senere.'},
              'el-GR':{open:'Άνοιγμα συνομιλίας AI',panel:'Συνομιλία εξυπηρέτησης AI',close:'Κλείσιμο',status:'Online',placeholder:'Πληκτρολογήστε την ερώτησή σας...',send:'Αποστολή',loading:'Σκέφτομαι',error:'Λυπούμαστε, δοκιμάστε ξανά αργότερα.',network:'Σφάλμα δικτύου. Δοκιμάστε ξανά αργότερα.'},
              'he-IL':{open:'פתח צ׳אט AI',panel:'צ׳אט שירות לקוחות AI',close:'סגור',status:'מחובר',placeholder:'הקלד את השאלה שלך...',send:'שלח',loading:'חושב',error:'מצטערים, נסה שוב מאוחר יותר.',network:'שגיאת רשת. נסה שוב מאוחר יותר.'},
              'uk-UA':{open:'Відкрити AI-чат',panel:'AI-чат підтримки',close:'Закрити',status:'Онлайн',placeholder:'Введіть ваше запитання...',send:'Надіслати',loading:'Думаю',error:'Вибачте, спробуйте пізніше.',network:'Помилка мережі. Спробуйте пізніше.'}
            };
            var localeAliases={'en':'en-US','de':'de-DE','fr':'fr-FR','it':'it-IT','nl':'nl-NL','ja':'ja-JP','ko':'ko-KR','pl':'pl-PL','cs':'cs-CZ','sk':'cs-CZ','mt':'en-US'};
            function add(role,text,loading){var item=document.createElement('div');item.className='gza-ai-chat__msg '+role+(loading?' loading':'');item.textContent=text;messages.appendChild(item);messages.scrollTop=messages.scrollHeight;return item;}
            function readSupportedLocales(){try{var raw=root.getAttribute('data-gza-supported-locales')||'[]';var list=JSON.parse(raw);return Array.isArray(list)&&list.length?list:defaultLocales;}catch(e){return defaultLocales;}}
            function normalizeLocaleTag(value){value=String(value||'').trim().replace('_','-');if(!value)return '';var lower=value.toLowerCase();if(lower.indexOf('zh')===0)return 'zh-CN';var map={'en':'en-US','de':'de-DE','fr':'fr-FR','es':'es-ES','pt':'pt-BR','ar':'ar','ja':'ja-JP','ko':'ko-KR','ru':'ru-RU','it':'it-IT','nl':'nl-NL','tr':'tr-TR','pl':'pl-PL','vi':'vi-VN','th':'th-TH','id':'id-ID','ms':'ms-MY','hi':'hi-IN','cs':'cs-CZ','hu':'hu-HU','ro':'ro-RO','sv':'sv-SE','da':'da-DK','fi':'fi-FI','nb':'nb-NO','no':'nb-NO','el':'el-GR','he':'he-IL','iw':'he-IL','uk':'uk-UA','sk':'cs-CZ','mt':'en-US'};var lang=lower.split('-')[0];return map[lower]||map[lang]||value;}
            function matchSupportedLocale(value){var supported=readSupportedLocales();var normalized=normalizeLocaleTag(value);if(!normalized)return '';if(supported.indexOf(normalized)>=0)return normalized;var legacy=localeAliases[normalized];if(legacy&&supported.indexOf(legacy)>=0)return legacy;var lang=normalized.split('-')[0];for(var i=0;i<supported.length;i++){var item=normalizeLocaleTag(supported[i]);if(item===normalized||supported[i]===lang||item.split('-')[0]===lang)return item;}return uiCopy[normalized]?normalized:'';}
            function locale(){var browser=(navigator.languages&&navigator.languages[0])||navigator.language||'';var site=document.documentElement.lang||'';var saas=root.getAttribute('data-gza-saas-locale')||root.getAttribute('data-gza-locale')||'zh-CN';return matchSupportedLocale(browser)||matchSupportedLocale(site)||matchSupportedLocale(saas)||'zh-CN';}
            function copy(){return uiCopy[normalizeLocaleTag(locale())]||uiCopy['en-US'];}
            function applyUiCopy(){var c=copy();trigger.setAttribute('aria-label',c.open);panel.setAttribute('aria-label',c.panel);close.setAttribute('aria-label',c.close);if(status)status.textContent=c.status;if(input)input.setAttribute('placeholder',c.placeholder);if(submit){submit.setAttribute('aria-label',c.send);submit.setAttribute('title',c.send);}}
            function sourcePage(){return location.pathname||'/';}
            function track(type,data){try{fetch(restBase+'/events',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({events:[{visitorId:visitorId,eventType:type,path:sourcePage(),url:location.href,title:document.title,referrer:document.referrer,eventData:data||{}}]})});}catch(e){}}
            function touchConversation(){}
            function responsePayload(data){if(data&&data.data&&typeof data.data==='object'){var payload=data.data;payload.ok=data.ok!==false;if(data.requestId&&!payload.requestId)payload.requestId=data.requestId;return payload;}return data||{};}
            function postChatMessage(payload){return fetch(restBase+'/chat/message',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}).then(function(r){return r.json()}).then(responsePayload);}
            function rememberMessage(message){if(!message||!message.id||seenMessages[message.id])return false;seenMessages[message.id]=true;if(message.createdAt){lastMessageAt=message.createdAt;localStorage.setItem(lastMessageKey,lastMessageAt);}touchConversation();return true;}
            function renderDirectMessage(pending,data,fallback){data=responsePayload(data);if(data&&data.ok&&data.conversationId){conversationId=data.conversationId;localStorage.setItem(conversationKey,conversationId);touchConversation();}if(data&&data.message){pending.classList.remove('loading');rememberMessage(data.message);pending.textContent=data.message.content||fallback;return true;}return false;}
            function fetchMessages(after){if(!conversationId)return Promise.resolve([]);return fetch(restBase+'/chat/messages?conversationId='+encodeURIComponent(conversationId)+'&after='+encodeURIComponent(after||''),{cache:'no-store'}).then(function(r){return r.json()}).then(function(data){data=responsePayload(data);if(!data||!data.ok||!Array.isArray(data.messages))return[];return data.messages;});}
            function renderRecoveredMessages(pending,list,fallback){var rendered=false;list.forEach(function(message){if(rememberMessage(message)){if(!rendered){pending.classList.remove('loading');pending.textContent=message.content||fallback;rendered=true;}else{add('assistant',message.content||'');}}});return rendered;}
            function recoverPendingReply(pending,after,fallback,payload){var attempts=0;var maxAttempts=12;function retry(){if(attempts<maxAttempts){setTimeout(tick,2500);}else{pending.classList.remove('loading');pending.textContent=fallback;}}function tick(){attempts+=1;if(!conversationId&&payload){postChatMessage(payload).then(function(data){if(renderDirectMessage(pending,data,fallback))return;retry();}).catch(retry);return;}if(!conversationId){pending.classList.remove('loading');pending.textContent=fallback;return;}fetchMessages(after).then(function(list){if(renderRecoveredMessages(pending,list,fallback))return;retry();}).catch(retry);}setTimeout(tick,2000);}
            function pollMessages(){if(!conversationId||!operatorOnline||panel.hidden)return;fetchMessages(lastMessageAt||'').then(function(list){list.forEach(function(message){if(rememberMessage(message)){add('assistant',message.content||'');}});}).catch(function(){});}
            function startMessagePolling(){if(messagePollTimer||!conversationId||!operatorOnline||panel.hidden)return;messagePollTimer=setInterval(pollMessages,messageIntervalMs);pollMessages();}
            function stopMessagePolling(){if(messagePollTimer){clearInterval(messagePollTimer);messagePollTimer=null;}}
            function checkPresence(){if(panel.hidden){stopPresenceLoop();return;}fetch(restBase+'/chat/presence?conversationId='+encodeURIComponent(conversationId||''),{cache:'no-store'}).then(function(r){return r.json()}).then(function(data){if(!data||!data.ok){operatorOnline=false;stopMessagePolling();return;}operatorOnline=!!data.shouldPollMessages;presenceIntervalMs=Math.max(10000,Math.min(60000,Number(data.suggestedPresenceIntervalMs)||30000));messageIntervalMs=Math.max(3000,Math.min(15000,Number(data.suggestedMessageIntervalMs)||6000));if(operatorOnline){startMessagePolling();}else{stopMessagePolling();}}).catch(function(){operatorOnline=false;stopMessagePolling();});}
            function startPresenceLoop(){if(panel.hidden)return;if(!presenceTimer){presenceTimer=setInterval(checkPresence,presenceIntervalMs);}checkPresence();}
            function stopPresenceLoop(){if(presenceTimer){clearInterval(presenceTimer);presenceTimer=null;}operatorOnline=false;stopMessagePolling();}
            function ensureWelcome(){var cached=root.getAttribute('data-gza-welcome')||'';if(cached&&!messages.childElementCount)add('assistant',cached);}
            function loadConfig(){fetch(restBase+'/chat/config?locale='+encodeURIComponent(locale()),{cache:'no-store'}).then(function(r){return r.json()}).then(function(data){if(data&&data.ok&&data.config){if(data.supportedLocales){root.setAttribute('data-gza-supported-locales',JSON.stringify(data.supportedLocales));}if(data.locale){root.setAttribute('data-gza-saas-locale',data.locale);}applyUiCopy();if(data.config.enabled===false||data.config.widgetEnabled===false){root.style.display='none';return;}name.textContent=data.config.assistantName||name.textContent;if(data.config.welcomeMessage&&!messages.childElementCount)add('assistant',data.config.welcomeMessage);}}).catch(function(){});}
            applyUiCopy();
            trigger.addEventListener('click',function(){root.classList.add('is-open');panel.hidden=false;applyUiCopy();ensureWelcome();loadConfig();track('chat_opened');startPresenceLoop();});
            close.addEventListener('click',function(){track('chat_closed',{conversationId:conversationId||''});panel.hidden=true;root.classList.remove('is-open');stopPresenceLoop();});
            form.addEventListener('submit',function(e){e.preventDefault();var text=(input.value||'').trim();if(!text)return;var c=copy();var recoverAfter=lastMessageAt||'';var payload={conversationId:conversationId||null,message:text,locale:locale(),sourcePage:sourcePage(),visitor:{visitorId:visitorId}};input.value='';add('user',text);track('chat_message',{direction:'outbound',conversationId:conversationId||''});touchConversation();var pending=add('assistant',c.loading,true);postChatMessage(payload).then(function(data){if(!renderDirectMessage(pending,data,c.error)){recoverPendingReply(pending,recoverAfter,c.error,payload);}checkPresence();}).catch(function(){recoverPendingReply(pending,recoverAfter,copy().network,payload);});});
            track('page_view');
        })();
        </script>
        <?php
    }

    public function check_for_plugin_update($transient) {
        if (!is_object($transient) || empty($transient->checked)) {
            return $transient;
        }

        $manifest = $this->get_update_manifest($this->get_options());
        $payload = $this->build_update_payload($manifest);

        if ($payload) {
            $transient->response[plugin_basename(__FILE__)] = $payload;
        }

        return $transient;
    }

    public function plugin_information($result, $action, $args) {
        if ($action !== 'plugin_information' || empty($args->slug) || $args->slug !== self::PLUGIN_SLUG) {
            return $result;
        }

        $manifest = $this->get_update_manifest($this->get_options(), true);
        $payload = $this->build_update_payload($manifest);

        if (!$payload) {
            return $result;
        }

        return (object) [
            'name' => $manifest['name'] ?? 'GrowthOS',
            'slug' => self::PLUGIN_SLUG,
            'version' => $manifest['version'],
            'author' => $manifest['author'] ?? 'GUANGERGAOZHI',
            'homepage' => $manifest['homepage'] ?? 'https://www.tui161.com',
            'requires' => $manifest['requires'] ?? '6.0',
            'requires_php' => $manifest['requiresPhp'] ?? '7.4',
            'tested' => $manifest['tested'] ?? '',
            'download_link' => $this->manifest_package_url($manifest),
            'sections' => [
                'description' => $manifest['description'] ?? 'Connect WordPress to GrowthOS.',
                'changelog' => $manifest['changelog'] ?? 'Maintenance update.',
            ],
        ];
    }

    public function rest_chat_config(): WP_REST_Response {
        $options = $this->get_options();
        if (empty($options['site_token'])) {
            return new WP_REST_Response(['ok' => false, 'message' => 'Site is not activated.'], 401);
        }

        $locale = isset($_GET['locale']) ? sanitize_text_field(wp_unslash($_GET['locale'])) : determine_locale();
        $response = wp_remote_get(add_query_arg('locale', $locale, $options['api_base'] . '/api/connector/v1/chat/config'), [
            'timeout' => self::CONFIG_TIMEOUT,
            'headers' => $this->auth_headers($options),
        ]);
        $body = $this->decode_response($response);
        if (empty($body['ok'])) {
            $cached_config = get_option(self::CHAT_CONFIG_CACHE_OPTION, []);
            if (is_array($cached_config) && !empty($cached_config)) {
                return new WP_REST_Response(['ok' => true, 'config' => $cached_config, 'stale' => true]);
            }
        } else {
            if (!empty($body['config']) && is_array($body['config'])) {
                update_option(self::CHAT_CONFIG_CACHE_OPTION, $this->sanitize_chat_config_cache($body), false);
            }
            $this->remember_cloud_success($options);
        }

        return new WP_REST_Response($body);
    }

    public function rest_chat_message(WP_REST_Request $request): WP_REST_Response {
        $options = $this->get_options();
        if (empty($options['site_token'])) {
            return new WP_REST_Response(['ok' => false, 'message' => 'Site is not activated.'], 401);
        }

        $payload = $request->get_json_params();
        $payload = is_array($payload) ? $payload : [];
        $response = wp_remote_post($options['api_base'] . '/api/connector/v1/chat/messages', [
            'timeout' => self::CHAT_TIMEOUT,
            'headers' => array_merge($this->auth_headers($options), [
                'Content-Type' => 'application/json',
                'Idempotency-Key' => $this->idempotency_key('chat-message', $payload),
            ]),
            'body' => wp_json_encode($payload),
        ]);
        $body = $this->decode_response($response);
        if (empty($body['ok'])) {
            return new WP_REST_Response($body, 502);
        } else {
            $this->remember_cloud_success($options);
        }

        return new WP_REST_Response($body);
    }

    public function rest_chat_presence(WP_REST_Request $request): WP_REST_Response {
        $options = $this->get_options();
        if (empty($options['site_token'])) {
            return new WP_REST_Response(['ok' => false, 'message' => 'Site is not activated.'], 401);
        }

        $offline = [
            'ok' => true,
            'operatorOnline' => false,
            'operatorActiveSessions' => 0,
            'operatorLastSeenAt' => null,
            'shouldPollMessages' => false,
            'suggestedPresenceIntervalMs' => 30000,
            'suggestedMessageIntervalMs' => 0,
        ];

        $conversation_id = sanitize_text_field((string) $request->get_param('conversationId'));
        $response = wp_remote_get(add_query_arg([
            'conversationId' => $conversation_id,
        ], $options['api_base'] . '/api/connector/v1/chat/presence'), [
            'timeout' => self::CONFIG_TIMEOUT,
            'headers' => $this->auth_headers($options),
        ]);
        $body = $this->decode_response($response);
        if (empty($body['ok'])) {
            return new WP_REST_Response(array_merge($offline, ['cloudUnavailable' => true]));
        }

        $this->remember_cloud_success($options);

        return new WP_REST_Response(array_merge($offline, [
            'operatorOnline' => !empty($body['operatorOnline']),
            'operatorActiveSessions' => isset($body['operatorActiveSessions']) ? max(0, (int) $body['operatorActiveSessions']) : 0,
            'operatorLastSeenAt' => isset($body['operatorLastSeenAt']) ? sanitize_text_field((string) $body['operatorLastSeenAt']) : null,
            'shouldPollMessages' => !empty($body['shouldPollMessages']),
            'suggestedPresenceIntervalMs' => isset($body['suggestedPresenceIntervalMs']) ? max(10000, min(60000, (int) $body['suggestedPresenceIntervalMs'])) : 30000,
            'suggestedMessageIntervalMs' => isset($body['suggestedMessageIntervalMs']) ? max(0, min(15000, (int) $body['suggestedMessageIntervalMs'])) : 0,
        ]));
    }

    public function rest_chat_messages(WP_REST_Request $request): WP_REST_Response {
        $options = $this->get_options();
        if (empty($options['site_token'])) {
            return new WP_REST_Response(['ok' => false, 'message' => 'Site is not activated.'], 401);
        }

        $conversation_id = sanitize_text_field((string) $request->get_param('conversationId'));
        $after = sanitize_text_field((string) $request->get_param('after'));
        if (!$conversation_id) {
            return new WP_REST_Response(['ok' => false, 'message' => 'Conversation is required.'], 400);
        }

        $response = wp_remote_get(add_query_arg([
            'conversationId' => $conversation_id,
            'after' => $after,
        ], $options['api_base'] . '/api/connector/v1/chat/messages'), [
            'timeout' => self::CONFIG_TIMEOUT,
            'headers' => $this->auth_headers($options),
        ]);
        $body = $this->decode_response($response);
        if (empty($body['ok'])) {
            return new WP_REST_Response(['ok' => true, 'messages' => [], 'cloudUnavailable' => true]);
        } else {
            $this->remember_cloud_success($options);
        }

        return new WP_REST_Response($body);
    }

    public function rest_track_events(WP_REST_Request $request): WP_REST_Response {
        $options = $this->get_options();
        if (empty($options['site_token'])) {
            return new WP_REST_Response(['ok' => true, 'accepted' => 0, 'deferred' => true], 202);
        }

        $payload = $request->get_json_params();
        $events = is_array($payload) && !empty($payload['events']) && is_array($payload['events']) ? $payload['events'] : [];
        $queued = $this->queue_customer_events($events);
        $this->schedule_event_flush();

        return new WP_REST_Response(['ok' => true, 'accepted' => $queued, 'queued' => true], 202);
    }

    public function rest_wake_jobs(WP_REST_Request $request): WP_REST_Response {
        $options = $this->get_options();
        if (empty($options['site_token']) || empty($options['site_id'])) {
            return new WP_REST_Response(['ok' => false, 'message' => 'Site is not activated.'], 401);
        }

        $timestamp = sanitize_text_field((string) ($request->get_header('x-gza-timestamp') ?: ''));
        $signature = sanitize_text_field((string) ($request->get_header('x-gza-signature') ?: ''));
        $site_id = sanitize_text_field((string) ($request->get_header('x-gza-site-id') ?: ''));
        $payload = $request->get_json_params();
        $payload = is_array($payload) ? $payload : [];
        $job_id = sanitize_text_field((string) ($payload['jobId'] ?? ''));
        $job_type = sanitize_text_field((string) ($payload['jobType'] ?? ''));

        if (!$this->is_valid_job_wake_signature($options, $site_id, $job_id, $job_type, $timestamp, $signature)) {
            return new WP_REST_Response(['ok' => false, 'message' => 'Invalid wake signature.'], 403);
        }

        $this->schedule_cloud_job_run(0);
        if (function_exists('ignore_user_abort')) {
            ignore_user_abort(true);
        }
        register_shutdown_function(function () {
            $this->process_cloud_jobs();
        });

        return new WP_REST_Response([
            'ok' => true,
            'accepted' => true,
            'jobId' => $job_id,
            'jobType' => $job_type,
        ], 202);
    }

    public function flush_queued_events(): void {
        $options = $this->get_options();
        if (empty($options['site_token'])) {
            return;
        }

        $queue = get_option(self::EVENT_QUEUE_OPTION, []);
        if (!is_array($queue) || empty($queue)) {
            return;
        }

        $batch = array_slice($queue, 0, 50);
        $remaining = array_slice($queue, count($batch));
        $payload = ['events' => $batch];
        $response = wp_remote_post($options['api_base'] . '/api/connector/v1/events', [
            'timeout' => self::JOB_TIMEOUT,
            'headers' => array_merge($this->auth_headers($options), [
                'Content-Type' => 'application/json',
                'Idempotency-Key' => $this->idempotency_key('events', $payload),
            ]),
            'body' => wp_json_encode($payload),
        ]);
        $body = $this->decode_response($response);

        if (!empty($body['ok'])) {
            update_option(self::EVENT_QUEUE_OPTION, $remaining, false);
        }

        if (!empty($remaining)) {
            $this->schedule_event_flush(60);
        }
    }

    public function register_cron_schedules($schedules) {
        if (!isset($schedules['gza_one_minute'])) {
            $schedules['gza_one_minute'] = [
                'interval' => MINUTE_IN_SECONDS,
                'display' => 'Every minute for GrowthOS event queue',
            ];
        }
        if (!isset($schedules['gza_five_minutes'])) {
            $schedules['gza_five_minutes'] = [
                'interval' => 5 * MINUTE_IN_SECONDS,
                'display' => 'Every five minutes for GrowthOS',
            ];
        }
        if (!isset($schedules['gza_fifteen_minutes'])) {
            $schedules['gza_fifteen_minutes'] = [
                'interval' => 15 * MINUTE_IN_SECONDS,
                'display' => 'Every fifteen minutes for GrowthOS incremental content sync',
            ];
        }

        return $schedules;
    }

    private function has_action_scheduler(): bool {
        return function_exists('as_has_scheduled_action') && function_exists('as_schedule_recurring_action');
    }

    private function schedule_recurring_runner(string $hook, int $interval, string $wp_schedule): void {
        if ($this->has_action_scheduler()) {
            if (!as_has_scheduled_action($hook, [], 'growthos')) {
                as_schedule_recurring_action(time() + 60, $interval, $hook, [], 'growthos');
            }
            return;
        }

        $next = wp_next_scheduled($hook);
        $schedule = wp_get_schedule($hook);

        if ($next && $schedule !== $wp_schedule) {
            wp_clear_scheduled_hook($hook);
            $next = false;
        }

        if (!$next) {
            wp_schedule_event(time() + 60, $wp_schedule, $hook);
        }
    }

    private function overlay_table_name(): string {
        global $wpdb;
        return $wpdb->prefix . 'gza_ai_growth_seo_overlays';
    }

    private function maybe_setup_database(): void {
        if (get_option(self::DB_VERSION_OPTION) === self::DB_VERSION) {
            return;
        }

        global $wpdb;
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';

        $charset_collate = $wpdb->get_charset_collate();
        $table = $this->overlay_table_name();
        $sql = "CREATE TABLE {$table} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            scope_hash char(64) NOT NULL,
            path_hash char(32) NOT NULL,
            path varchar(255) NOT NULL,
            locale varchar(32) NOT NULL,
            overlay_id varchar(80) NOT NULL DEFAULT '',
            title text NULL,
            description text NULL,
            ai_summary longtext NULL,
            payload_json longtext NOT NULL,
            updated_at datetime NOT NULL,
            created_at datetime NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY uniq_scope_path_locale (scope_hash, path_hash, locale),
            KEY idx_scope_updated (scope_hash, updated_at)
        ) {$charset_collate};";

        dbDelta($sql);
        update_option(self::DB_VERSION_OPTION, self::DB_VERSION, false);
    }

    public function maybe_schedule_job_runner(): void {
        $this->maybe_setup_database();

        if (get_option(self::CRON_VERSION_OPTION) !== self::CRON_VERSION) {
            wp_clear_scheduled_hook('gza_ai_growth_process_jobs');
            wp_clear_scheduled_hook('gza_ai_growth_flush_events');
            wp_clear_scheduled_hook('gza_ai_growth_incremental_sync');
            update_option(self::CRON_VERSION_OPTION, self::CRON_VERSION, false);
        }

        $this->schedule_recurring_runner('gza_ai_growth_process_jobs', 5 * MINUTE_IN_SECONDS, 'gza_five_minutes');
        $this->schedule_recurring_runner('gza_ai_growth_flush_events', MINUTE_IN_SECONDS, 'gza_one_minute');
        $this->schedule_recurring_runner('gza_ai_growth_incremental_sync', 15 * MINUTE_IN_SECONDS, 'gza_fifteen_minutes');
    }

    public function process_cloud_jobs(): void {
        $options = $this->get_options();
        $this->flush_queued_events();
        $this->run_cloud_jobs($options, 3);
        update_option(self::OPTION_KEY, $options, false);
    }

    public function process_incremental_sync(): void {
        $options = $this->get_options();
        if (empty($options['site_token']) || $this->is_cloud_temporarily_unavailable($options)) {
            return;
        }
        if (!$this->should_run_incremental_sync($options)) {
            return;
        }
        if (get_transient('gza_ai_growth_incremental_sync_lock')) {
            return;
        }

        set_transient('gza_ai_growth_incremental_sync_lock', '1', 10 * MINUTE_IN_SECONDS);
        $this->sync_content($options, true);
        delete_transient('gza_ai_growth_incremental_sync_lock');
        update_option(self::OPTION_KEY, $options, false);
    }

    private function activate_site(array $options, string $license_code): array {
        if (!$license_code) {
            return ['options' => $options, 'message' => 'License code is required.'];
        }

        $payload = [
            'licenseCode' => $license_code,
            'domain' => home_url(),
            'platform' => 'wordpress',
            'siteName' => get_bloginfo('name'),
            'primaryLocale' => str_replace('_', '-', determine_locale()),
            'supportedLocales' => $this->detect_supported_locales(),
        ];
        $response = wp_remote_post($options['api_base'] . '/api/connector/v1/auth/activate', [
            'timeout' => self::ADMIN_TIMEOUT,
            'headers' => [
                'Content-Type' => 'application/json',
                'Idempotency-Key' => $this->idempotency_key('activate', $payload),
            ],
            'body' => wp_json_encode($payload),
        ]);

        $body = $this->decode_response($response);
        if (empty($body['ok']) || empty($body['token'])) {
            $options['last_error'] = sanitize_text_field($body['message'] ?? 'Activation failed.');
            $this->remember_cloud_failure($options, $options['last_error'], 60);
            return ['options' => $options, 'message' => $body['message'] ?? 'Activation failed.'];
        }

        $options['site_token'] = sanitize_text_field($body['token']);
        $options['tenant_id'] = sanitize_text_field($body['tenant']['id'] ?? '');
        $options['site_id'] = sanitize_text_field($body['site']['id'] ?? '');
        $options['last_status_checked_at'] = current_time('mysql');
        $options['last_error'] = '';
        $this->remember_cloud_success($options);

        return ['options' => $options, 'message' => 'Site activated.'];
    }

    private function check_status(array &$options): string {
        if (empty($options['site_token'])) {
            $options['last_error'] = 'Site is not activated.';
            return 'Site is not activated.';
        }

        $response = wp_remote_get($options['api_base'] . '/api/connector/v1/site/status', [
            'timeout' => self::ADMIN_TIMEOUT,
            'headers' => $this->auth_headers($options),
        ]);
        $body = $this->decode_response($response);

        if (empty($body['ok'])) {
            $options['last_status_checked_at'] = current_time('mysql');
            $options['last_error'] = sanitize_text_field($body['message'] ?? 'Status check failed.');
            $this->remember_cloud_failure($options, $options['last_error'], 60);
            return $body['message'] ?? 'Status check failed.';
        }

        $balance = isset($body['quota']['balance']) ? (int) $body['quota']['balance'] : 0;
        $diagnostics = isset($body['diagnostics']) && is_array($body['diagnostics']) ? $body['diagnostics'] : [];
        $content_count = isset($diagnostics['syncedContentCount']) ? (int) $diagnostics['syncedContentCount'] : 0;
        $overlay_count = isset($diagnostics['publishedOverlayCount']) ? (int) $diagnostics['publishedOverlayCount'] : 0;
        $job_count = isset($diagnostics['activeJobCount']) ? (int) $diagnostics['activeJobCount'] : 0;
        $locales = !empty($diagnostics['locales']) && is_array($diagnostics['locales']) ? implode(', ', array_map('sanitize_text_field', $diagnostics['locales'])) : '-';
        $options['last_status_checked_at'] = current_time('mysql');
        $options['last_error'] = '';
        $this->remember_cloud_success($options);

        return 'Connected. Quota balance: ' . $balance . '. Synced content: ' . $content_count . '. Published SEO/GEO overlays: ' . $overlay_count . '. Active cloud jobs: ' . $job_count . '. Locales: ' . $locales . '.';
    }

    private function sync_content(array &$options, bool $incremental = true): string {
        if (empty($options['site_token'])) {
            $options['last_error'] = 'Site is not activated.';
            return 'Site is not activated.';
        }

        $items = $this->collect_content_items();
        if (!$items) {
            $payload = [
                'items' => [],
                'snapshot' => [
                    'complete' => true,
                    'seen' => [],
                ],
            ];
            $response = wp_remote_post($options['api_base'] . '/api/connector/v1/content/sync', [
                'timeout' => self::SYNC_TIMEOUT,
                'headers' => array_merge($this->auth_headers($options), [
                    'Content-Type' => 'application/json',
                    'Idempotency-Key' => $this->idempotency_key('content-sync', $payload),
                ]),
                'body' => wp_json_encode($payload),
            ]);
            $body = $this->decode_response($response);

            if (empty($body['ok'])) {
                $options['last_error'] = sanitize_text_field($body['message'] ?? 'Content sync failed.');
                $this->remember_cloud_failure($options, $options['last_error'], 60);
                return $body['message'] ?? 'Content sync failed.';
            }

            $this->write_content_manifest($options, []);
            $options['last_sync_at'] = current_time('mysql');
            $options['last_sync_count'] = '0';
            $options['last_content_inventory'] = $this->content_inventory_signature();
            $options['last_error'] = '';
            $this->remember_cloud_success($options);

            return 'Synced 0 content items.';
        }

        $manifest = $this->read_content_manifest($options);
        $next_manifest = [];
        $changed_items = [];
        foreach ($items as $item) {
            $key = $this->content_item_manifest_key($item);
            if (!$key) {
                continue;
            }
            $checksum = $this->content_item_checksum($item);
            $next_manifest[$key] = $checksum;
            if (!$incremental || !isset($manifest[$key]) || !hash_equals((string) $manifest[$key], $checksum)) {
                $changed_items[] = $item;
            }
        }

        $synced = 0;
        $items_to_send = $incremental ? $changed_items : $items;
        $chunks = $items_to_send ? array_chunk($items_to_send, self::CLOUD_SYNC_BATCH_SIZE) : [[]];
        $snapshot_complete = count($items) < self::SYNC_MAX_ITEMS;
        $seen_items = array_values(array_map(function ($item) {
            return [
                'externalId' => isset($item['externalId']) ? (string) $item['externalId'] : '',
                'locale' => isset($item['locale']) ? (string) $item['locale'] : '',
            ];
        }, $items));

        foreach ($chunks as $chunk_index => $chunk) {
            $payload = ['items' => $chunk];
            if ($snapshot_complete && $chunk_index === count($chunks) - 1) {
                $payload['snapshot'] = [
                    'complete' => true,
                    'seen' => $seen_items,
                ];
            }

            $response = wp_remote_post($options['api_base'] . '/api/connector/v1/content/sync', [
                'timeout' => self::SYNC_TIMEOUT,
                'headers' => array_merge($this->auth_headers($options), [
                    'Content-Type' => 'application/json',
                    'Idempotency-Key' => $this->idempotency_key('content-sync', $payload),
                ]),
                'body' => wp_json_encode($payload),
            ]);
            $body = $this->decode_response($response);

            if (empty($body['ok'])) {
                $options['last_error'] = sanitize_text_field($body['message'] ?? 'Content sync failed.');
                $this->remember_cloud_failure($options, $options['last_error'], 60);
                return $body['message'] ?? 'Content sync failed.';
            }

            $synced += (int) ($body['synced'] ?? count($chunk));
        }

        $this->write_content_manifest($options, $next_manifest);
        $options['last_sync_at'] = current_time('mysql');
        $options['last_sync_count'] = (string) $synced;
        $options['last_content_inventory'] = $this->content_inventory_signature();
        $options['last_error'] = '';
        $this->remember_cloud_success($options);

        return $incremental && !$synced
            ? 'Content already up to date.'
            : 'Synced ' . $synced . ' content items.';
    }

    private function run_cloud_jobs(array &$options, int $limit = 3): string {
        if (empty($options['site_token'])) {
            $options['last_error'] = 'Site is not activated.';
            return 'Site is not activated.';
        }

        $processed = 0;
        $messages = [];
        $claim_run_id = uniqid('claim_', true);

        for ($i = 0; $i < max(1, $limit); $i++) {
            $job = $this->claim_cloud_job($options, $claim_run_id, $i);
            if (!$job) {
                break;
            }

            $result = $this->execute_cloud_job($options, $job);
            $completed = $this->complete_cloud_job(
                $options,
                (string) $job['id'],
                isset($job['claimToken']) ? (string) $job['claimToken'] : '',
                !empty($result['ok']),
                isset($result['result']) && is_array($result['result']) ? $result['result'] : [],
                isset($result['error']) ? (string) $result['error'] : ''
            );

            $processed++;
            $messages[] = sanitize_text_field((string) ($job['title'] ?? $job['type'] ?? 'Cloud job')) . ': ' . ($completed ? (!empty($result['ok']) ? 'done' : 'failed') : 'completion callback failed');
        }

        $options['last_job_at'] = current_time('mysql');
        $options['last_job_count'] = (string) $processed;
        if ($processed && empty($options['last_error'])) {
            $options['last_error'] = '';
        }

        return $processed ? implode(' ', $messages) : 'No pending cloud jobs.';
    }

    private function claim_cloud_job(array $options, string $claim_run_id = '', int $claim_index = 0): ?array {
        if ($this->is_cloud_temporarily_unavailable($options)) {
            return null;
        }

        $payload = [
            'site' => (string) ($options['site_id'] ?? ''),
            'run' => $claim_run_id ?: (string) time(),
            'index' => $claim_index,
        ];
        $response = wp_remote_post($options['api_base'] . '/api/connector/v1/site/jobs/next', [
            'timeout' => self::JOB_TIMEOUT,
            'headers' => array_merge($this->auth_headers($options), [
                'Idempotency-Key' => $this->idempotency_key('job-claim', $payload),
            ]),
        ]);
        $body = $this->decode_response($response);

        if (empty($body['ok'])) {
            $this->remember_cloud_failure($options, sanitize_text_field($body['message'] ?? 'Cloud job polling unavailable.'));
            return null;
        }

        $this->remember_cloud_success($options);

        return !empty($body['job']) && is_array($body['job']) ? $body['job'] : null;
    }

    private function execute_cloud_job(array &$options, array $job): array {
        $type = isset($job['type']) ? (string) $job['type'] : '';
        $payload = isset($job['payload']) && is_array($job['payload']) ? $job['payload'] : [];

        if ($type === 'sync_content') {
            $message = $this->sync_content($options, empty($payload['forceFull']));
            $ok = stripos($message, 'Synced ') === 0;
            if ($message === 'Content already up to date.') {
                $ok = true;
            }
            return [
                'ok' => $ok,
                'result' => [
                    'message' => $message,
                    'synced' => (int) ($options['last_sync_count'] ?: 0),
                    'lastSyncAt' => $options['last_sync_at'],
                ],
                'error' => $ok ? '' : $message,
            ];
        }

        if ($type === 'publish_article') {
            return $this->publish_article_from_job($options, $payload);
        }

        if ($type === 'apply_seo_overlay') {
            return $this->apply_seo_overlay_from_job($options, $payload);
        }

        return ['ok' => false, 'result' => [], 'error' => 'Unsupported cloud job type.'];
    }

    private function complete_cloud_job(array &$options, string $job_id, string $claim_token, bool $ok, array $result = [], string $error = ''): bool {
        if (!$job_id) {
            return false;
        }

        $payload = [
            'ok' => $ok,
            'result' => $result,
            'error' => $error,
        ];
        if ($claim_token !== '') {
            $payload['claimToken'] = $claim_token;
        }
        $response = wp_remote_post($options['api_base'] . '/api/connector/v1/site/jobs/' . rawurlencode($job_id) . '/complete', [
            'timeout' => self::JOB_TIMEOUT,
            'headers' => array_merge($this->auth_headers($options), [
                'Content-Type' => 'application/json',
                'Idempotency-Key' => $this->idempotency_key('job-complete:' . $job_id, $payload),
            ]),
            'body' => wp_json_encode($payload),
        ]);
        $body = $this->decode_response($response);

        if (empty($body['ok'])) {
            $message = sanitize_text_field($body['message'] ?? 'Cloud job completion callback failed.');
            $this->remember_cloud_failure($options, $message, 60);
            return false;
        }

        $this->remember_cloud_success($options);
        return true;
    }

    private function content_item_manifest_key(array $item): string {
        $external_id = isset($item['externalId']) ? (string) $item['externalId'] : '';
        $locale = isset($item['locale']) ? (string) $item['locale'] : '';
        return $external_id ? $external_id . '::' . $locale : '';
    }

    private function content_manifest_scope(array $options): string {
        $parts = [
            $this->normalize_api_base((string) ($options['api_base'] ?? '')),
            (string) ($options['tenant_id'] ?? ''),
            (string) ($options['site_id'] ?? ''),
        ];

        return hash('sha256', implode('|', $parts));
    }

    private function read_content_manifest(array $options): array {
        $manifest = get_option(self::CONTENT_MANIFEST_OPTION, []);
        if (!is_array($manifest)) {
            return [];
        }

        if (isset($manifest['_scope'], $manifest['items']) && is_array($manifest['items'])) {
            return hash_equals((string) $manifest['_scope'], $this->content_manifest_scope($options))
                ? $manifest['items']
                : [];
        }

        return [];
    }

    private function write_content_manifest(array $options, array $items): void {
        update_option(self::CONTENT_MANIFEST_OPTION, [
            '_scope' => $this->content_manifest_scope($options),
            'updatedAt' => current_time('mysql'),
            'items' => $items,
        ], false);
    }

    private function truncate_sync_text(string $value, int $max_chars): string {
        if ($max_chars <= 0) {
            return '';
        }

        if (function_exists('mb_strlen') && function_exists('mb_substr')) {
            return mb_strlen($value) > $max_chars ? mb_substr($value, 0, $max_chars) : $value;
        }

        return strlen($value) > $max_chars ? substr($value, 0, $max_chars) : $value;
    }

    private function content_item_checksum(array $item): string {
        $payload = [
            'externalId' => isset($item['externalId']) ? (string) $item['externalId'] : '',
            'type' => isset($item['type']) ? (string) $item['type'] : '',
            'locale' => isset($item['locale']) ? (string) $item['locale'] : '',
            'path' => isset($item['path']) ? (string) $item['path'] : '',
            'url' => isset($item['url']) ? (string) $item['url'] : '',
            'title' => isset($item['title']) ? (string) $item['title'] : '',
            'excerpt' => isset($item['excerpt']) ? (string) $item['excerpt'] : '',
            'content' => isset($item['content']) ? (string) $item['content'] : '',
            'status' => isset($item['status']) ? (string) $item['status'] : '',
            'raw' => isset($item['raw']) && is_array($item['raw']) ? $item['raw'] : [],
        ];

        return hash('sha256', (string) wp_json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
    }

    private function should_run_incremental_sync(array $options): bool {
        $last_sync = isset($options['last_sync_at']) ? strtotime((string) $options['last_sync_at']) : 0;
        if (!$last_sync) {
            return true;
        }

        if (time() - $last_sync > 6 * HOUR_IN_SECONDS) {
            return true;
        }

        $previous = isset($options['last_content_inventory']) ? (string) $options['last_content_inventory'] : '';
        $current = $this->content_inventory_signature();

        return !$previous || !hash_equals($previous, $current);
    }

    private function content_inventory_signature(): string {
        $post_types = ['page', 'post'];
        if (post_type_exists('product')) {
            $post_types[] = 'product';
        }

        $parts = [];
        foreach ($post_types as $post_type) {
            $counts = wp_count_posts($post_type);
            $published_count = isset($counts->publish) ? (int) $counts->publish : 0;
            $latest = get_posts([
                'post_type' => $post_type,
                'post_status' => 'publish',
                'posts_per_page' => 1,
                'orderby' => 'modified',
                'order' => 'DESC',
                'fields' => 'ids',
                'no_found_rows' => true,
            ]);
            $latest_modified = !empty($latest[0]) ? (int) get_post_modified_time('U', true, (int) $latest[0]) : 0;
            $parts[] = $post_type . ':' . $published_count . ':' . $latest_modified;
        }

        foreach (['category', 'product_cat'] as $taxonomy) {
            if (!taxonomy_exists($taxonomy)) {
                continue;
            }
            $count = wp_count_terms([
                'taxonomy' => $taxonomy,
                'hide_empty' => false,
            ]);
            $parts[] = $taxonomy . ':' . (is_wp_error($count) ? 0 : (int) $count);
        }

        $parts[] = implode(',', $this->detect_supported_locales());

        return hash('sha256', implode('|', $parts));
    }

    private function find_published_article_post_id(array $payload): int {
        $post_id = absint($payload['postId'] ?? 0);
        if (!$post_id && !empty($payload['externalId']) && is_numeric((string) $payload['externalId'])) {
            $post_id = absint($payload['externalId']);
        }
        if ($post_id && get_post($post_id)) {
            return $post_id;
        }

        foreach (['path', 'sourcePath'] as $path_key) {
            $candidate_path = (string) ($payload[$path_key] ?? '');
            if (preg_match('#/(?:archives|p)/(\d+)/?$#', $candidate_path, $matches)) {
                $post_id = absint($matches[1]);
                if ($post_id && get_post($post_id)) {
                    return $post_id;
                }
            }
        }

        $overlay_id = sanitize_text_field((string) ($payload['overlayId'] ?? ''));
        if (!$overlay_id) {
            return 0;
        }

        $existing = get_posts([
            'post_type' => ['post', 'page'],
            'post_status' => 'any',
            'posts_per_page' => 1,
            'fields' => 'ids',
            'meta_key' => '_gza_ai_growth_overlay_id',
            'meta_value' => $overlay_id,
            'no_found_rows' => true,
        ]);

        return !empty($existing[0]) ? (int) $existing[0] : 0;
    }

    private function save_published_article_metadata(int $post_id, string $locale, array $payload = []): void {
        update_post_meta($post_id, '_gza_ai_growth_locale', sanitize_text_field($locale));

        $overlay_id = sanitize_text_field((string) ($payload['overlayId'] ?? ''));
        if ($overlay_id) {
            update_post_meta($post_id, '_gza_ai_growth_overlay_id', $overlay_id);
        }

        if (!empty($payload['sourcePath'])) {
            update_post_meta($post_id, '_gza_ai_growth_source_path', sanitize_text_field((string) $payload['sourcePath']));
        }
    }

    private function schema_type_matches($type, string $expected): bool {
        if (is_array($type)) {
            foreach ($type as $item) {
                if ($this->schema_type_matches($item, $expected)) {
                    return true;
                }
            }
            return false;
        }

        if (!is_string($type)) {
            return false;
        }

        return strcasecmp($type, $expected) === 0 || stripos($type, $expected) !== false;
    }

    private function rewrite_article_schema_node($node, string $permalink, string $title, string $description, string $locale) {
        if (!is_array($node)) {
            return $node;
        }

        foreach ($node as $key => $value) {
            if (is_array($value)) {
                $node[$key] = $this->rewrite_article_schema_node($value, $permalink, $title, $description, $locale);
            }
        }

        $type = $node['@type'] ?? '';
        $base = trailingslashit($permalink);

        if ($this->schema_type_matches($type, 'Article') || $this->schema_type_matches($type, 'BlogPosting')) {
            $node['@id'] = $base . '#article';
            $node['url'] = $permalink;
            $node['headline'] = $title;
            $node['description'] = $description;
            $node['inLanguage'] = $locale;
            $node['mainEntityOfPage'] = [
                '@type' => 'WebPage',
                '@id' => $base . '#webpage',
            ];
        }

        if ($this->schema_type_matches($type, 'WebPage')) {
            $node['@id'] = $base . '#webpage';
            $node['url'] = $permalink;
            $node['name'] = $title;
            $node['description'] = $description;
            $node['inLanguage'] = $locale;
        }

        if ($this->schema_type_matches($type, 'FAQPage')) {
            $node['@id'] = $base . '#faq';
            $node['inLanguage'] = $locale;
        }

        if ($this->schema_type_matches($type, 'BreadcrumbList') && !empty($node['itemListElement']) && is_array($node['itemListElement'])) {
            $last_index = count($node['itemListElement']) - 1;
            if ($last_index >= 0 && is_array($node['itemListElement'][$last_index])) {
                $node['itemListElement'][$last_index]['name'] = $title;
                $node['itemListElement'][$last_index]['item'] = $permalink;
            }
        }

        return $node;
    }

    private function article_overlay_payload_for_post(array $payload, int $post_id, string $permalink, string $path, string $locale): array {
        $overlay = isset($payload['overlay']) && is_array($payload['overlay']) ? $payload['overlay'] : [];
        $title = sanitize_text_field((string) ($payload['title'] ?? ($overlay['title'] ?? get_the_title($post_id))));
        $description = sanitize_textarea_field((string) ($payload['description'] ?? ($overlay['description'] ?? '')));
        $ai_summary = sanitize_textarea_field((string) ($payload['aiSummary'] ?? ($overlay['aiSummary'] ?? '')));

        $overlay['id'] = sanitize_text_field((string) ($payload['overlayId'] ?? ($overlay['id'] ?? '')));
        $overlay['path'] = $path;
        $overlay['locale'] = $locale;
        $overlay['title'] = $title;
        $overlay['description'] = $description;
        $overlay['canonical'] = $permalink;
        $overlay['aiSummary'] = $ai_summary;
        $overlay['openGraph'] = array_merge(is_array($overlay['openGraph'] ?? null) ? $overlay['openGraph'] : [], [
            'type' => 'article',
            'title' => $title,
            'description' => $description,
            'url' => $permalink,
        ]);
        $overlay['twitter'] = array_merge(is_array($overlay['twitter'] ?? null) ? $overlay['twitter'] : [], [
            'card' => 'summary_large_image',
            'title' => $title,
            'description' => $description,
        ]);
        if (!empty($overlay['schemaGraph']) && is_array($overlay['schemaGraph'])) {
            $overlay['schemaGraph'] = $this->rewrite_article_schema_node($overlay['schemaGraph'], $permalink, $title, $description, $locale);
            $overlay['schemaJsonLd'] = [];
        } elseif (!empty($overlay['schemaJsonLd']) && is_array($overlay['schemaJsonLd'])) {
            $overlay['schemaJsonLd'] = array_map(function ($schema) use ($permalink, $title, $description, $locale) {
                return is_array($schema) ? $this->rewrite_article_schema_node($schema, $permalink, $title, $description, $locale) : $schema;
            }, $overlay['schemaJsonLd']);
        } else {
            $overlay['schemaJsonLd'] = [[
                '@context' => 'https://schema.org',
                '@type' => 'Article',
                '@id' => trailingslashit($permalink) . '#article',
                'url' => $permalink,
                'headline' => $title,
                'description' => $description,
                'inLanguage' => $locale,
                'mainEntityOfPage' => [
                    '@type' => 'WebPage',
                    '@id' => trailingslashit($permalink) . '#webpage',
                ],
            ]];
        }

        return $this->sanitize_overlay_payload($overlay);
    }

    private function flush_article_paragraph(array &$parts, array &$paragraph_lines): void {
        if (!$paragraph_lines) {
            return;
        }
        $parts[] = '<p>' . implode('<br />', array_map('esc_html', $paragraph_lines)) . '</p>';
        $paragraph_lines = [];
    }

    private function flush_article_list(array &$parts, array &$list_items): void {
        if (!$list_items) {
            return;
        }
        $parts[] = '<ul><li>' . implode('</li><li>', array_map('esc_html', $list_items)) . '</li></ul>';
        $list_items = [];
    }

    private function format_article_body_html(string $body, string $title): string {
        $body = str_replace(["\r\n", "\r"], "\n", trim($body));
        if ($body === '') {
            return '';
        }

        $parts = [];
        $paragraph_lines = [];
        $list_items = [];
        $lines = explode("\n", $body);
        $plain_title = trim(wp_strip_all_tags($title));

        foreach ($lines as $line) {
            $line = trim($line);
            if ($line === '') {
                $this->flush_article_paragraph($parts, $paragraph_lines);
                $this->flush_article_list($parts, $list_items);
                continue;
            }

            if (preg_match('/^#\s+(.+)$/u', $line, $matches)) {
                $heading = trim($matches[1]);
                if ($plain_title && $heading === $plain_title) {
                    continue;
                }
                $this->flush_article_paragraph($parts, $paragraph_lines);
                $this->flush_article_list($parts, $list_items);
                $parts[] = '<h2>' . esc_html($heading) . '</h2>';
                continue;
            }

            if (preg_match('/^#{2,6}\s+(.+)$/u', $line, $matches)) {
                $this->flush_article_paragraph($parts, $paragraph_lines);
                $this->flush_article_list($parts, $list_items);
                $parts[] = '<h2>' . esc_html(trim($matches[1])) . '</h2>';
                continue;
            }

            if (preg_match('/^[-*]\s+(.+)$/u', $line, $matches)) {
                $this->flush_article_paragraph($parts, $paragraph_lines);
                $list_items[] = trim($matches[1]);
                continue;
            }

            if (preg_match('/^\d+\.\s+(.+)$/u', $line, $matches)) {
                $this->flush_article_paragraph($parts, $paragraph_lines);
                $list_items[] = trim($matches[1]);
                continue;
            }

            $this->flush_article_list($parts, $list_items);
            $paragraph_lines[] = $line;
        }

        $this->flush_article_paragraph($parts, $paragraph_lines);
        $this->flush_article_list($parts, $list_items);

        return wp_kses_post(implode("\n\n", $parts));
    }

    private function publish_article_from_job(array $options, array $payload): array {
        $action = sanitize_key((string) ($payload['action'] ?? 'upsert'));
        if ($action === 'delete') {
            $post_id = $this->find_published_article_post_id($payload);
            $path = $this->normalize_overlay_path((string) ($payload['path'] ?? ($payload['sourcePath'] ?? '/')));
            $locale = $this->normalize_locale_tag((string) ($payload['locale'] ?? determine_locale()));

            if ($post_id) {
                $permalink = get_permalink($post_id);
                $path = $this->normalize_overlay_path(wp_parse_url($permalink, PHP_URL_PATH) ?: $path);
                wp_delete_post($post_id, true);
            }
            $this->delete_stored_overlay($options, $path, $locale);
            $this->clear_overlay_cache();

            return [
                'ok' => true,
                'result' => [
                    'action' => 'delete',
                    'postId' => $post_id,
                    'path' => $path,
                    'locale' => $locale,
                ],
                'error' => '',
            ];
        }

        $title = sanitize_text_field((string) ($payload['title'] ?? 'AI Growth Article'));
        $body = $this->format_article_body_html((string) ($payload['content'] ?? ''), $title);
        $description = sanitize_textarea_field((string) ($payload['description'] ?? ''));
        $locale = $this->normalize_locale_tag((string) ($payload['locale'] ?? determine_locale()));
        $category_id = $this->resolve_publish_category_id($payload, $locale);
        $category_taxonomy = sanitize_key((string) ($payload['categoryTaxonomy'] ?? 'category')) ?: 'category';

        if (!$title || !$body) {
            return ['ok' => false, 'result' => [], 'error' => 'Article title or content is empty.'];
        }

        $overlay_id = sanitize_text_field((string) ($payload['overlayId'] ?? ''));
        if ($overlay_id) {
            $post_id = $this->find_published_article_post_id($payload);

            if ($post_id) {
                wp_update_post([
                    'ID' => $post_id,
                    'post_title' => wp_slash($title),
                    'post_content' => wp_slash($body),
                    'post_excerpt' => wp_slash($description),
                ]);
                $this->save_published_article_metadata((int) $post_id, $locale, $payload);
                $this->assign_post_locale((int) $post_id, $locale);
                $permalink = get_permalink($post_id);
                $path = wp_parse_url($permalink, PHP_URL_PATH) ?: '/';
                $this->upsert_stored_overlay($options, $this->article_overlay_payload_for_post($payload, $post_id, $permalink, $path, $locale));
                $this->clear_overlay_cache();

                return [
                    'ok' => true,
                    'result' => [
                        'externalId' => (string) $post_id,
                        'postId' => $post_id,
                        'path' => $path,
                        'url' => $permalink,
                        'status' => get_post_status($post_id) ?: 'published',
                        'categoryId' => $category_id ? (string) $category_id : '',
                        'deduped' => true,
                    ],
                    'error' => '',
                ];
            }
        }

        $post_args = [
            'post_type' => 'post',
            'post_status' => 'publish',
            'post_title' => wp_slash($title),
            'post_content' => wp_slash($body),
            'post_excerpt' => wp_slash($description),
        ];

        if ($category_id && $category_taxonomy === 'category' && term_exists($category_id, 'category')) {
            $post_args['post_category'] = [$category_id];
        }

        $post_id = wp_insert_post($post_args, true);
        if (is_wp_error($post_id)) {
            return ['ok' => false, 'result' => [], 'error' => $post_id->get_error_message()];
        }
        if ($category_id && $category_taxonomy !== 'category' && term_exists($category_id, $category_taxonomy)) {
            wp_set_post_terms($post_id, [$category_id], $category_taxonomy, false);
        }

        $this->save_published_article_metadata((int) $post_id, $locale, $payload);
        $this->assign_post_locale($post_id, $locale);

        $permalink = get_permalink($post_id);
        $path = wp_parse_url($permalink, PHP_URL_PATH) ?: '/';
        $this->upsert_stored_overlay($options, $this->article_overlay_payload_for_post($payload, (int) $post_id, $permalink, $path, $locale));
        $this->clear_overlay_cache();

        return [
            'ok' => true,
            'result' => [
                'externalId' => (string) $post_id,
                'postId' => (int) $post_id,
                'path' => $path,
                'url' => $permalink,
                'status' => 'published',
                'categoryId' => $category_id ? (string) $category_id : '',
            ],
            'error' => '',
        ];
    }

    private function resolve_publish_category_id(array $payload, string $locale): int {
        $category_id = absint($payload['categoryId'] ?? 0);
        $taxonomy = sanitize_key((string) ($payload['categoryTaxonomy'] ?? 'category')) ?: 'category';
        $locale_map = isset($payload['categoryLocaleMap']) && is_array($payload['categoryLocaleMap'])
            ? $payload['categoryLocaleMap']
            : [];

        $candidates = [];
        if (isset($locale_map[$locale]) && is_array($locale_map[$locale])) {
            $candidates[] = absint($locale_map[$locale]['termId'] ?? 0);
        }
        $target_language = strtolower(strtok($locale, '-') ?: $locale);
        foreach ($locale_map as $map_locale => $entry) {
            if (!is_array($entry)) {
                continue;
            }
            $map_language = strtolower(strtok((string) $map_locale, '-') ?: (string) $map_locale);
            if ($map_language === $target_language) {
                $candidates[] = absint($entry['termId'] ?? 0);
            }
        }

        $translated = $this->translate_term_id_for_locale($category_id, $taxonomy, $locale);
        if ($translated > 0) {
            $candidates[] = $translated;
        }
        $candidates[] = $category_id;

        foreach ($candidates as $candidate) {
            if ($candidate > 0 && term_exists($candidate, $taxonomy)) {
                return $candidate;
            }
        }

        return 0;
    }

    private function translate_term_id_for_locale(int $term_id, string $taxonomy, string $locale): int {
        if ($term_id <= 0) {
            return 0;
        }

        $language_code = $this->language_code_for_locale($locale);
        if ($language_code && function_exists('pll_get_term')) {
            $translated = (int) pll_get_term($term_id, $language_code);
            if ($translated > 0) {
                return $translated;
            }
        }

        if ($language_code && has_filter('wpml_object_id')) {
            $translated = (int) apply_filters('wpml_object_id', $term_id, $taxonomy, true, $language_code);
            if ($translated > 0) {
                return $translated;
            }
        }

        return 0;
    }

    private function assign_post_locale(int $post_id, string $locale): void {
        if ($post_id <= 0 || $locale === '') {
            return;
        }

        $language_code = $this->language_code_for_locale($locale) ?: strtolower(strtok($locale, '-') ?: $locale);
        if ($language_code && function_exists('pll_set_post_language')) {
            pll_set_post_language($post_id, $language_code);
        }

        if ($language_code && has_action('wpml_set_element_language_details')) {
            do_action('wpml_set_element_language_details', [
                'element_id' => $post_id,
                'element_type' => 'post_post',
                'trid' => false,
                'language_code' => $language_code,
            ]);
        }
    }

    private function normalize_overlay_path(string $path): string {
        $parsed = wp_parse_url($path, PHP_URL_PATH);
        $clean = is_string($parsed) && $parsed !== '' ? $parsed : $path;
        $clean = '/' . ltrim($clean ?: '/', '/');
        if ($clean !== '/') {
            $clean = rtrim($clean, '/');
        }

        return $clean ?: '/';
    }

    private function sanitize_overlay_list(array $items, callable $sanitize_item): array {
        $clean = [];
        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }
            $value = $sanitize_item($item);
            if ($value !== null) {
                $clean[] = $value;
            }
        }
        return $clean;
    }

    private function sanitize_overlay_map(array $item, array $allowed_keys): array {
        $clean = [];
        foreach ($allowed_keys as $key) {
            if (!array_key_exists($key, $item)) {
                continue;
            }
            $value = (string) $item[$key];
            $clean[$key] = in_array($key, ['url', 'image'], true) ? esc_url_raw($value) : sanitize_text_field($value);
        }
        return array_filter($clean, static function ($value) {
            return $value !== '';
        });
    }

    private function sanitize_overlay_payload(array $overlay): array {
        $path = $this->normalize_overlay_path((string) ($overlay['path'] ?? '/'));
        $locale = $this->normalize_locale_tag((string) ($overlay['locale'] ?? determine_locale()));

        return [
            'id' => sanitize_text_field((string) ($overlay['id'] ?? '')),
            'path' => $path,
            'locale' => $locale,
            'title' => sanitize_text_field((string) ($overlay['title'] ?? '')),
            'description' => sanitize_textarea_field((string) ($overlay['description'] ?? '')),
            'canonical' => esc_url_raw((string) ($overlay['canonical'] ?? '')),
            'robots' => sanitize_text_field((string) ($overlay['robots'] ?? '')),
            'aiSummary' => sanitize_textarea_field((string) ($overlay['aiSummary'] ?? '')),
            'openGraph' => $this->sanitize_overlay_map((array) ($overlay['openGraph'] ?? []), [
                'type',
                'title',
                'description',
                'url',
                'siteName',
                'locale',
                'image',
            ]),
            'twitter' => $this->sanitize_overlay_map((array) ($overlay['twitter'] ?? []), [
                'card',
                'title',
                'description',
                'image',
            ]),
            'faq' => $this->sanitize_overlay_list((array) ($overlay['faq'] ?? []), function (array $item) {
                $question = sanitize_text_field((string) ($item['question'] ?? ''));
                $answer = sanitize_textarea_field((string) ($item['answer'] ?? ''));
                return $question && $answer ? ['question' => $question, 'answer' => $answer] : null;
            }),
            'schemaGraph' => is_array($overlay['schemaGraph'] ?? null) ? $overlay['schemaGraph'] : [],
            'schemaJsonLd' => array_values(array_filter((array) ($overlay['schemaJsonLd'] ?? []), 'is_array')),
            'internalLinks' => $this->sanitize_overlay_list((array) ($overlay['internalLinks'] ?? []), function (array $item) {
                $target = $this->normalize_overlay_path((string) ($item['targetPath'] ?? ''));
                $anchor = sanitize_text_field((string) ($item['anchor'] ?? ''));
                $reason = sanitize_text_field((string) ($item['reason'] ?? ''));
                return $target && $anchor ? ['targetPath' => $target, 'anchor' => $anchor, 'reason' => $reason] : null;
            }),
            'entities' => $this->sanitize_overlay_list((array) ($overlay['entities'] ?? []), function (array $item) {
                $name = sanitize_text_field((string) ($item['name'] ?? ''));
                $type = sanitize_text_field((string) ($item['type'] ?? ''));
                $description = sanitize_textarea_field((string) ($item['description'] ?? ''));
                return $name ? ['name' => $name, 'type' => $type, 'description' => $description] : null;
            }),
            'updatedAt' => sanitize_text_field((string) ($overlay['updatedAt'] ?? current_time('mysql'))),
        ];
    }

    private function get_stored_overlay(array $options, string $path, string $locale): ?array {
        global $wpdb;

        $table = $this->overlay_table_name();
        $scope = $this->content_manifest_scope($options);
        $path = $this->normalize_overlay_path($path);
        $locale = $this->normalize_locale_tag($locale);
        $path_hash = md5($path);
        $payload = $wpdb->get_var($wpdb->prepare(
            "SELECT payload_json FROM {$table} WHERE scope_hash = %s AND path_hash = %s AND locale = %s LIMIT 1",
            $scope,
            $path_hash,
            $locale
        ));

        if (!is_string($payload) || $payload === '') {
            return null;
        }

        $decoded = json_decode($payload, true);
        return is_array($decoded) ? $decoded : null;
    }

    private function count_stored_overlays(array $options): int {
        global $wpdb;

        $table = $this->overlay_table_name();
        $scope = $this->content_manifest_scope($options);
        return (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE scope_hash = %s", $scope));
    }

    private function upsert_stored_overlay(array $options, array $overlay): void {
        global $wpdb;

        $table = $this->overlay_table_name();
        $scope = $this->content_manifest_scope($options);
        $path = $this->normalize_overlay_path((string) ($overlay['path'] ?? '/'));
        $locale = $this->normalize_locale_tag((string) ($overlay['locale'] ?? determine_locale()));
        $now = current_time('mysql');
        $wpdb->query($wpdb->prepare(
            "INSERT INTO {$table}
                (scope_hash, path_hash, path, locale, overlay_id, title, description, ai_summary, payload_json, updated_at, created_at)
             VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
             ON DUPLICATE KEY UPDATE
                path = VALUES(path),
                overlay_id = VALUES(overlay_id),
                title = VALUES(title),
                description = VALUES(description),
                ai_summary = VALUES(ai_summary),
                payload_json = VALUES(payload_json),
                updated_at = VALUES(updated_at)",
            $scope,
            md5($path),
            $path,
            $locale,
            (string) ($overlay['id'] ?? ''),
            (string) ($overlay['title'] ?? ''),
            (string) ($overlay['description'] ?? ''),
            (string) ($overlay['aiSummary'] ?? ''),
            (string) wp_json_encode($overlay, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
            $now,
            $now
        ));
    }

    private function delete_stored_overlay(array $options, string $path, string $locale): void {
        global $wpdb;

        $table = $this->overlay_table_name();
        $wpdb->query($wpdb->prepare(
            "DELETE FROM {$table} WHERE scope_hash = %s AND path_hash = %s AND locale = %s",
            $this->content_manifest_scope($options),
            md5($this->normalize_overlay_path($path)),
            $this->normalize_locale_tag($locale)
        ));
    }

    private function apply_seo_overlay_from_job(array $options, array $payload): array {
        $action = sanitize_key((string) ($payload['action'] ?? 'upsert'));
        $path = $this->normalize_overlay_path((string) ($payload['path'] ?? ($payload['overlay']['path'] ?? '/')));
        $locale = $this->normalize_locale_tag((string) ($payload['locale'] ?? ($payload['overlay']['locale'] ?? determine_locale())));

        if ($action === 'delete') {
            $this->delete_stored_overlay($options, $path, $locale);
            $this->clear_overlay_cache();

            return [
                'ok' => true,
                'result' => [
                    'action' => 'delete',
                    'path' => $path,
                    'locale' => $locale,
                    'storedCount' => $this->count_stored_overlays($options),
                ],
                'error' => '',
            ];
        }

        $overlay = isset($payload['overlay']) && is_array($payload['overlay']) ? $payload['overlay'] : [];
        if (!$overlay) {
            return ['ok' => false, 'result' => [], 'error' => 'SEO/GEO overlay payload is empty.'];
        }

        $clean = $this->sanitize_overlay_payload(array_merge($overlay, [
            'path' => $path,
            'locale' => $locale,
        ]));
        $this->upsert_stored_overlay($options, $clean);
        $this->clear_overlay_cache();

        return [
            'ok' => true,
            'result' => [
                'action' => 'upsert',
                'path' => $clean['path'],
                'locale' => $clean['locale'],
                'storedCount' => $this->count_stored_overlays($options),
            ],
            'error' => '',
        ];
    }

    private function collect_content_items(): array {
        $post_types = ['page', 'post'];
        if (post_type_exists('product')) {
            $post_types[] = 'product';
        }

        $posts = $this->collect_posts_for_sync($post_types);
        $posts = array_slice($posts, 0, self::SYNC_MAX_ITEMS);

        $post_items = array_values(array_filter(array_map(function ($post) {
            $permalink = get_permalink($post);
            $path = wp_parse_url($permalink, PHP_URL_PATH) ?: '/';
            $full_content = trim(wp_strip_all_tags(strip_shortcodes($post->post_content)));
            $content = $this->truncate_sync_text($full_content, self::SYNC_CONTENT_MAX_CHARS);
            $locale = $this->detect_post_locale((int) $post->ID);
            $terms = [];
            if ($post->post_type === 'post') {
                $terms = get_the_category($post->ID);
            } elseif ($post->post_type === 'product' && taxonomy_exists('product_cat')) {
                $product_terms = get_the_terms($post->ID, 'product_cat');
                $terms = is_array($product_terms) ? $product_terms : [];
            }
            $categories = array_values(array_map(function ($term) {
                $term_link = get_term_link($term);
                $translation_payload = $this->term_translation_payload((int) $term->term_id, (string) $term->taxonomy);
                return array_merge([
                    'id' => (string) $term->term_id,
                    'name' => $term->name,
                    'slug' => $term->slug,
                    'url' => is_wp_error($term_link) ? '' : $term_link,
                    'taxonomy' => (string) $term->taxonomy,
                    'locale' => $this->detect_term_locale((int) $term->term_id),
                ], $translation_payload);
            }, $terms));

            if (!$full_content) {
                return null;
            }

            $raw_html = (string) $post->post_content;
            $hrefs = [];
            preg_match_all('/href=["\']([^"\']+)["\']/i', $raw_html, $href_matches);
            if (!empty($href_matches[1]) && is_array($href_matches[1])) {
                $hrefs = array_values(array_filter(array_map('esc_url_raw', $href_matches[1])));
            }
            $home_host = wp_parse_url(home_url(), PHP_URL_HOST);
            $internal_links = 0;
            $external_links = 0;
            foreach ($hrefs as $href) {
                $host = wp_parse_url($href, PHP_URL_HOST);
                if (!$host || $host === $home_host) {
                    $internal_links++;
                } else {
                    $external_links++;
                }
            }
            $seo_description = get_post_meta($post->ID, '_yoast_wpseo_metadesc', true)
                ?: get_post_meta($post->ID, '_aioseo_description', true)
                ?: get_post_meta($post->ID, 'rank_math_description', true);
            $seo_canonical = get_post_meta($post->ID, '_yoast_wpseo_canonical', true)
                ?: get_post_meta($post->ID, '_aioseo_canonical_url', true)
                ?: get_post_meta($post->ID, 'rank_math_canonical_url', true);
            $robots = get_post_meta($post->ID, '_yoast_wpseo_meta-robots-noindex', true);
            $char_count = function_exists('mb_strlen') ? mb_strlen($full_content) : strlen($full_content);

            return [
                'externalId' => (string) $post->ID,
                'type' => $post->post_type === 'page' ? 'page' : ($post->post_type === 'product' ? 'product' : 'post'),
                'locale' => $locale,
                'path' => $path,
                'url' => $permalink,
                'title' => get_the_title($post),
                'excerpt' => has_excerpt($post) ? get_the_excerpt($post) : wp_trim_words($full_content, 40, ''),
                'content' => $content,
                'status' => 'published',
                'raw' => [
                    'postType' => $post->post_type,
                    'slug' => $post->post_name,
                    'locale' => $locale,
                    'modifiedGmt' => $post->post_modified_gmt,
                    'canonical' => $seo_canonical ?: $permalink,
                    'metaDescription' => $seo_description ? wp_strip_all_tags((string) $seo_description) : '',
                    'robots' => $robots ? 'noindex' : 'index',
                    'wordCount' => max(str_word_count($full_content), (int) ceil($char_count / 3)),
                    'internalLinkCount' => $internal_links,
                    'externalLinkCount' => $external_links,
                    'headingCount' => preg_match_all('/<h[1-3][^>]*>/i', $raw_html),
                    'categories' => $categories,
                ],
            ];
        }, $posts)));

        $homepage_items = $this->collect_homepage_items();
        $category_items = $this->collect_taxonomy_items('category', 'category');
        $product_category_items = taxonomy_exists('product_cat')
            ? $this->collect_taxonomy_items('product_cat', 'product_category')
            : [];

        return array_slice(array_merge($homepage_items, $post_items, $category_items, $product_category_items), 0, self::SYNC_MAX_ITEMS);
    }

    private function collect_homepage_items(): array {
        $languages = $this->detect_supported_language_entries();
        $items = [];

        foreach ($languages as $language) {
            $locale = $language['locale'];
            $home_url = $this->localized_home_url($locale, $language);
            $path = wp_parse_url($home_url, PHP_URL_PATH) ?: '/';
            $front_page = $this->localized_front_page($locale);
            $title = $front_page ? get_the_title($front_page) : get_bloginfo('name');
            $full_content = $front_page
                ? trim(wp_strip_all_tags(strip_shortcodes($front_page->post_content)))
                : trim(get_bloginfo('name') . "\n" . get_bloginfo('description'));

            if ($full_content === '') {
                $full_content = $title ?: home_url('/');
            }
            $content = $this->truncate_sync_text($full_content, self::SYNC_CONTENT_MAX_CHARS);

            $raw_html = $front_page ? (string) $front_page->post_content : '';
            $char_count = function_exists('mb_strlen') ? mb_strlen($full_content) : strlen($full_content);
            $modified_gmt = $front_page ? $front_page->post_modified_gmt : gmdate('Y-m-d H:i:s');
            $seo_description = $front_page
                ? (get_post_meta($front_page->ID, '_yoast_wpseo_metadesc', true)
                    ?: get_post_meta($front_page->ID, '_aioseo_description', true)
                    ?: get_post_meta($front_page->ID, 'rank_math_description', true))
                : get_bloginfo('description');
            $seo_canonical = $front_page
                ? (get_post_meta($front_page->ID, '_yoast_wpseo_canonical', true)
                    ?: get_post_meta($front_page->ID, '_aioseo_canonical_url', true)
                    ?: get_post_meta($front_page->ID, 'rank_math_canonical_url', true))
                : '';
            $robots = $front_page ? get_post_meta($front_page->ID, '_yoast_wpseo_meta-robots-noindex', true) : '';

            $items[] = [
                'externalId' => 'homepage:' . $locale,
                'type' => 'page',
                'locale' => $locale,
                'path' => $path,
                'url' => $home_url,
                'title' => $title ?: get_bloginfo('name'),
                'excerpt' => $seo_description ? wp_strip_all_tags((string) $seo_description) : wp_trim_words($full_content, 40, ''),
                'content' => $content,
                'status' => 'published',
                'raw' => [
                    'postType' => 'homepage',
                    'sourcePostId' => $front_page ? (string) $front_page->ID : '',
                    'slug' => 'home',
                    'locale' => $locale,
                    'modifiedGmt' => $modified_gmt,
                    'canonical' => $seo_canonical ?: $home_url,
                    'metaDescription' => $seo_description ? wp_strip_all_tags((string) $seo_description) : '',
                    'robots' => $robots ? 'noindex' : 'index',
                    'wordCount' => max(str_word_count($full_content), (int) ceil($char_count / 3)),
                    'internalLinkCount' => 0,
                    'externalLinkCount' => 0,
                    'headingCount' => $raw_html ? preg_match_all('/<h[1-3][^>]*>/i', $raw_html) : 0,
                    'categories' => [],
                    'isHomepage' => true,
                ],
            ];
        }

        return $items;
    }

    private function collect_posts_for_sync(array $post_types): array {
        $queries = [['lang' => null]];
        $languages = $this->detect_supported_language_entries();
        if (function_exists('pll_languages_list') || has_filter('wpml_active_languages')) {
            $queries = array_values(array_filter(array_map(function ($language) {
                return !empty($language['code']) ? ['lang' => (string) $language['code']] : null;
            }, $languages)));
            if (!$queries) {
                $queries = [['lang' => null]];
            }
        }

        $posts = [];
        $seen = [];
        foreach ($queries as $query) {
            for ($page = 1; count($posts) < self::SYNC_MAX_ITEMS; $page++) {
                $args = [
                    'post_type' => $post_types,
                    'post_status' => 'publish',
                    'posts_per_page' => self::SYNC_PAGE_SIZE,
                    'paged' => $page,
                    'orderby' => 'modified',
                    'order' => 'DESC',
                    'no_found_rows' => true,
                ];
                if (!empty($query['lang'])) {
                    $args['lang'] = sanitize_key((string) $query['lang']);
                }

                $batch = get_posts($args);

                if (!$batch) {
                    break;
                }

                foreach ($batch as $post) {
                    if (!$post instanceof WP_Post || isset($seen[$post->ID])) {
                        continue;
                    }
                    $seen[$post->ID] = true;
                    $posts[] = $post;
                }

                if (count($batch) < self::SYNC_PAGE_SIZE) {
                    break;
                }
            }
        }

        return $posts;
    }

    private function localized_home_url(string $locale, array $language = []): string {
        if (!empty($language['homeUrl'])) {
            return esc_url_raw((string) $language['homeUrl']);
        }

        $slug = $this->language_code_for_locale($locale);
        if ($slug && function_exists('pll_home_url')) {
            $url = pll_home_url($slug);
            if (is_string($url) && $url !== '') {
                return $url;
            }
        }

        if ($slug && has_filter('wpml_permalink')) {
            $url = apply_filters('wpml_permalink', home_url('/'), $slug);
            if (is_string($url) && $url !== '') {
                return $url;
            }
        }

        return home_url('/');
    }

    private function localized_front_page(string $locale): ?WP_Post {
        $front_page_id = (int) get_option('page_on_front');
        if (get_option('show_on_front') !== 'page' || $front_page_id <= 0) {
            return null;
        }

        $slug = $this->language_code_for_locale($locale);
        if ($slug && function_exists('pll_get_post')) {
            $translated_id = (int) pll_get_post($front_page_id, $slug);
            if ($translated_id > 0) {
                $front_page_id = $translated_id;
            }
        }

        if ($slug && has_filter('wpml_object_id')) {
            $translated_id = (int) apply_filters('wpml_object_id', $front_page_id, 'page', false, $slug);
            if ($translated_id > 0) {
                $front_page_id = $translated_id;
            }
        }

        $post = get_post($front_page_id);
        return $post instanceof WP_Post && $post->post_status === 'publish' ? $post : null;
    }

    private function collect_taxonomy_items(string $taxonomy, string $prefix): array {
        $base_args = [
            'taxonomy' => $taxonomy,
            'hide_empty' => false,
            'number' => min(self::SYNC_MAX_ITEMS, 300),
            'orderby' => 'name',
            'order' => 'ASC',
        ];

        $term_batches = [];
        $languages = $this->detect_supported_language_entries();
        if (function_exists('pll_languages_list') || has_filter('wpml_active_languages')) {
            foreach ($languages as $language) {
                if (empty($language['code'])) {
                    continue;
                }
                $term_batches[] = get_terms(array_merge($base_args, ['lang' => sanitize_key((string) $language['code'])]));
            }
        }
        if (!$term_batches) {
            $term_batches[] = get_terms($base_args);
        }

        $terms = [];
        $seen = [];
        foreach ($term_batches as $batch) {
            if (is_wp_error($batch) || !is_array($batch)) {
                continue;
            }
            foreach ($batch as $term) {
                if (!isset($term->term_id) || isset($seen[$term->term_id])) {
                    continue;
                }
                $seen[$term->term_id] = true;
                $terms[] = $term;
            }
        }

        return array_values(array_filter(array_map(function ($category) use ($taxonomy, $prefix) {
            $link = get_term_link($category, $taxonomy);
            if (is_wp_error($link)) {
                $link = '';
            }
            $path = wp_parse_url($link, PHP_URL_PATH) ?: '/';
            $description = trim(wp_strip_all_tags(term_description($category->term_id, $taxonomy)));
            $locale = $this->detect_term_locale((int) $category->term_id);
            $translation_payload = $this->term_translation_payload((int) $category->term_id, $taxonomy);

            return [
                'externalId' => $prefix . ':' . (string) $category->term_id,
                'type' => 'category',
                'locale' => $locale,
                'path' => $path,
                'url' => $link,
                'title' => $category->name,
                'excerpt' => $description ?: $category->name,
                'content' => $description ?: $category->name,
                'status' => 'published',
                'raw' => [
                    'taxonomy' => $taxonomy,
                    'termId' => (string) $category->term_id,
                    'slug' => $category->slug,
                    'locale' => $locale,
                    'count' => (int) $category->count,
                ] + $translation_payload,
            ];
        }, $terms)));
    }

    private function term_translation_payload(int $term_id, string $taxonomy): array {
        $translations = [];
        $translation_ids = [$term_id];

        if (function_exists('pll_get_term_translations')) {
            $pll_translations = pll_get_term_translations($term_id);
            if (is_array($pll_translations)) {
                foreach ($pll_translations as $language_code => $translated_id) {
                    $translated_id = (int) $translated_id;
                    if ($translated_id <= 0) {
                        continue;
                    }
                    $term = get_term($translated_id, $taxonomy);
                    if (!$term || is_wp_error($term)) {
                        continue;
                    }
                    $locale = $this->detect_term_locale($translated_id);
                    $link = get_term_link($term, $taxonomy);
                    $translations[$locale] = [
                        'termId' => (string) $translated_id,
                        'name' => $term->name,
                        'slug' => $term->slug,
                        'taxonomy' => $taxonomy,
                        'path' => is_wp_error($link) ? '' : (wp_parse_url($link, PHP_URL_PATH) ?: ''),
                        'url' => is_wp_error($link) ? '' : $link,
                        'languageCode' => (string) $language_code,
                    ];
                    $translation_ids[] = $translated_id;
                }
            }
        }

        $wpml_trid = '';
        if (!$translations && has_filter('wpml_element_language_details')) {
            $term = get_term($term_id, $taxonomy);
            if ($term && !is_wp_error($term) && isset($term->term_taxonomy_id)) {
                $details = apply_filters('wpml_element_language_details', null, [
                    'element_id' => (int) $term->term_taxonomy_id,
                    'element_type' => 'tax_' . $taxonomy,
                ]);
                if (is_object($details) && isset($details->trid)) {
                    $wpml_trid = (string) $details->trid;
                } elseif (is_array($details) && isset($details['trid'])) {
                    $wpml_trid = (string) $details['trid'];
                }
            }
        }

        sort($translation_ids);
        $group_id = $wpml_trid !== ''
            ? $taxonomy . ':wpml:' . $wpml_trid
            : $taxonomy . ':terms:' . implode('-', array_unique($translation_ids));

        return [
            'categoryGroupId' => $group_id,
            'translationGroup' => $group_id,
            'translations' => $translations,
        ];
    }

    private function detect_supported_locales(): array {
        return array_values(array_map(function ($language) {
            return $language['locale'];
        }, $this->detect_supported_language_entries()));
    }

    private function detect_supported_language_entries(): array {
        $entries = [];
        $has_structural_multilingual_provider = false;
        $fallback_locale = $this->normalize_locale_tag(get_locale() ?: determine_locale());

        if (function_exists('pll_languages_list')) {
            $languages = pll_languages_list(['fields' => []]);
            if (is_array($languages)) {
                foreach ($languages as $language) {
                    $slug = '';
                    $locale = '';
                    $home_url = '';
                    if (is_object($language)) {
                        $slug = isset($language->slug) ? (string) $language->slug : '';
                        $locale = isset($language->locale) ? (string) $language->locale : $slug;
                        $home_url = isset($language->home_url) ? (string) $language->home_url : '';
                    } elseif (is_array($language)) {
                        $slug = isset($language['slug']) ? (string) $language['slug'] : '';
                        $locale = isset($language['locale']) ? (string) $language['locale'] : $slug;
                        $home_url = isset($language['home_url']) ? (string) $language['home_url'] : '';
                    } elseif (is_string($language)) {
                        $slug = $language;
                        $locale = $language;
                    }

                    if ($home_url === '' && $slug && function_exists('pll_home_url')) {
                        $home_url = (string) pll_home_url($slug);
                    }
                    $this->add_language_entry($entries, $locale ?: $slug, $slug, $home_url, 'polylang');
                    $has_structural_multilingual_provider = true;
                }
            }
        }

        if (has_filter('wpml_active_languages')) {
            $languages = apply_filters('wpml_active_languages', null, ['skip_missing' => 0]);
            if (is_array($languages)) {
                foreach ($languages as $language) {
                    if (!is_array($language)) {
                        continue;
                    }
                    $code = isset($language['language_code']) ? (string) $language['language_code'] : '';
                    $locale = isset($language['default_locale']) ? (string) $language['default_locale'] : $code;
                    $home_url = isset($language['url']) ? (string) $language['url'] : '';
                    $this->add_language_entry($entries, $locale ?: $code, $code, $home_url, 'wpml');
                    $has_structural_multilingual_provider = true;
                }
            }
        }

        if (!$has_structural_multilingual_provider) {
            $this->add_language_entry($entries, $fallback_locale, '', home_url('/'), 'wordpress');

            $trp_settings = get_option('trp_settings', []);
            if (is_array($trp_settings)) {
                $translation_languages = isset($trp_settings['translation-languages']) && is_array($trp_settings['translation-languages'])
                    ? $trp_settings['translation-languages']
                    : [];
                foreach ($translation_languages as $locale) {
                    $this->add_language_entry($entries, (string) $locale, '', '', 'translatepress');
                }
            }

            $weglot_options = get_option('weglot');
            if (is_array($weglot_options)) {
                foreach (['original_language', 'destination_language'] as $key) {
                    $value = $weglot_options[$key] ?? null;
                    $values = is_array($value) ? $value : [$value];
                    foreach ($values as $locale) {
                        if (is_string($locale) && $locale !== '') {
                            $this->add_language_entry($entries, $locale, $locale, '', 'weglot');
                        }
                    }
                }
            }
        }

        if (!$entries) {
            $this->add_language_entry($entries, $fallback_locale, '', home_url('/'), 'wordpress');
        }

        return array_values($entries);
    }

    private function add_language_entry(array &$entries, string $locale, string $code = '', string $home_url = '', string $source = ''): void {
        $normalized = $this->normalize_locale_tag($locale ?: $code);
        if (!$normalized) {
            return;
        }
        $key = strtolower($normalized);
        $entries[$key] = [
            'locale' => $normalized,
            'code' => sanitize_key($code ?: strtok(strtolower(str_replace('_', '-', $normalized)), '-')),
            'homeUrl' => $home_url ? esc_url_raw($home_url) : '',
            'source' => sanitize_key($source),
        ];
    }

    private function language_code_for_locale(string $locale): string {
        $target = $this->normalize_locale_tag($locale);
        foreach ($this->detect_supported_language_entries() as $language) {
            if ($language['locale'] === $target && !empty($language['code'])) {
                return sanitize_key((string) $language['code']);
            }
        }

        $language = strtolower(str_replace('_', '-', $target));
        return sanitize_key(strtok($language, '-'));
    }

    private function normalize_locale_tag(string $locale): string {
        $value = trim(str_replace('_', '-', $locale));
        if ($value === '') {
            return 'zh-CN';
        }

        $lower = strtolower($value);
        $aliases = [
            'zh' => 'zh-CN',
            'zh-cn' => 'zh-CN',
            'zh-hans' => 'zh-CN',
            'zh-hans-cn' => 'zh-CN',
            'zh-tw' => 'zh-TW',
            'zh-hant' => 'zh-TW',
            'zh-hant-tw' => 'zh-TW',
            'en' => 'en-US',
            'en-us' => 'en-US',
            'en-gb' => 'en-US',
            'de' => 'de-DE',
            'fr' => 'fr-FR',
            'es' => 'es-ES',
            'pt' => 'pt-BR',
            'pt-br' => 'pt-BR',
            'ja' => 'ja-JP',
            'ko' => 'ko-KR',
            'ru' => 'ru-RU',
            'ar' => 'ar',
            'it' => 'it-IT',
            'nl' => 'nl-NL',
            'tr' => 'tr-TR',
            'pl' => 'pl-PL',
            'vi' => 'vi-VN',
            'th' => 'th-TH',
            'id' => 'id-ID',
            'ms' => 'ms-MY',
            'hi' => 'hi-IN',
            'cs' => 'cs-CZ',
            'da' => 'da-DK',
            'he' => 'he-IL',
            'hu' => 'hu-HU',
            'fi' => 'fi-FI',
            'uk' => 'uk-UA',
            'ro' => 'ro-RO',
            'nb' => 'nb-NO',
            'sv' => 'sv-SE',
            'el' => 'el-GR',
        ];

        if (isset($aliases[$lower])) {
            return $aliases[$lower];
        }

        if (preg_match('/^([a-z]{2,3})-([a-z]{2}|\d{3})$/i', $value, $matches)) {
            return strtolower($matches[1]) . '-' . strtoupper($matches[2]);
        }

        return sanitize_text_field($value);
    }

    private function detect_post_locale(int $post_id): string {
        if (function_exists('pll_get_post_language')) {
            $locale = pll_get_post_language($post_id, 'locale');
            if (!$locale) {
                $locale = pll_get_post_language($post_id, 'slug');
            }
            if (is_string($locale) && $locale !== '') {
                return $this->normalize_locale_tag($locale);
            }
        }

        if (has_filter('wpml_post_language_details')) {
            $details = apply_filters('wpml_post_language_details', null, $post_id);
            if (is_array($details)) {
                $locale = isset($details['locale']) ? (string) $details['locale'] : '';
                $code = isset($details['language_code']) ? (string) $details['language_code'] : '';
                if ($locale || $code) {
                    return $this->normalize_locale_tag($locale ?: $code);
                }
            }
        }

        if (taxonomy_exists('language')) {
            $terms = wp_get_object_terms($post_id, 'language', ['fields' => 'slugs']);
            if (!is_wp_error($terms) && !empty($terms[0])) {
                return $this->normalize_locale_tag((string) $terms[0]);
            }
        }

        return $this->normalize_locale_tag(determine_locale());
    }

    private function detect_term_locale(int $term_id): string {
        if (function_exists('pll_get_term_language')) {
            $locale = pll_get_term_language($term_id, 'locale');
            if (!$locale) {
                $locale = pll_get_term_language($term_id, 'slug');
            }
            if (is_string($locale) && $locale !== '') {
                return $this->normalize_locale_tag($locale);
            }
        }

        if (has_filter('wpml_element_language_details')) {
            $term = get_term($term_id);
            if ($term instanceof WP_Term) {
                $details = apply_filters('wpml_element_language_details', null, [
                    'element_id' => (int) $term->term_taxonomy_id,
                    'element_type' => 'tax_' . $term->taxonomy,
                ]);
                if (is_object($details) && !empty($details->language_code)) {
                    return $this->normalize_locale_tag((string) $details->language_code);
                }
                if (is_array($details) && !empty($details['language_code'])) {
                    return $this->normalize_locale_tag((string) $details['language_code']);
                }
            }
        }

        foreach (['term_language', 'language'] as $taxonomy) {
            if (!taxonomy_exists($taxonomy)) {
                continue;
            }
            $terms = wp_get_object_terms($term_id, $taxonomy, ['fields' => 'slugs']);
            if (is_wp_error($terms) || empty($terms[0])) {
                continue;
            }
            $slug = preg_replace('/^pll_/', '', (string) $terms[0]);
            return $this->normalize_locale_tag($slug ?: (string) $terms[0]);
        }

        return $this->normalize_locale_tag(determine_locale());
    }

    private function detect_current_request_locale(): string {
        if (function_exists('pll_current_language')) {
            $locale = pll_current_language('locale');
            if (!$locale) {
                $locale = pll_current_language('slug');
            }
            if (is_string($locale) && $locale !== '') {
                return $this->normalize_locale_tag($locale);
            }
        }

        if (has_filter('wpml_current_language')) {
            $code = apply_filters('wpml_current_language', null);
            if (is_string($code) && $code !== '') {
                foreach ($this->detect_supported_language_entries() as $language) {
                    if (!empty($language['code']) && $language['code'] === $code) {
                        return $language['locale'];
                    }
                }
                return $this->normalize_locale_tag($code);
            }
        }

        global $TRP_LANGUAGE;
        if (is_string($TRP_LANGUAGE ?? null) && $TRP_LANGUAGE !== '') {
            return $this->normalize_locale_tag($TRP_LANGUAGE);
        }

        if (function_exists('weglot_get_current_language')) {
            $code = weglot_get_current_language();
            if (is_string($code) && $code !== '') {
                return $this->normalize_locale_tag($code);
            }
        }

        if (is_singular()) {
            $post_id = (int) get_queried_object_id();
            if ($post_id > 0) {
                return $this->detect_post_locale($post_id);
            }
        }

        $queried = get_queried_object();
        if ($queried instanceof WP_Term) {
            return $this->detect_term_locale((int) $queried->term_id);
        }

        return $this->normalize_locale_tag(determine_locale());
    }

    private function get_current_overlay(array $options): ?array {
        if (empty($options['site_token'])) {
            return null;
        }

        $path = $this->normalize_overlay_path(wp_parse_url(home_url(add_query_arg([])), PHP_URL_PATH) ?: '/');
        $locale = $this->detect_current_request_locale();
        $overlay = $this->get_stored_overlay($options, $path, $locale);

        if ($overlay) {
            return $overlay;
        }

        return null;
    }

    private function clear_overlay_cache(): void {
        global $wpdb;

        $like = $wpdb->esc_like('_transient_' . self::TRANSIENT_PREFIX) . '%';
        $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $like));
    }

    private function check_plugin_update(array &$options): string {
        $manifest = $this->get_update_manifest($options, true);
        $options['last_update_checked_at'] = current_time('mysql');

        if (!$manifest || empty($manifest['version'])) {
            $options['last_error'] = 'Unable to retrieve plugin update information.';
            return 'Unable to retrieve plugin update information.';
        }

        $options['last_update_version'] = sanitize_text_field((string) $manifest['version']);
        $options['last_error'] = '';

        if (version_compare((string) $manifest['version'], self::VERSION, '>')) {
            return 'New plugin version available: ' . sanitize_text_field((string) $manifest['version']) . '. Please update from the WordPress Plugins page.';
        }

        return 'Plugin is up to date. Current version: ' . self::VERSION . '.';
    }

    private function get_update_manifest(array $options, bool $force = false): ?array {
        $api_base = $this->normalize_api_base((string) ($options['api_base'] ?? ''));
        if (!$api_base) {
            return null;
        }

        $cache_key = self::UPDATE_TRANSIENT_PREFIX . md5($api_base);
        if ($force) {
            delete_site_transient($cache_key);
        }

        $cached = get_site_transient($cache_key);
        if (is_array($cached)) {
            return $cached;
        }

        $endpoint = add_query_arg([
            'platform' => 'wordpress',
            'version' => self::VERSION,
            'site' => rawurlencode(home_url()),
        ], $api_base . '/api/connector/v1/plugin/manifest');

        $response = wp_remote_get($endpoint, [
            'timeout' => self::JOB_TIMEOUT,
            'headers' => !empty($options['site_token']) ? $this->auth_headers($options) : [],
        ]);
        $body = $this->decode_response($response);
        $manifest = !empty($body['data']['plugin']) && is_array($body['data']['plugin'])
            ? $body['data']['plugin']
            : (!empty($body['plugin']) && is_array($body['plugin']) ? $body['plugin'] : $body);

        $package_url = $this->manifest_package_url($manifest);
        if (empty($manifest['version']) || empty($package_url)) {
            return null;
        }

        $manifest['packageUrl'] = $package_url;
        set_site_transient($cache_key, $manifest, 6 * HOUR_IN_SECONDS);
        return $manifest;
    }

    private function build_update_payload(?array $manifest) {
        $package_url = $this->manifest_package_url($manifest);
        if (!$manifest || empty($manifest['version']) || empty($package_url)) {
            return null;
        }

        if (version_compare((string) $manifest['version'], self::VERSION, '<=')) {
            return null;
        }

        return (object) [
            'id' => $manifest['homepage'] ?? 'https://www.tui161.com',
            'slug' => self::PLUGIN_SLUG,
            'plugin' => plugin_basename(__FILE__),
            'new_version' => (string) $manifest['version'],
            'url' => $manifest['homepage'] ?? 'https://www.tui161.com',
            'package' => $package_url,
            'tested' => $manifest['tested'] ?? '',
            'requires' => $manifest['requires'] ?? '6.0',
            'requires_php' => $manifest['requiresPhp'] ?? '7.4',
        ];
    }

    private function manifest_package_url(?array $manifest): string {
        if (!$manifest) {
            return '';
        }

        $url = $manifest['package'] ?? $manifest['packageUrl'] ?? $manifest['downloadUrl'] ?? '';
        return esc_url_raw((string) $url);
    }

    public function allow_configured_update_package_host($external, $host, $url = '') {
        if ($external || empty($host) || empty($url)) {
            return $external;
        }

        return $this->is_configured_update_package_url((string) $host, (string) $url) ? true : $external;
    }

    public function allow_configured_update_package_port($ports, $host, $url = '') {
        if (empty($host) || empty($url) || !$this->is_configured_update_package_url((string) $host, (string) $url)) {
            return $ports;
        }

        $url_port = wp_parse_url((string) $url, PHP_URL_PORT);
        if (!$url_port) {
            return $ports;
        }

        $allowed = is_array($ports) ? array_map('intval', $ports) : [80, 443, 8080];
        $allowed[] = (int) $url_port;

        return array_values(array_unique($allowed));
    }

    private function is_configured_update_package_url(string $host, string $url): bool {
        $options = $this->get_options();
        $api_base = $this->normalize_api_base((string) ($options['api_base'] ?? ''));
        if (!$api_base) {
            return false;
        }

        $api_host = wp_parse_url($api_base, PHP_URL_HOST);
        $api_port = wp_parse_url($api_base, PHP_URL_PORT);
        $url_host = wp_parse_url((string) $url, PHP_URL_HOST);
        $url_port = wp_parse_url((string) $url, PHP_URL_PORT);
        $url_path = wp_parse_url((string) $url, PHP_URL_PATH) ?: '';

        if (!$api_host || !$url_host || strcasecmp((string) $host, (string) $api_host) !== 0 || strcasecmp((string) $url_host, (string) $api_host) !== 0) {
            return false;
        }

        if ((int) ($api_port ?: 0) !== (int) ($url_port ?: 0)) {
            return false;
        }

        if ($url_path !== '/downloads/guangergaozhi-ai-growth.zip') {
            return false;
        }

        return true;
    }

    private function is_cloud_temporarily_unavailable(array $options): bool {
        $until = isset($options['cloud_unavailable_until']) ? (int) $options['cloud_unavailable_until'] : 0;
        return $until > time();
    }

    private function remember_cloud_failure(array &$options, string $message, int $cooldown = self::FAILURE_COOLDOWN_SECONDS): void {
        $until = time() + max(30, $cooldown);
        $current_until = isset($options['cloud_unavailable_until']) ? (int) $options['cloud_unavailable_until'] : 0;
        $options['cloud_failure_count'] = (string) (max(0, (int) ($options['cloud_failure_count'] ?? 0)) + 1);
        $options['cloud_unavailable_until'] = (string) max($until, $current_until);
        $options['last_error'] = $message ?: 'Cloud service is temporarily unavailable.';

        if ($until >= $current_until) {
            update_option(self::OPTION_KEY, $options, false);
        }
    }

    private function remember_cloud_success(array &$options): void {
        if (!empty($options['cloud_unavailable_until']) || !empty($options['cloud_failure_count']) || !empty($options['last_error'])) {
            $options['cloud_unavailable_until'] = '';
            $options['cloud_failure_count'] = '';
            $options['last_error'] = '';
            update_option(self::OPTION_KEY, $options, false);
        }
    }

    private function queue_customer_events(array $events): int {
        if (empty($events)) {
            return 0;
        }

        $queue = get_option(self::EVENT_QUEUE_OPTION, []);
        if (!is_array($queue)) {
            $queue = [];
        }

        $accepted = 0;
        foreach (array_slice($events, 0, 50) as $event) {
            if (!is_array($event)) {
                continue;
            }

            $event_data = isset($event['eventData']) && is_array($event['eventData']) ? $event['eventData'] : [];
            $queue[] = [
                'visitorId' => sanitize_text_field((string) ($event['visitorId'] ?? '')),
                'customerId' => sanitize_text_field((string) ($event['customerId'] ?? '')),
                'eventType' => sanitize_key((string) ($event['eventType'] ?? 'custom')) ?: 'custom',
                'channel' => sanitize_key((string) ($event['channel'] ?? 'website')) ?: 'website',
                'path' => sanitize_text_field((string) ($event['path'] ?? '')),
                'url' => esc_url_raw((string) ($event['url'] ?? '')),
                'title' => sanitize_text_field((string) ($event['title'] ?? '')),
                'referrer' => esc_url_raw((string) ($event['referrer'] ?? '')),
                'eventData' => $this->sanitize_event_data($event_data),
                'createdAt' => gmdate('c'),
            ];
            $accepted++;
        }

        if (count($queue) > self::EVENT_QUEUE_MAX_ITEMS) {
            $queue = array_slice($queue, -self::EVENT_QUEUE_MAX_ITEMS);
        }

        update_option(self::EVENT_QUEUE_OPTION, $queue, false);
        return $accepted;
    }

    private function sanitize_event_data(array $data): array {
        $clean = [];
        foreach (array_slice($data, 0, 20, true) as $key => $value) {
            $clean_key = substr((string) preg_replace('/[^A-Za-z0-9_-]/', '', (string) $key), 0, 60);
            if (!$clean_key) {
                continue;
            }

            if (is_scalar($value) || $value === null) {
                $clean[$clean_key] = sanitize_text_field((string) $value);
            }
        }

        return $clean;
    }

    private function sanitize_chat_config_cache(array $body): array {
        $config = isset($body['config']) && is_array($body['config']) ? $body['config'] : $body;
        $supported_locales = isset($body['supportedLocales']) && is_array($body['supportedLocales'])
            ? array_values(array_filter(array_map(function ($locale) {
                return sanitize_text_field((string) $locale);
            }, $body['supportedLocales'])))
            : $this->default_supported_locales();

        return [
            'enabled' => !array_key_exists('enabled', $config) || !empty($config['enabled']),
            'widgetEnabled' => !array_key_exists('widgetEnabled', $config) || !empty($config['widgetEnabled']),
            'assistantName' => sanitize_text_field((string) ($config['assistantName'] ?? '广而告之 AI 客服')),
            'welcomeMessage' => sanitize_textarea_field((string) ($config['welcomeMessage'] ?? '')),
            'locale' => sanitize_text_field((string) ($body['locale'] ?? 'zh-CN')),
            'supportedLocales' => $supported_locales ?: $this->default_supported_locales(),
            'cachedAt' => gmdate('c'),
        ];
    }

    private function default_supported_locales(): array {
        return [
            'zh-CN', 'en-US', 'de-DE', 'fr-FR', 'es-ES', 'pt-BR', 'ar', 'ja-JP', 'ko-KR', 'ru-RU',
            'it-IT', 'nl-NL', 'tr-TR', 'pl-PL', 'vi-VN', 'th-TH', 'id-ID', 'ms-MY', 'hi-IN',
            'cs-CZ', 'hu-HU', 'ro-RO', 'sv-SE', 'da-DK', 'fi-FI', 'nb-NO', 'el-GR', 'he-IL', 'uk-UA',
            'en', 'de', 'fr', 'it', 'nl', 'ja', 'ko', 'pl', 'cs', 'sk', 'mt'
        ];
    }

    private function schedule_event_flush(int $delay = 5): void {
        if (!wp_next_scheduled('gza_ai_growth_flush_events')) {
            $scheduled = wp_schedule_single_event(time() + max(1, $delay), 'gza_ai_growth_flush_events');
            if ($scheduled && function_exists('spawn_cron')) {
                spawn_cron(time());
            }
        }
    }

    private function schedule_cloud_job_run(int $delay = 1): void {
        $run_at = time() + max(0, $delay);
        $next = wp_next_scheduled('gza_ai_growth_process_jobs');
        if (!$next || $next > time() + 30) {
            wp_schedule_single_event($run_at, 'gza_ai_growth_process_jobs');
        }
        if (function_exists('spawn_cron')) {
            spawn_cron(time());
        }
    }

    private function idempotency_key(string $prefix, $payload): string {
        $encoded = is_string($payload) ? $payload : wp_json_encode($payload);
        return 'wp:' . substr(sanitize_key($prefix), 0, 64) . ':' . hash('sha256', (string) $encoded);
    }

    private function auth_headers(array $options): array {
        return ['Authorization' => 'Bearer ' . $options['site_token']];
    }

    private function site_token_hash(array $options): string {
        return hash('sha256', trim((string) ($options['site_token'] ?? '')));
    }

    private function is_valid_job_wake_signature(array $options, string $site_id, string $job_id, string $job_type, string $timestamp, string $signature): bool {
        if (!$site_id || !$job_id || !$job_type || !$timestamp || !$signature) {
            return false;
        }
        if (!hash_equals((string) ($options['site_id'] ?? ''), $site_id)) {
            return false;
        }
        if (!ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 5 * MINUTE_IN_SECONDS) {
            return false;
        }

        $payload = implode('.', [$site_id, $job_id, $job_type, $timestamp]);
        $expected = hash_hmac('sha256', $payload, $this->site_token_hash($options));

        return hash_equals($expected, $signature);
    }

    private function decode_response($response): array {
        if (is_wp_error($response)) {
            return ['ok' => false, 'message' => $response->get_error_message()];
        }

        $body = json_decode((string) wp_remote_retrieve_body($response), true);
        if (!is_array($body)) {
            return ['ok' => false, 'message' => 'Invalid cloud response.'];
        }

        if (array_key_exists('ok', $body) && (array_key_exists('data', $body) || array_key_exists('error', $body))) {
            if (empty($body['ok'])) {
                $error = isset($body['error']) && is_array($body['error']) ? $body['error'] : [];
                return [
                    'ok' => false,
                    'message' => sanitize_text_field((string) ($error['message'] ?? 'Cloud request failed.')),
                    'code' => sanitize_text_field((string) ($error['code'] ?? 'UPSTREAM_ERROR')),
                    'details' => isset($error['details']) && is_array($error['details']) ? $error['details'] : [],
                ];
            }

            $data = is_array($body['data']) ? $body['data'] : ['data' => $body['data']];
            $data['ok'] = true;
            if (!empty($body['requestId'])) {
                $data['requestId'] = sanitize_text_field((string) $body['requestId']);
            }
            return $data;
        }

        return $body;
    }

    private function post_text(string $key): string {
        return isset($_POST[$key]) ? sanitize_text_field(wp_unslash($_POST[$key])) : '';
    }

    private function normalize_api_base(string $value): string {
        $value = untrailingslashit($value ?: 'https://www.tui161.com');
        return esc_url_raw($value);
    }

    private function normalize_chat_theme(string $value): string {
        $themes = array_keys($this->chat_theme_presets());
        $themes[] = 'custom';
        return in_array($value, $themes, true) ? $value : 'teal';
    }

    private function normalize_color(string $value, string $fallback): string {
        $value = trim($value);
        return preg_match('/^#[0-9a-fA-F]{6}$/', $value) ? $value : $fallback;
    }

    private function chat_theme_colors(array $options): array {
        $theme = $this->normalize_chat_theme((string) ($options['chat_theme'] ?? 'teal'));
        $presets = $this->chat_theme_presets();

        if ($theme !== 'custom') {
            return $presets[$theme] ?? $presets['teal'];
        }

        return [
            'primary' => $this->normalize_color((string) ($options['chat_primary'] ?? ''), '#0f766e'),
            'accent' => $this->normalize_color((string) ($options['chat_primary'] ?? ''), '#14b8a6'),
            'panel' => $this->normalize_color((string) ($options['chat_panel'] ?? ''), '#07111f'),
            'panel_soft' => '#1e293b',
            'input' => '#020617',
            'input_text' => $this->normalize_color((string) ($options['chat_text'] ?? ''), '#e5edf7'),
            'border' => 'rgba(148,163,184,.22)',
            'text' => $this->normalize_color((string) ($options['chat_text'] ?? ''), '#e5edf7'),
            'muted' => '#94a3b8',
        ];
    }

    private function chat_theme_presets(): array {
        return [
            'teal' => [
                'label' => 'Default Teal / 默认官网视觉',
                'description' => 'Dark premium panel with teal action color, suitable for most B2B sites.',
                'primary' => '#0f766e',
                'accent' => '#2563eb',
                'panel' => '#07111f',
                'panel_soft' => '#1e293b',
                'input' => '#020617',
                'input_text' => '#e5edf7',
                'border' => 'rgba(148,163,184,.22)',
                'text' => '#e5edf7',
                'muted' => '#94a3b8',
            ],
            'blue' => [
                'label' => 'Ocean Blue / 科技蓝',
                'description' => 'Cool blue accents for SaaS, software, data and technical service sites.',
                'primary' => '#2563eb',
                'accent' => '#38bdf8',
                'panel' => '#071426',
                'panel_soft' => '#172554',
                'input' => '#020617',
                'input_text' => '#eef5ff',
                'border' => 'rgba(147,197,253,.24)',
                'text' => '#eef5ff',
                'muted' => '#93c5fd',
            ],
            'emerald' => [
                'label' => 'Emerald / 自然绿',
                'description' => 'Fresh green accents for health, education, sustainability and service brands.',
                'primary' => '#059669',
                'accent' => '#22c55e',
                'panel' => '#07130f',
                'panel_soft' => '#123026',
                'input' => '#03130c',
                'input_text' => '#ecfdf5',
                'border' => 'rgba(167,243,208,.22)',
                'text' => '#ecfdf5',
                'muted' => '#a7f3d0',
            ],
            'violet' => [
                'label' => 'Violet / 创意紫',
                'description' => 'Expressive purple accents for creative, design and lifestyle sites.',
                'primary' => '#7c3aed',
                'accent' => '#db2777',
                'panel' => '#0d0718',
                'panel_soft' => '#22123f',
                'input' => '#090313',
                'input_text' => '#f5f3ff',
                'border' => 'rgba(196,181,253,.22)',
                'text' => '#f5f3ff',
                'muted' => '#c4b5fd',
            ],
            'amber' => [
                'label' => 'Amber / 暖金',
                'description' => 'Warm gold accents for consulting, finance, real estate and premium services.',
                'primary' => '#d97706',
                'accent' => '#f59e0b',
                'panel' => '#160d05',
                'panel_soft' => '#39250d',
                'input' => '#120904',
                'input_text' => '#fffbeb',
                'border' => 'rgba(253,230,138,.22)',
                'text' => '#fffbeb',
                'muted' => '#fde68a',
            ],
            'rose' => [
                'label' => 'Rose / 轻奢红',
                'description' => 'Confident red accents for beauty, retail, fashion and high-conversion landing pages.',
                'primary' => '#e11d48',
                'accent' => '#f97316',
                'panel' => '#17070c',
                'panel_soft' => '#3a1420',
                'input' => '#12050a',
                'input_text' => '#fff1f2',
                'border' => 'rgba(253,164,175,.24)',
                'text' => '#fff1f2',
                'muted' => '#fda4af',
            ],
            'slate' => [
                'label' => 'Slate / 极简深色',
                'description' => 'Quiet neutral styling for corporate, industrial and information-heavy sites.',
                'primary' => '#475569',
                'accent' => '#0f172a',
                'panel' => '#05070d',
                'panel_soft' => '#111827',
                'input' => '#030712',
                'input_text' => '#f8fafc',
                'border' => 'rgba(148,163,184,.22)',
                'text' => '#f8fafc',
                'muted' => '#9ca3af',
            ],
            'light_teal' => [
                'label' => 'Light Teal / 清透青绿',
                'description' => 'Bright white panel with teal actions, good for clean corporate and service sites.',
                'primary' => '#0f766e',
                'accent' => '#14b8a6',
                'panel' => '#ffffff',
                'panel_soft' => '#ecfdf5',
                'input' => '#ffffff',
                'input_text' => '#0f172a',
                'border' => 'rgba(15,118,110,.18)',
                'text' => '#0f172a',
                'muted' => '#64748b',
            ],
            'light_blue' => [
                'label' => 'Light Blue / 清爽科技蓝',
                'description' => 'Light blue surfaces for SaaS, education, data dashboards and modern product sites.',
                'primary' => '#2563eb',
                'accent' => '#0ea5e9',
                'panel' => '#ffffff',
                'panel_soft' => '#eff6ff',
                'input' => '#ffffff',
                'input_text' => '#0f172a',
                'border' => 'rgba(37,99,235,.18)',
                'text' => '#0f172a',
                'muted' => '#64748b',
            ],
            'light_amber' => [
                'label' => 'Light Amber / 温暖浅金',
                'description' => 'Warm and approachable for local services, consulting and lifestyle brands.',
                'primary' => '#b45309',
                'accent' => '#f59e0b',
                'panel' => '#fffdf7',
                'panel_soft' => '#fef3c7',
                'input' => '#ffffff',
                'input_text' => '#1f2937',
                'border' => 'rgba(180,83,9,.2)',
                'text' => '#1f2937',
                'muted' => '#92400e',
            ],
            'light_rose' => [
                'label' => 'Light Rose / 柔和浅玫',
                'description' => 'Soft, friendly and conversion-oriented for retail, beauty and consumer brands.',
                'primary' => '#e11d48',
                'accent' => '#fb7185',
                'panel' => '#ffffff',
                'panel_soft' => '#fff1f2',
                'input' => '#ffffff',
                'input_text' => '#111827',
                'border' => 'rgba(225,29,72,.18)',
                'text' => '#111827',
                'muted' => '#9f1239',
            ],
        ];
    }

    private function get_options(): array {
        $options = get_option(self::OPTION_KEY, []);
        return wp_parse_args(is_array($options) ? $options : [], [
            'api_base' => 'https://www.tui161.com',
            'site_token' => '',
            'tenant_id' => '',
            'site_id' => '',
            'inject_seo' => true,
            'chat_widget' => true,
            'chat_theme' => 'teal',
            'chat_primary' => '#0f766e',
            'chat_panel' => '#07111f',
            'chat_text' => '#e5edf7',
            'chat_avatar_url' => '',
            'last_status_checked_at' => '',
            'last_sync_at' => '',
            'last_sync_count' => '',
            'last_job_at' => '',
            'last_job_count' => '',
            'last_content_inventory' => '',
            'last_update_checked_at' => '',
            'last_update_version' => '',
            'last_error' => '',
            'cloud_unavailable_until' => '',
            'cloud_failure_count' => '',
        ]);
    }
}

Guangergaozhi_Ai_Growth_Plugin::instance();
