<?php

/**
 * Shop2LLM — AI Discovery for Your Website
 *
 * Make your website content discoverable by AI assistants.
 * Works with any WordPress site — with WooCommerce for product AI visibility,
 * or standalone for blog/article AI visibility.
 * Free version supports ChatGPT with AI-friendly catalog, crawler tracking,
 * and auto-generated schema. Upgrade to Pro for 6 AI platforms.
 *
 * ---
 * Plugin Name: Shop2LLM — AI Visibility
 * Plugin URI:  https://shop2llm.com/
 * Description: Your WordPress site is invisible to ChatGPT, Perplexity, and AI search. Fix that in 2 clicks — Shop2LLM gives you a real-time AI visibility score, auto-generates llms.txt, and tracks 12 AI crawlers.
 * Version: 1.1.2
 * Author:      FinCore
 * Author URI:  https://shop2llm.com/
 * License:     GPL-2.0+
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: shop-to-llm
 * Domain Path: /languages
 * Requires PHP: 7.4
 * Requires at least: 6.0
 * Tested up to: 7.0
 * WC requires at least: 8.0
 * WC tested up to: 10.8
 * ---
 */

declare(strict_types=1);

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

define('SHOP_TO_LLM_VERSION', '1.1.2');
define('SHOP_TO_LLM_DIR', plugin_dir_path(__FILE__));
define('SHOP_TO_LLM_URL', plugin_dir_url(__FILE__));

/**
 * Check if WooCommerce is installed and active.
 */
function shop_to_llm_has_woocommerce(): bool
{
    return class_exists('WooCommerce');
}

// ─── PHP 7.4 Polyfills ─────────────────────────────────────────────────

if (!function_exists('str_starts_with')) {
    function str_starts_with(string $haystack, string $needle): bool
    {
        return $needle === '' || substr($haystack, 0, strlen($needle)) === $needle;
    }
}

// ─── Lifecycle ─────────────────────────────────────────────────────────

register_activation_hook(__FILE__, 'shop_to_llm_activate');
register_deactivation_hook(__FILE__, 'shop_to_llm_deactivate');

// Translation — load textdomain for i18n
$shop2llm_mofile = __DIR__ . '/languages/shop-to-llm-' . determine_locale() . '.mo';
if (file_exists($shop2llm_mofile)) {
    load_textdomain('shop-to-llm', $shop2llm_mofile);
}
unset($shop2llm_mofile);

// Register custom cron schedules ('monthly' and 'weekly' are not built-in)
add_filter('cron_schedules', 'shop_to_llm_cron_schedules');
function shop_to_llm_cron_schedules(array $schedules): array
{
    if (!isset($schedules['weekly'])) {
        $schedules['weekly'] = [
            'interval' => WEEK_IN_SECONDS,
            'display' => __('Once Weekly', 'shop-to-llm'),
        ];
    }
    if (!isset($schedules['monthly'])) {
        $schedules['monthly'] = [
            'interval' => MONTH_IN_SECONDS,
            'display' => __('Once Monthly', 'shop-to-llm'),
        ];
    }
    return $schedules;
}

// HPOS compatibility (required for WooCommerce Marketplace)
if (shop_to_llm_has_woocommerce()) {
    add_action('before_woocommerce_init', function () {
        if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) {
            \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true);
        }
    });
}

function shop_to_llm_activate(): void
{
    if (!shop_to_llm_has_woocommerce()) {
        set_transient('shop_to_llm_woocommerce_warning', 1, DAY_IN_SECONDS);
    }

    add_option('shop_to_llm_enabled', 'yes');
    add_option('shop_to_llm_api_key', wp_generate_password(32, false));
    add_option('shop_to_llm_catalog_search', 'yes');
    add_option('shop_to_llm_analytics', 'no');
    add_option('shop_to_llm_show_badge', 'yes');
    // Show onboarding wizard on first visit after activation
    add_option('shop_to_llm_just_activated', '1');
    shop_to_llm_log_event('activated');
    flush_rewrite_rules();

    // Create AI crawler activity table for persistent tracking
    shop_to_llm_create_crawler_table();

    // Create AI referral tracking table
    shop_to_llm_create_ai_referrals_table();

    // Schedule daily cleanup of crawler logs older than 30 days
    if (!wp_next_scheduled('shop2llm_cleanup_crawler_logs')) {
        wp_schedule_event(time(), 'daily', 'shop2llm_cleanup_crawler_logs');
    }

    // Always register install on activation — metadata only, no behavior
    $site_url = get_site_url();
    wp_remote_post('https://api.shop2llm.com/fincore/v1/free/ping', [
        'timeout' => 5,
        'blocking' => false,
        'headers' => ['Content-Type' => 'application/json'],
        'body' => json_encode([
            'site_url' => $site_url,
            'site_hash' => hash('sha256', wp_parse_url($site_url, PHP_URL_HOST) ?: $site_url),
            'platform' => shop_to_llm_has_woocommerce() ? 'woocommerce' : 'wordpress',
            'plugin_version' => SHOP_TO_LLM_VERSION,
            'php_version' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION,
            'wc_version' => shop_to_llm_has_woocommerce() ? WC()->version : '',
            'wp_version' => get_bloginfo('version'),
        ]),
    ]);

    // Push product catalog to search index immediately on activation
    shop_to_llm_sync_product_feed();

    // Daily cloud heartbeat — refreshes behavior data and active status
    if (!wp_next_scheduled('shop2llm_cloud_heartbeat')) {
        wp_schedule_event(time() + HOUR_IN_SECONDS, 'daily', 'shop2llm_cloud_heartbeat');
    }

    // Weekly visibility snapshot for all published posts
    if (!wp_next_scheduled('shop2llm_weekly_snapshot')) {
        wp_schedule_event(time(), 'weekly', 'shop2llm_weekly_snapshot');
    }

    // Redirect to dashboard after activation
    add_option('shop_to_llm_do_activation_redirect', true);
}

function shop_to_llm_deactivate(): void
{
    wp_clear_scheduled_hook('shop2llm_cloud_heartbeat');
    wp_clear_scheduled_hook('shop2llm_cleanup_crawler_logs');
    wp_clear_scheduled_hook('shop2llm_weekly_snapshot');
    flush_rewrite_rules();
}

// ─── REST API Routes ───────────────────────────────────────────────────

add_action('rest_api_init', 'shop_to_llm_register_routes');
add_action('wp', 'shop_to_llm_detect_ai_crawler', 1);

// ─── Admin Hooks ──────────────────────────────────────────────────────

add_action('admin_menu', 'shop_to_llm_add_admin_page');
add_action('admin_enqueue_scripts', 'shop_to_llm_enqueue_admin_assets');
add_action('admin_notices', 'shop_to_llm_render_onboarding_banner');
add_action('admin_notices', 'shop_to_llm_first_crawler_notice');
add_action('admin_notices', 'shop_to_llm_review_request_notice');
add_action('admin_notices', 'shop_to_llm_weekly_crawler_summary');
add_action('admin_notices', 'shop_to_llm_woocommerce_warning_notice');
add_action('admin_notices', 'shop_to_llm_robots_txt_fix_notice');
add_action('admin_init', 'shop_to_llm_activation_redirect');
add_action('wp_ajax_shop2llm_dismiss_onboarding', 'shop_to_llm_dismiss_onboarding');
add_action('wp_ajax_shop2llm_cache_community_count', 'shop_to_llm_cache_community_count');
add_action('wp_ajax_shop2llm_dismiss_pro_cta', 'shop_to_llm_dismiss_pro_cta');
add_action('wp_ajax_shop2llm_test_search', 'shop_to_llm_handle_test_search');
add_action('wp_ajax_shop2llm_fix_item', 'shop_to_llm_handle_fix_item');
add_action('wp_ajax_shop2llm_log_event', 'shop_to_llm_handle_log_event');
add_action('wp_ajax_shop2llm_enable_analytics', 'shop_to_llm_handle_enable_analytics');
add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'shop_to_llm_add_action_links');
add_action('wp_head', 'shop_to_llm_inject_product_schema');
add_action('init', 'shop_to_llm_generate_llms_txt');
add_action('shop2llm_cleanup_crawler_logs', 'shop_to_llm_cleanup_old_crawler_logs');
add_filter('query_vars', 'shop_to_llm_llms_txt_query_var');
add_action('template_redirect', 'shop_to_llm_render_llms_txt');
add_filter('robots_txt', 'shop_to_llm_robots_txt');

// ─── Feature 1: AI Discovery Badge & Verification Page ────────────────

add_action('wp_footer', 'shop_to_llm_render_footer_badge');
add_action('init', 'shop_to_llm_add_visibility_rewrite');
add_filter('query_vars', 'shop_to_llm_visibility_query_var');
add_action('template_redirect', 'shop_to_llm_render_visibility_page');

// ─── Feature 2: AI Referral Tracker ───────────────────────────────────

add_action('template_redirect', 'shop_to_llm_detect_ai_referral', 1);

// ─── Anchoring Feature Hooks ──────────────────────────────────────────

// P0: Admin Bar Real-time Badge
add_action('admin_bar_menu', 'shop_to_llm_admin_bar_crawler_badge', 999);
// P0: Dashboard Widget Recovery
add_action('wp_dashboard_setup', 'shop_to_llm_register_dashboard_widget');
// P0: Uninstall Confirmation Dialog
add_action('admin_footer-plugins.php', 'shop_to_llm_uninstall_confirmation_js');
// P1: AI Visibility Score Meta Box
add_action('add_meta_boxes', 'shop_to_llm_add_visibility_meta_box');
add_action('admin_head', 'shop_to_llm_visibility_meta_box_css');
// P2: AI Visibility Column in Post List
add_filter('manage_edit-post_columns', 'shop_to_llm_add_visibility_column');
add_filter('manage_edit-product_columns', 'shop_to_llm_add_visibility_column');
add_action('manage_post_posts_custom_column', 'shop_to_llm_render_visibility_column', 10, 2);
add_action('manage_product_posts_custom_column', 'shop_to_llm_render_visibility_column', 10, 2);
add_action('save_post', 'shop_to_llm_save_focus_query');
add_action('shop2llm_weekly_snapshot', 'shop_to_llm_weekly_snapshot_callback');

function shop_to_llm_register_routes(): void
{
    if (get_option('shop_to_llm_enabled', 'yes') !== 'yes') {
        return;
    }

    register_rest_route('shop2llm/v1', '/mcp', [
        'methods' => ['POST', 'GET'],
        'callback' => 'shop_to_llm_handle_mcp',
        'permission_callback' => 'shop_to_llm_check_auth',
    ]);

    register_rest_route('shop2llm/v1', '/.well-known/mcp', [
        'methods' => ['GET'],
        'callback' => 'shop_to_llm_mcp_discovery',
        'permission_callback' => '__return_true',
    ]);

    register_rest_route('shop2llm/v1', '/community', [
        'methods' => ['GET'],
        'callback' => 'shop_to_llm_community_stats_endpoint',
        'permission_callback' => '__return_true',
    ]);
}

function shop_to_llm_add_admin_page(): void
{
    add_menu_page(
        'Shop2LLM',
        'Shop2LLM',
        'manage_options',
        'shop2llm',
        'shop_to_llm_render_settings',
        'data:image/svg+xml;base64,' . base64_encode('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M5.5 7C3.5 7 2 8.5 2 10s1.5 3 3.5 3c1.2 0 2-.5 3-1.5L10 10l1.5 1.5c1 1 1.8 1.5 3 1.5 2 0 3.5-1.5 3.5-3S16.5 7 14.5 7c-1.2 0-2 .5-3 1.5L10 10 8.5 8.5C7.5 7.5 6.7 7 5.5 7z"/></svg>'),
        56
    );
}

function shop_to_llm_enqueue_admin_assets(string $hook): void
{
    if ($hook !== 'toplevel_page_shop2llm') {
        return;
    }
    wp_enqueue_style(
        'shop2llm-admin',
        SHOP_TO_LLM_URL . 'assets/css/admin.css',
        [],
        SHOP_TO_LLM_VERSION
    );
    wp_enqueue_script(
        'shop2llm-admin',
        SHOP_TO_LLM_URL . 'assets/js/admin.js',
        [],
        SHOP_TO_LLM_VERSION,
        true
    );
    wp_localize_script('shop2llm-admin', 'shop2llm', [
        'nonce' => wp_create_nonce('shop2llm_admin_nonce'),
        'ajaxurl' => admin_url('admin-ajax.php'),
        'restUrl' => rest_url('shop2llm/v1/'),
    ]);
}

function shop_to_llm_add_action_links(array $links): array
{
    $settings_link = '<a href="' . admin_url('admin.php?page=shop2llm') . '">' . esc_html__('Settings', 'shop-to-llm') . '</a>';
    array_unshift($links, $settings_link);
    return $links;
}

function shop_to_llm_activation_redirect(): void
{
    if (get_option('shop_to_llm_do_activation_redirect', false)) {
        delete_option('shop_to_llm_do_activation_redirect');
        // Only redirect if this is a single plugin activation (not bulk)
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- presence check only, no form processing
        if (!isset($_REQUEST['activate-multi'])) {
            wp_safe_redirect(admin_url('admin.php?page=shop2llm'));
            exit;
        }
    }
}

// ─── Onboarding Wizard ────────────────────────────────────────────────

function shop_to_llm_first_crawler_notice(): void
{
    $platform = get_transient('shop_to_llm_first_crawler_admin_notice');
    if (!$platform) {
        return;
    }
    // Only show on non-Shop2LLM pages to avoid cluttering the dashboard
    $screen = get_current_screen();
    if ($screen && $screen->id === 'toplevel_page_shop2llm') {
        return;
    }
    delete_transient('shop_to_llm_first_crawler_admin_notice');
    ?>
    <div class="notice notice-success is-dismissible" style="border-left-color:#166534">
        <p style="font-size:14px">
            <strong><?php echo shop_to_llm_has_woocommerce() ? esc_html(sprintf(/* translators: %s: platform name */ __('🎉 %s just discovered your store!', 'shop-to-llm'), $platform)) : esc_html(sprintf(/* translators: %s: platform name */ __('🎉 %s just discovered your site!', 'shop-to-llm'), $platform)); ?></strong><br>
            <?php echo shop_to_llm_has_woocommerce() ? esc_html__('Your products are now visible to AI assistants. View your AI visibility dashboard:', 'shop-to-llm') : esc_html__('Your content is now visible to AI assistants. View your AI visibility dashboard:', 'shop-to-llm'); ?>
            <a href="<?php echo esc_url(admin_url('admin.php?page=shop2llm')); ?>"><?php echo esc_html__('Open Shop2LLM Dashboard', 'shop-to-llm'); ?></a>
        </p>
    </div>
    <?php
}

function shop_to_llm_review_request_notice(): void
{
    $milestone = get_transient('shop_to_llm_review_request');
    if (!$milestone) {
        return;
    }
    // Only show on non-Shop2LLM pages to avoid cluttering the dashboard
    $screen = get_current_screen();
    if ($screen && $screen->id === 'toplevel_page_shop2llm') {
        return;
    }
    delete_transient('shop_to_llm_review_request');
    // Pull real community count from transient (set by async API call)
    $communityCount = get_transient('shop2llm_community_count');
    $communityText = $communityCount && $communityCount > 0
        ? sprintf(/* translators: %s: formatted community count */ __('🔥 %s stores are making their sites AI-visible', 'shop-to-llm'), number_format((int)$communityCount))
        : __('Join thousands of stores making their sites AI-visible', 'shop-to-llm');
    ?>
    <div class="notice notice-info is-dismissible" style="border-left-color:#6366f1">
        <p style="font-size:14px">
            <strong><?php echo esc_html(sprintf(/* translators: %s: milestone label */ __('🚀 %s — and AI consumers are already searching!', 'shop-to-llm'), $milestone)); ?></strong><br>
            <?php echo esc_html($communityText); ?><br>
            <a href="https://wordpress.org/support/plugin/shop2llm/reviews/#new-post" target="_blank" style="display:inline-block;margin-top:6px;padding:5px 14px;background:#6366f1;color:#fff;border-radius:4px;text-decoration:none;font-weight:600">
                <?php echo esc_html__('⭐ Leave a Review on WP.org', 'shop-to-llm'); ?>
            </a>
        </p>
    </div>
    <?php
}

function shop_to_llm_weekly_crawler_summary(): void
{
    // Only show once per week, on non-Shop2LLM pages
    $screen = get_current_screen();
    if ($screen && $screen->id === 'toplevel_page_shop2llm') {
        return;
    }

    $transientKey = 'shop2llm_weekly_summary_dismissed';
    if (get_transient($transientKey)) {
        return;
    }

    global $wpdb;
    $table = $wpdb->prefix . 'shop2llm_crawler_log';
    $exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    if (!$exists) {
        return;
    }

    // Count crawlers this week
    $weekAgo = gmdate('Y-m-d H:i:s', time() - 7 * DAY_IN_SECONDS);
    $weekCount = (int) $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, PluginCheck.Security.DirectDB.UnescapedDBParameter
        "SELECT COUNT(*) FROM {$table} WHERE visited_at >= %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $weekAgo
    ));
    if ($weekCount < 1) {
        return;
    }

    $uniqueBots = (int) $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, PluginCheck.Security.DirectDB.UnescapedDBParameter
        "SELECT COUNT(DISTINCT bot_name) FROM {$table} WHERE visited_at >= %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $weekAgo
    ));
    $totalAll = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table}"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared

    // Mark as shown for 7 days
    set_transient($transientKey, 1, 7 * DAY_IN_SECONDS);
    ?>
    <div class="notice notice-info is-dismissible" style="border-left-color:#8b5cf6">
        <p style="font-size:13px">
            <strong><?php echo esc_html(sprintf(
                /* translators: 1: number of crawler visits, 2: number of AI bots */
                __('🤖 %1$d AI crawler visits from %2$d platforms this week', 'shop-to-llm'),
                $weekCount,
                $uniqueBots
            )); ?></strong><br>
            <?php echo esc_html(sprintf(
                /* translators: %d: total crawler records */
                __('%d total crawler records accumulated. View your AI visibility trends:', 'shop-to-llm'),
                $totalAll
            )); ?>
            <a href="<?php echo esc_url(admin_url('admin.php?page=shop2llm')); ?>" style="margin-left:4px;font-weight:600">
                <?php echo esc_html__('Open Dashboard →', 'shop-to-llm'); ?>
            </a>
        </p>
    </div>
    <?php
}

function shop_to_llm_woocommerce_warning_notice(): void
{
    if (!get_transient('shop_to_llm_woocommerce_warning')) {
        return;
    }
    delete_transient('shop_to_llm_woocommerce_warning');
    ?>
    <div class="notice notice-warning is-dismissible">
        <p>
            <strong>Shop2LLM:</strong>
            <?php echo esc_html__('WooCommerce is not active. Product-related features (product search, product feed, product AI score) are disabled. Blog and page content is still discoverable by AI. Install WooCommerce for full product AI visibility.', 'shop-to-llm'); ?>
        </p>
    </div>
    <?php
}

function shop_to_llm_dismiss_onboarding(): void
{
    if (!current_user_can('manage_options')) {
        wp_die('unauthorized');
    }
    check_ajax_referer('shop2llm_admin_nonce', 'nonce');
    update_option('shop2llm_onboarding_done', 1);
    wp_die('ok');
}

function shop_to_llm_cache_community_count(): void
{
    if (!current_user_can('manage_options')) {
        wp_die('unauthorized');
    }
    check_ajax_referer('shop2llm_admin_nonce', 'nonce');
    $count = isset($_POST['count']) ? absint($_POST['count']) : 0;
    if ($count > 0) {
        set_transient('shop2llm_community_count', $count, DAY_IN_SECONDS);
    }
    wp_die('ok');
}

function shop_to_llm_dismiss_pro_cta(): void
{
    if (!current_user_can('manage_options')) {
        wp_die('unauthorized');
    }
    check_ajax_referer('shop2llm_admin_nonce', 'nonce');
    $userId = get_current_user_id();
    if ($userId > 0) {
        update_user_meta($userId, 'shop2llm_pro_cta_dismissed', '1');
        update_user_meta($userId, 'shop2llm_pro_cta_dismissed_at', time());
    }
    wp_die('ok');
}

function shop_to_llm_handle_test_search(): void
{
    if (!current_user_can('manage_options')) {
        wp_die();
    }
    check_ajax_referer('shop2llm_admin_nonce', 'nonce');
    $query = isset($_POST['query']) ? sanitize_text_field(wp_unslash($_POST['query'])) : '';
    if (empty($query)) {
        wp_send_json_error(['error' => __('Query required', 'shop-to-llm')]);
    }

    // Increment today's API count so the score updates on next page load
    $today = (int) get_transient('shop_to_llm_count_' . gmdate('Y-m-d'));
    set_transient('shop_to_llm_count_' . gmdate('Y-m-d'), $today + 1, DAY_IN_SECONDS);
    $count7d = (int) get_transient('shop_to_llm_count_7d');
    set_transient('shop_to_llm_count_7d', $count7d + 1, DAY_IN_SECONDS);

    // v1.1.1: Local quota counter (UI hint only; real enforcement is server-side)
    $todayKey = gmdate('Y-m-d');
    $quotaKey = 'shop2llm_test_quota_' . $todayKey;
    $usedToday = (int) get_transient($quotaKey);
    $dailyLimit = (int) apply_filters('shop2llm_test_search_daily_limit', 5);
    $remainingBefore = max(0, $dailyLimit - $usedToday);

    $results = shop_to_llm_search_products(['query' => $query, 'limit' => 10]);
    $items = $results['products'] ?? [];

    // Format results for the frontend — v1.1.1: include full product fields
    $products = [];
    foreach ($items as $r) {
        $products[] = [
            'name' => $r['name'] ?? 'Unknown',
            'price' => $r['price'] ?? '',
            'currency' => $r['currency'] ?? '',
            'in_stock' => !empty($r['in_stock']),
            'rating' => (float) ($r['rating'] ?? 0),
            'short_description' => $r['short_description'] ?? '',
            'url' => $r['url'] ?? '',
            'image_url' => $r['image_url'] ?? '',
        ];
    }

    // v1.1.1: Generate ChatGPT-style reply via FinCore cloud LLM proxy.
    // Server-side enforces real quota (5/day per site_hash). Local counter is UI hint only.
    $aiReply = shop_to_llm_generate_ai_test_reply($query, $products);

    // Increment local quota counter after the cloud call (matches server-side accounting)
    set_transient($quotaKey, $usedToday + 1, DAY_IN_SECONDS);
    $remainingAfter = max(0, $dailyLimit - ($usedToday + 1));

    wp_send_json_success([
        'products' => $products,
        'ai_reply' => $aiReply,
        'remaining_quota' => $remainingAfter,
        'daily_limit' => $dailyLimit,
    ]);
}

/**
 * v1.1.1: Generate a ChatGPT-style natural-language reply via FinCore cloud LLM proxy.
 *
 * Server-side endpoint (api.shop2llm.com/fincore/v1/free/ai-test) enforces:
 *   - site_hash registration check
 *   - 5 calls/day per site quota
 *   - Fixed prompt template (no free-form prompt injection)
 *   - Returns natural-language reply only (no product data — that stays local)
 *
 * If the cloud service is unavailable or quota exceeded, gracefully degrades:
 *   - 429: returns localized "quota exhausted" message
 *   - Other errors: returns empty string (UI will fall back to plain product list)
 *
 * @param string $query  User's search query (already sanitized)
 * @param array  $products  Local search results (already formatted)
 * @return string  AI-generated reply (empty string on failure)
 */
function shop_to_llm_generate_ai_test_reply(string $query, array $products): string
{
    $site_url = get_site_url();
    $site_hash = hash('sha256', wp_parse_url($site_url, PHP_URL_HOST) ?: $site_url);

    // Build lightweight product summary — no images, no full descriptions (bandwidth-friendly)
    $productSummary = array_map(function ($p) {
        return [
            'name' => mb_substr($p['name'], 0, 120),
            'price' => $p['price'],
            'currency' => $p['currency'] ?? '',
            'in_stock' => $p['in_stock'],
            'rating' => $p['rating'] ?? 0,
        ];
    }, array_slice($products, 0, 10));

    $payload = [
        'site_url' => $site_url,
        'site_hash' => $site_hash,
        'plugin_version' => SHOP_TO_LLM_VERSION,
        'query' => mb_substr($query, 0, 200),
        'has_woocommerce' => shop_to_llm_has_woocommerce(),
        'site_name' => get_bloginfo('name'),
        'locale' => get_locale(),
        'products' => $productSummary,
        'product_count' => count($products),
    ];

    $response = wp_remote_post('https://api.shop2llm.com/fincore/v1/free/ai-test', [
        'timeout' => 15,
        'headers' => ['Content-Type' => 'application/json'],
        'body' => json_encode($payload),
    ]);

    if (is_wp_error($response)) {
        return '';
    }

    $code = wp_remote_retrieve_response_code($response);
    $body = json_decode(wp_remote_retrieve_body($response), true);

    if ($code === 429) {
        return __('You have reached the daily limit for AI test searches (5 per day). Try again tomorrow, or upgrade to Pro for unlimited AI test searches with multi-platform support.', 'shop-to-llm');
    }

    if ($code !== 200 || empty($body['ai_reply'])) {
        return '';
    }

    // Sanitize but preserve line breaks and basic formatting
    $reply = sanitize_textarea_field($body['ai_reply']);
    return trim($reply);
}

/**
 * AJAX handler: Fix a single score item.
 * Supports: fix_robots, flush_rewrite
 */
function shop_to_llm_handle_fix_item(): void
{
    if (!current_user_can('manage_options')) {
        wp_die();
    }
    check_ajax_referer('shop2llm_admin_nonce', 'nonce');
    $fixAction = isset($_POST['fix_action']) ? sanitize_text_field(wp_unslash($_POST['fix_action'])) : '';
    
    switch ($fixAction) {
        case 'fix_robots':
            flush_rewrite_rules();
            delete_transient('shop2llm_robots_allows_ai');
            delete_transient('shop2llm_admin_bar_score');
            update_option('shop_to_llm_robots_fixed', 1);
            wp_send_json_success(['message' => 'Robots.txt fixed', 'fix_action' => 'fix_robots']);
            break;
        case 'flush_rewrite':
            flush_rewrite_rules();
            // Clear cached accessibility checks so score recalculates on next load
            delete_transient('shop2llm_llms_accessible');
            delete_transient('shop2llm_feed_accessible');
            delete_transient('shop2llm_admin_bar_score');
            update_option('shop_to_llm_rewrite_flushed', 1);
            wp_send_json_success(['message' => 'Permalinks flushed + cache cleared', 'fix_action' => 'flush_rewrite']);
            break;
        default:
            wp_send_json_error(['error' => 'Unknown fix action: ' . $fixAction]);
    }
}

function shop_to_llm_render_onboarding_banner(): void
{
    if (get_option('shop2llm_onboarding_done')) {
        return;
    }
    $dashboardUrl = admin_url('admin.php?page=shop2llm');

    // Get crawler stats for banner
    $stats = shop_to_llm_get_crawler_stats();
    $todayBots = (int) $stats['today_bots'];
    $todayPages = (int) $stats['today_pages'];
    $total30d = (int) $stats['total_30d'];

    ?>
    <div class="shop2llm-onboarding" style="background:#fff;border:1px solid #e2e8f0;border-radius:10px;margin:20px 0;padding:16px 20px;box-shadow:0 1px 3px rgba(0,0,0,0.04);display:flex;align-items:center;gap:16px;flex-wrap:wrap">
      <span style="flex-shrink:0;font-size:22px;font-weight:700;color:#6366f1;letter-spacing:-0.5px">Shop2LLM</span>
      <div style="flex:1;min-width:200px;font-size:13px;color:#334155"><?php echo esc_html__('Your site is now visible to ChatGPT and AI assistants.', 'shop-to-llm'); ?></div>
      <?php if ($total30d > 0) : ?>
      <div style="display:flex;align-items:center;gap:12px;font-size:12px;color:#64748b;background:#f8fafc;padding:5px 12px;border-radius:6px;border:1px solid #e2e8f0">
        <span>🤖 <strong style="color:#6366f1"><?php echo esc_html((string)$todayBots); ?></strong> bots today</span>
        <span style="color:#e2e8f0">|</span>
        <span>📄 <strong style="color:#6366f1"><?php echo esc_html((string)$todayPages); ?></strong> pages</span>
        <span style="color:#e2e8f0">|</span>
        <span>📊 <strong style="color:#6366f1"><?php echo esc_html((string)$total30d); ?></strong> crawls (30d)</span>
      </div>
      <?php endif; ?>
      <div style="display:flex;align-items:center;gap:8px">
        <a href="<?php echo esc_url($dashboardUrl); ?>" style="font-size:12px;background:#6366f1;color:#fff;border:none;padding:6px 14px;border-radius:5px;text-decoration:none;font-weight:500;white-space:nowrap"><?php echo esc_html__('Open Dashboard', 'shop-to-llm'); ?></a>
        <button type="button" onclick="var e=this.closest('.shop2llm-onboarding');fetch(ajaxurl+'?action=shop2llm_dismiss_onboarding',{method:'POST'}).then(()=>e.style.display='none')" style="font-size:12px;background:none;border:none;color:#94a3b8;cursor:pointer;padding:0 4px" title="<?php echo esc_attr__('Dismiss', 'shop-to-llm'); ?>">✕</button>
      </div>
    </div>
    <?php
}

function shop_to_llm_render_onboarding_wizard(
    int $apiCount7d,
    array $crawlerVisits,
    array $recentSearches
): void {
    $crawlerCount = count($crawlerVisits);
    $searchCount = count($recentSearches);
    $mcpUrl = rest_url('shop2llm/v1/mcp');
    // Use user's site mode selection, not just WooCommerce presence
    $siteMode = get_option('shop_to_llm_site_mode', '');
    $wcInstalled = shop_to_llm_has_woocommerce();
    $isCommerce = ($siteMode === 'commerce' && $wcInstalled);

    $step1 = true;
    $step2 = $crawlerCount > 0;
    $step3 = $crawlerCount > 0;
    $step4 = $searchCount > 0;
    $completed = ($step1 ? 1 : 0) + ($step2 ? 1 : 0) + ($step3 ? 1 : 0) + ($step4 ? 1 : 0);
    $pct = round(($completed / 4) * 100);
    // Visual distinction: content mode = blue, commerce mode = purple
    $wizardTheme = $isCommerce
        ? ['bg' => '#f5f3ff', 'border' => '#ddd6fe', 'accent' => '#6366f1', 'accentDark' => '#4338ca', 'progress' => 'linear-gradient(90deg,#6366f1,#4338ca)']
        : ['bg' => '#eff6ff', 'border' => '#bfdbfe', 'accent' => '#3b82f6', 'accentDark' => '#1d4ed8', 'progress' => 'linear-gradient(90deg,#3b82f6,#1d4ed8)'];
    ?>
    <div class="stl-card stl-wizard" style="background:<?php echo esc_attr($wizardTheme['bg']); ?>;border-color:<?php echo esc_attr($wizardTheme['border']); ?>">
        <div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:6px">
            <h2 style="color:<?php echo esc_attr($wizardTheme['accentDark']); ?>;margin:0;display:flex;align-items:center;gap:6px">
                <?php $lReviewUrl = 'https://wordpress.org/support/plugin/shop2llm/reviews/#new-post'; ?>
                <span style="display:inline-flex;flex-direction:row-reverse;gap:2px" title="<?php echo esc_attr__('Rate us on WordPress.org', 'shop-to-llm'); ?>">
                    <?php for ($i = 5; $i >= 1; $i--) : ?>
                    <a href="<?php echo esc_url($lReviewUrl); ?>" target="_blank" style="font-size:16px;text-decoration:none;color:#94a3b8;transition:color 0.15s;line-height:1"
                       onmouseover="for(var s=this.parentElement.children,i=0;i<s.length;i++)s[i].style.color=i>=5-<?php echo (int)$i; ?>?'#eab308':'#94a3b8'"
                       onmouseout="for(var s=this.parentElement.children,i=0;i<s.length;i++)s[i].style.color='#94a3b8'">★</a>
                    <?php endfor; ?>
                </span>
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
                <?php echo esc_html__('Wizard', 'shop-to-llm'); ?>
            </h2>
            <div style="display:flex;align-items:center;gap:10px">
                <span style="font-size:12px;font-weight:600;color:<?php echo esc_attr($wizardTheme['accent']); ?>"><?php echo esc_html((string)$completed); ?>/4</span>
                <button type="button" id="stl-wizard-close-btn" style="background:none;border:none;cursor:pointer;color:#94a3b8;font-size:16px;line-height:1;padding:4px" title="<?php echo esc_attr__('Close — will re-appear after 8 hours', 'shop-to-llm'); ?>">✕</button>
            </div>

        </div>
        <div style="height:3px;background:#e2e8f0;border-radius:2px;margin-bottom:12px">
            <div style="width:<?php echo esc_attr((string)$pct); ?>%;height:100%;background:<?php echo esc_attr($wizardTheme['progress']); ?>;border-radius:2px;transition:width 0.4s"></div>
        </div>

        <div class="stl-wizard-steps">
            <div class="stl-wizard-step">
                <div class="stl-wizard-step-bullet">1</div>
                <div class="stl-wizard-step-body">
                    <?php if ($isCommerce) : ?>
                    <strong><?php echo esc_html__('Check Your Products', 'shop-to-llm'); ?></strong>
                    <span><?php echo esc_html__('Enter a SKU in the Product AI Score card below to see which products need improvement for AI discovery.', 'shop-to-llm'); ?></span>
                    <?php else : ?>
                    <strong><?php echo esc_html__('Review Your Content', 'shop-to-llm'); ?></strong>
                    <span><?php echo esc_html__('Check your posts and pages — AI can now search your content by keyword. Make sure your key content is published.', 'shop-to-llm'); ?></span>
                    <?php endif; ?>
                </div>
            </div>

            <div class="stl-wizard-step" data-step="2">
                <div class="stl-wizard-step-bullet">2</div>
                <div class="stl-wizard-step-body">
                    <strong><?php echo esc_html__('Connect to ChatGPT', 'shop-to-llm'); ?></strong>
                    <?php if ($isCommerce) : ?>
                    <span><?php echo esc_html__('Copy this link and add it to ChatGPT — then you can search your own products to verify everything works.', 'shop-to-llm'); ?></span>
                    <?php else : ?>
                    <span><?php echo esc_html__('Copy this link and add it to ChatGPT — then you can search your own content to verify everything works.', 'shop-to-llm'); ?></span>
                    <?php endif; ?>
                    <div class="stl-mcp-url-row">
                        <code class="stl-mono" id="stl-mcp-url"><?php echo esc_url($mcpUrl); ?></code>
                        <button type="button" class="stl-copy-btn" id="stl-copy-mcp-btn">📋 <?php echo esc_html__('Copy', 'shop-to-llm'); ?></button>
                    </div>
                </div>
            </div>

            <div class="stl-wizard-step">
                <div class="stl-wizard-step-bullet">3</div>
                <div class="stl-wizard-step-body">
                    <?php if ($isCommerce) : ?>
                    <strong><?php echo esc_html__('Improve Product Pages', 'shop-to-llm'); ?></strong>
                    <span><?php echo esc_html__('AI prefers products with clear descriptions, images, and categories. Use the score card below to find what to fix.', 'shop-to-llm'); ?></span>
                    <?php else : ?>
                    <strong><?php echo esc_html__('Optimize Your Posts', 'shop-to-llm'); ?></strong>
                    <span><?php echo esc_html__('AI prefers clear titles, meta descriptions, and well-structured content. Use the Visibility Score in the post editor to guide improvements.', 'shop-to-llm'); ?></span>
                    <?php endif; ?>
                </div>
            </div>

            <div class="stl-wizard-step">
                <div class="stl-wizard-step-bullet">4</div>
                <div class="stl-wizard-step-body">
                    <strong><?php echo esc_html__('Watch AI Activity Grow', 'shop-to-llm'); ?></strong>
                    <?php if ($isCommerce) : ?>
                    <span><?php echo esc_html__('When customers ask AI about products you sell, their search terms and crawler visits will appear here on your dashboard.', 'shop-to-llm'); ?></span>
                    <?php else : ?>
                    <span><?php echo esc_html__('When people ask AI about topics you cover, their search terms and crawler visits will appear here on your dashboard.', 'shop-to-llm'); ?></span>
                    <?php endif; ?>
                </div>
            </div>
        </div>

        <div class="stl-wizard-footer">
            <span style="font-size:12px;color:#64748b"><?php echo esc_html__('Dismiss anytime.', 'shop-to-llm'); ?></span>
        </div>
    </div>
    <?php
}

/**
 * First-Run Welcome Modal — clean single-card overlay shown once after activation.
 * One step: brief intro + optional opt-in, dismiss to dashboard.
 */
function shop_to_llm_render_welcome_modal(): void
{
    $isCommerce = shop_to_llm_has_woocommerce();
    $title = $isCommerce ? __('Shop2LLM — your store is now AI-visible', 'shop-to-llm') : __('Shop2LLM — your site is now AI-visible', 'shop-to-llm');
    $desc = $isCommerce
        ? __('ChatGPT and other AI assistants can now discover your products. Your dashboard shows AI traffic and visibility score.', 'shop-to-llm')
        : __('ChatGPT and other AI assistants can now discover your content. Your dashboard shows AI traffic and visibility score.', 'shop-to-llm');
    ?>
    <div id="stl-welcome-overlay" style="position:fixed;inset:0;background:rgba(15,23,42,0.5);z-index:100000;display:flex;align-items:center;justify-content:center;padding:24px">
        <div style="background:#fff;border-radius:14px;max-width:400px;width:100%;box-shadow:0 16px 48px rgba(0,0,0,0.25);text-align:center;padding:32px 28px 24px">
            <img src="<?php echo esc_url(SHOP_TO_LLM_URL . 'icon.png'); ?>" alt="Shop2LLM" style="width:48px;height:48px;border-radius:10px;margin-bottom:16px" />
            <h2 style="font-size:18px;font-weight:700;color:#0f172a;margin:0 0 8px"><?php echo esc_html($title); ?></h2>
            <p style="font-size:13px;color:#64748b;margin:0 0 24px;line-height:1.5"><?php echo esc_html($desc); ?></p>

            <div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:14px;margin-bottom:20px;text-align:left">
                <p style="font-size:12px;color:#475569;margin:0 0 10px"><?php echo esc_html__('Help us improve — share anonymous usage stats?', 'shop-to-llm'); ?></p>
                <div style="display:flex;gap:8px">
                    <button type="button" id="stl-optin-yes" style="flex:1;padding:8px 0;background:#6366f1;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer"><?php echo esc_html__('Yes, sure', 'shop-to-llm'); ?></button>
                    <button type="button" id="stl-optin-no" style="flex:1;padding:8px 0;background:#fff;color:#64748b;border:1px solid #e2e8f0;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer"><?php echo esc_html__('No thanks', 'shop-to-llm'); ?></button>
                </div>
                <p style="font-size:10px;color:#94a3b8;margin:8px 0 0"><?php echo esc_html__('No personal data. Change anytime in Settings.', 'shop-to-llm'); ?></p>
            </div>

            <button type="button" id="stl-welcome-close" style="width:100%;padding:10px 0;background:none;color:#94a3b8;border:0;font-size:12px;cursor:pointer"><?php echo esc_html__('Go to dashboard', 'shop-to-llm'); ?></button>
        </div>
    </div>

    <script>
    (function(){
        var overlay = document.getElementById('stl-welcome-overlay');
        if (!overlay) return;
        var ajaxUrl = '<?php echo esc_js(admin_url('admin-ajax.php')); ?>';
        var adminNonce = '<?php echo esc_js(wp_create_nonce('shop2llm_admin_nonce')); ?>';

        function ajax(action) {
            var fd = new FormData();
            fd.append('action', action);
            fd.append('nonce', adminNonce);
            return fetch(ajaxUrl, {method:'POST', body:fd});
        }

        function close() {
            overlay.style.display = 'none';
            var fd = new FormData();
            fd.append('action', 'shop2llm_dismiss_onboarding');
            fd.append('nonce', adminNonce);
            fetch(ajaxUrl, {method:'POST', body:fd});
        }

        // Fire welcome_shown
        (function(){ var f = new FormData(); f.append('action','shop2llm_log_event'); f.append('nonce',adminNonce); f.append('event','welcome_shown'); fetch(ajaxUrl,{method:'POST',body:f}); })();

        document.getElementById('stl-optin-yes').addEventListener('click', function(){
            ajax('shop2llm_enable_analytics').then(function(){ close(); });
        });
        document.getElementById('stl-optin-no').addEventListener('click', function(){ close(); });
        document.getElementById('stl-welcome-close').addEventListener('click', function(){ close(); });
    })();
    </script>
    <?php
}

// ─── Settings Page ─────────────────────────────────────────────────────

function shop_to_llm_render_settings(): void
{
    if (!current_user_can('manage_options')) {
        wp_die(esc_html__('Unauthorized', 'shop-to-llm'));
    }

    shop_to_llm_log_event('dashboard_visit');

    if (isset($_POST['shop_to_llm_save'])) {
        check_admin_referer('shop_to_llm_settings');
        $enabled_post = sanitize_text_field(wp_unslash($_POST['shop_to_llm_enabled'] ?? 'yes'));
        $enabled = in_array($enabled_post, ['yes', 'no'], true) ? $enabled_post : 'yes';
        $catalog_post = sanitize_text_field(wp_unslash($_POST['shop_to_llm_catalog_search'] ?? 'yes'));
        $catalogSearch = in_array($catalog_post, ['yes', 'no'], true) ? $catalog_post : 'yes';
        $analytics_post = sanitize_text_field(wp_unslash($_POST['shop_to_llm_analytics'] ?? 'no'));
        $analytics = in_array($analytics_post, ['yes', 'no'], true) ? $analytics_post : 'no';
        $wasAnalyticsEnabled = get_option('shop_to_llm_analytics', 'no');
        update_option('shop_to_llm_enabled', $enabled);
        update_option('shop_to_llm_catalog_search', $catalogSearch);
        update_option('shop_to_llm_analytics', $analytics);
        $badge_post = sanitize_text_field(wp_unslash($_POST['shop_to_llm_show_badge'] ?? 'yes'));
        $showBadge = in_array($badge_post, ['yes', 'no'], true) ? $badge_post : 'yes';
        update_option('shop_to_llm_show_badge', $showBadge);
        $entityChecks = isset($_POST['shop_to_llm_enable_entity_checks']) ? 1 : 0;
        update_option('shop_to_llm_enable_entity_checks', $entityChecks);
        if ($wasAnalyticsEnabled !== 'yes' && $analytics === 'yes') {
            $site_url = get_site_url();
            wp_remote_post('https://api.shop2llm.com/fincore/v1/free/ping', [
                'timeout' => 5,
                'blocking' => false,
                'headers' => ['Content-Type' => 'application/json'],
                'body' => json_encode([
                    'site_url' => $site_url,
                'site_hash' => hash('sha256', wp_parse_url($site_url, PHP_URL_HOST) ?: $site_url),
                'platform' => shop_to_llm_has_woocommerce() ? 'woocommerce' : 'wordpress',
                'plugin_version' => SHOP_TO_LLM_VERSION,
                'php_version' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION,
                'wc_version' => class_exists('WooCommerce') ? WC()->version : '',
                'wp_version' => get_bloginfo('version'),
                'behavior' => shop_to_llm_get_behavior_summary_for_upload(),
                ]),
            ]);
        }
        echo '<div class="notice notice-success"><p>' . esc_html__('Settings saved.', 'shop-to-llm') . '</p></div>';
    }

    $enabled = get_option('shop_to_llm_enabled', 'yes');
    $apiKey = get_option('shop_to_llm_api_key', '');
    $catalogSearch = get_option('shop_to_llm_catalog_search', 'yes');
    $analytics = get_option('shop_to_llm_analytics', 'no');
    $showBadge = get_option('shop_to_llm_show_badge', 'yes');
    $storeName = get_bloginfo('name');
    $apiCount7d = (int) get_transient('shop_to_llm_count_7d');
    $todayCount = shop_to_llm_get_today_api_count();
    $mcpEndpoint = rest_url('shop2llm/v1/mcp');

    // Handle mode toggle (mirrors Pro)
    if (isset($_POST['shop_to_llm_mode']) && in_array($_POST['shop_to_llm_mode'], ['content', 'commerce'], true)) {
        update_option('shop_to_llm_site_mode', sanitize_text_field(wp_unslash($_POST['shop_to_llm_mode'])));
    }
    $siteMode = get_option('shop_to_llm_site_mode', '');
    if ($siteMode === '') {
        // Auto-detect on first visit
        $hasWC = shop_to_llm_has_woocommerce();
        if (!$hasWC) {
            $siteMode = 'content';
        } else {
            $productCount = (int) (wp_count_posts('product')->publish ?? 0);
            $postCount = (int) (wp_count_posts('post')->publish ?? 0);
            $siteMode = ($postCount >= 5 && $productCount < 5) ? 'content' : 'commerce';
        }
        update_option('shop_to_llm_site_mode', $siteMode);
    }
    $hasWC = shop_to_llm_has_woocommerce();
    $effectiveWCMode = ($siteMode === 'commerce' && $hasWC);

    // Fetch wizard data early
    $crawlerVisits = shop_to_llm_get_recent_crawlers();
    $recentSearches = shop_to_llm_get_recent_searches();
    $crawlerCount = count($crawlerVisits);
    $searchCount = count($recentSearches);

    ?>
    <div class="stl-wrap">
        <div class="stl-header">
            <div class="stl-logo">
                <span>∞</span>
            </div>
            <div style="flex:1">
                <h1>Shop2LLM <span style="font-size:12px;color:#94a3b8;font-weight:400">v<?php echo esc_html(SHOP_TO_LLM_VERSION); ?></span></h1>
                <p style="font-size:13px;color:#64748b;margin:4px 0 0;line-height:1.4"><?php echo $effectiveWCMode ? esc_html__('Get your products discovered by ChatGPT and AI-powered search engines.', 'shop-to-llm') : esc_html__('Get your content discovered by ChatGPT and AI-powered search engines.', 'shop-to-llm'); ?></p>
            </div>
            <form method="post" style="display:flex;align-items:center;gap:4px;background:#f8fafc;padding:4px;border-radius:8px;border:1px solid #e2e8f0;margin-right:8px">
                <label style="font-size:11px;color:#64748b;white-space:nowrap;padding:0 3px;cursor:pointer">
                    <input type="radio" name="shop_to_llm_mode" value="content" <?php checked($siteMode, 'content'); ?> onchange="this.form.submit()" />
                    <?php echo esc_html__('Content', 'shop-to-llm'); ?>
                </label>
                <label style="font-size:11px;color:#64748b;white-space:nowrap;padding:0 3px;cursor:pointer">
                    <input type="radio" name="shop_to_llm_mode" value="commerce" <?php checked($siteMode, 'commerce'); ?> onchange="this.form.submit()" />
                    <?php echo esc_html__('Store', 'shop-to-llm'); ?>
                </label>
            </form>
            <button type="button" class="stl-wizard-toggle" id="stl-wizard-toggle-btn">🚀 <?php echo esc_html__('Wizard', 'shop-to-llm'); ?></button>
        </div>

        <!-- Onboarding Wizard (hidden until triggered) -->
        <div id="stl-openai-guide" style="display:none">
            <?php shop_to_llm_render_onboarding_wizard($apiCount7d, $crawlerVisits, $recentSearches); ?>
        </div>

        <script>
        (function(){
            var card = document.getElementById('stl-openai-guide');
            var toggleBtn = document.getElementById('stl-wizard-toggle-btn');
            var closeBtn = document.getElementById('stl-wizard-close-btn');
            var copyBtn = document.getElementById('stl-copy-mcp-btn');

            function showWizard() {
                if (card) card.style.display = 'block';
                if (toggleBtn) toggleBtn.classList.add('stl-wizard-toggle--active');
            }
            function hideWizard() {
                if (card) card.style.display = 'none';
                if (toggleBtn) toggleBtn.classList.remove('stl-wizard-toggle--active');
            }

            if (toggleBtn) {
                toggleBtn.addEventListener('click', function () {
                    if (!card) return;
                    if (card.style.display === 'none') showWizard();
                    else hideWizard();
                });
            }
            if (closeBtn) {
                closeBtn.addEventListener('click', hideWizard);
            }
            if (copyBtn) {
                copyBtn.addEventListener('click', function () {
                    var el = document.getElementById('stl-mcp-url');
                    if (!el) return;
                    navigator.clipboard.writeText(el.textContent.trim()).then(function () {
                        copyBtn.classList.add('stl-copy-btn--copied');
                        copyBtn.textContent = '✓ Copied';
                        setTimeout(function () {
                            copyBtn.classList.remove('stl-copy-btn--copied');
                            copyBtn.textContent = '📋 Copy';
                        }, 2000);
                        var s = document.querySelector('.stl-wizard-step[data-step="2"]');
                        if (s) {
                            s.classList.add('stl-wizard-step--done');
                            var bullet = s.querySelector('.stl-wizard-step-bullet');
                            if (bullet) bullet.textContent = '✓';
                        }
                    });
                });
            }
        })();
        </script>

        <?php
        // Show full-screen welcome modal on first visit after activation
        $justActivated = get_option('shop_to_llm_just_activated');
        if ($justActivated) {
            delete_option('shop_to_llm_just_activated');
            update_option('shop2llm_onboarding_done', 1);
            shop_to_llm_render_welcome_modal();
        }
        ?>

        <?php if ($siteMode === 'commerce' && !$hasWC) : ?>
        <div style="background:#fef3c7;border:1px solid #fcd34d;border-radius:8px;padding:10px 16px;margin-bottom:12px;font-size:12px;color:#92400e;display:flex;align-items:center;gap:8px">
            <span style="flex-shrink:0;font-size:16px">⚠️</span>
            <span style="flex:1"><?php echo esc_html__('WooCommerce is not active. Product search by price/category/stock, product AI scoring, and product feed XML are unavailable. Install WooCommerce for the full experience.', 'shop-to-llm'); ?></span>
            <a href="<?php echo esc_url(admin_url('plugin-install.php?s=woocommerce&tab=search')); ?>" style="font-size:11px;color:#92400e;font-weight:600;white-space:nowrap"><?php echo esc_html__('Install →', 'shop-to-llm'); ?></a>
        </div>
        <?php endif; ?>

        <!-- Tabs Navigation -->
        <div class="stl-tabs">
          <button class="stl-tab stl-tab-active" data-tab="dashboard">Dashboard</button>
          <button class="stl-tab" data-tab="analytics">Analytics</button>
          <button class="stl-tab" data-tab="settings">Settings</button>
        </div>

        <!-- Dashboard Tab -->
        <div class="stl-tab-panel stl-tab-panel-active" id="tab-dashboard">

        <?php
        // v1.1.1: Hero Test Search — AI-powered product recommendation at Dashboard top
        $heroHasWC = shop_to_llm_has_woocommerce();
        $heroPlaceholder = $heroHasWC
            ? __('e.g. wireless headphones under $50', 'shop-to-llm')
            : __('e.g. how to start a blog', 'shop-to-llm');
        $heroTitle = $heroHasWC
            ? __('See how AI sees your store', 'shop-to-llm')
            : __('See how AI sees your site', 'shop-to-llm');
        $heroSubtitle = __('Ask anything — get an instant AI-powered answer with your real products and content.', 'shop-to-llm');
        $heroTodayKey = gmdate('Y-m-d');
        $heroUsed = (int) get_transient('shop2llm_test_quota_' . $heroTodayKey);
        $heroLimit = (int) apply_filters('shop2llm_test_search_daily_limit', 5);
        $heroRemaining = max(0, $heroLimit - $heroUsed);
        ?>
        <div id="stl-hero-test-search" style="background:linear-gradient(135deg,#fef3c7 0%,#fde68a 100%);border:1px solid #fcd34d;border-radius:12px;padding:18px 20px;margin-bottom:16px;position:relative;overflow:hidden">
            <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px">
                <div style="width:40px;height:40px;background:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:22px;box-shadow:0 2px 4px rgba(0,0,0,0.06);flex-shrink:0">🤖</div>
                <div style="flex:1;min-width:0">
                    <div style="font-size:16px;font-weight:700;color:#78350f;line-height:1.2"><?php echo esc_html($heroTitle); ?></div>
                    <div style="font-size:12px;color:#92400e;margin-top:2px;line-height:1.4"><?php echo esc_html($heroSubtitle); ?></div>
                </div>
                <div id="stl-test-quota" style="margin-left:auto;font-size:11px;color:#92400e;background:#fff;padding:4px 10px;border-radius:12px;border:1px solid #fcd34d;white-space:nowrap;flex-shrink:0">
                    <?php echo esc_html(sprintf(/* translators: %d: remaining tests */ __('%d tests left today', 'shop-to-llm'), $heroRemaining)); ?>
                </div>
            </div>
            <div style="display:flex;gap:8px;background:#fff;border-radius:8px;padding:6px;border:1px solid #fcd34d">
                <input type="text" id="stl-hero-search-input" placeholder="<?php echo esc_attr($heroPlaceholder); ?>" style="flex:1;border:none;outline:none;padding:8px 10px;font-size:13px;background:transparent;min-width:0">
                <button type="button" id="stl-hero-search-go" style="padding:8px 18px;background:#78350f;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap"><?php echo esc_html__('Ask AI', 'shop-to-llm'); ?> →</button>
            </div>
            <div id="stl-hero-results" style="display:none;margin-top:12px"></div>
            <div id="stl-hero-loading" style="display:none;text-align:center;padding:20px;color:#92400e;font-size:13px">
                <span style="display:inline-block;animation:stl-spin 1s linear infinite;border:2px solid #fcd34d;border-top-color:transparent;border-radius:50%;width:16px;height:16px;vertical-align:middle;margin-right:8px"></span>
                <?php echo esc_html__('Asking AI about your store...', 'shop-to-llm'); ?>
            </div>
            <div style="font-size:10px;color:#92400e;margin-top:8px;opacity:0.8">
                <?php echo esc_html__('Powered by FinCore cloud · AI by DeepSeek · 5 free tests per day', 'shop-to-llm'); ?>
            </div>
        </div>
        <style>
        @keyframes stl-spin { to { transform: rotate(360deg); } }
        .stl-ai-bubble { background:#fff; border-radius:12px; padding:14px 16px; border:1px solid #fcd34d; box-shadow:0 2px 6px rgba(120,53,15,0.06); }
        .stl-ai-bubble-header { display:flex; align-items:center; gap:8px; margin-bottom:8px; font-size:12px; color:#92400e; font-weight:600; }
        .stl-ai-bubble-text { font-size:13px; color:#1e1e1e; line-height:1.6; white-space:pre-wrap; word-wrap:break-word; }
        .stl-product-card { display:flex; gap:10px; padding:10px; background:#fff; border:1px solid #e2e8f0; border-radius:8px; margin-top:8px; transition:transform 0.15s, box-shadow 0.15s; }
        .stl-product-card:hover { transform:translateY(-1px); box-shadow:0 3px 8px rgba(0,0,0,0.06); }
        .stl-product-thumb { width:48px; height:48px; border-radius:6px; object-fit:cover; background:#f1f5f9; flex-shrink:0; }
        .stl-product-thumb-placeholder { width:48px; height:48px; border-radius:6px; background:#f1f5f9; flex-shrink:0; display:flex; align-items:center; justify-content:center; font-size:20px; }
        .stl-product-info { flex:1; min-width:0; }
        .stl-product-name { font-size:13px; font-weight:600; color:#1e1e1e; margin:0 0 2px; word-break:break-word; }
        .stl-product-meta { font-size:11px; color:#64748b; display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
        .stl-product-price { color:#16a34a; font-weight:600; }
        .stl-product-stock-in { color:#16a34a; }
        .stl-product-stock-out { color:#dc2626; }
        .stl-product-rating { color:#f59e0b; }
        .stl-product-desc { font-size:11px; color:#64748b; margin-top:4px; line-height:1.4; }
        </style>
        <script>
        (function(){
            var heroInput = document.getElementById('stl-hero-search-input');
            var heroGoBtn = document.getElementById('stl-hero-search-go');
            var heroResults = document.getElementById('stl-hero-results');
            var heroLoading = document.getElementById('stl-hero-loading');
            var quotaDisplay = document.getElementById('stl-test-quota');
            if (!heroInput || !heroGoBtn) return;

            function escapeHtml(str){
                if (str === null || str === undefined) return '';
                return String(str)
                    .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
                    .replace(/"/g,'&quot;').replace(/'/g,'&#039;');
            }

            function updateQuotaDisplay(remaining){
                if (!quotaDisplay || remaining === undefined || remaining === null) return;
                quotaDisplay.textContent = remaining + ' <?php echo esc_js(__('tests left today', 'shop-to-llm')); ?>';
                if (remaining === 0) {
                    quotaDisplay.style.background = '#fee2e2';
                    quotaDisplay.style.borderColor = '#fecaca';
                    quotaDisplay.style.color = '#991b1b';
                }
            }

            function runHeroSearch(){
                var q = (heroInput.value || '').trim();
                if (!q) { heroInput.focus(); return; }

                heroResults.style.display = 'none';
                heroLoading.style.display = 'block';
                heroGoBtn.disabled = true;
                heroGoBtn.style.opacity = '0.7';

                var fd = new FormData();
                fd.append('action', 'shop2llm_test_search');
                fd.append('query', q);
                fd.append('nonce', '<?php echo esc_js(wp_create_nonce('shop2llm_admin_nonce')); ?>');

                fetch('<?php echo esc_js(admin_url('admin-ajax.php')); ?>', {method:'POST', body:fd})
                    .then(function(r){ return r.json(); })
                    .then(function(d){
                        heroLoading.style.display = 'none';
                        heroGoBtn.disabled = false;
                        heroGoBtn.style.opacity = '1';

                        var data = (d && d.data) || {};
                        if (d && d.success) {
                            updateQuotaDisplay(data.remaining_quota);
                        }

                        if (!d || !d.success) {
                            var errMsg = (data.error) ? data.error : '<?php echo esc_js(__('Search failed. Please try again.', 'shop-to-llm')); ?>';
                            heroResults.innerHTML = '<div class="stl-ai-bubble"><div class="stl-ai-bubble-text" style="color:#dc2626">' + escapeHtml(errMsg) + '</div></div>';
                            heroResults.style.display = 'block';
                            return;
                        }

                        var products = data.products || [];
                        var aiReply = data.ai_reply || '';
                        var html = '';

                        if (aiReply) {
                            html += '<div class="stl-ai-bubble">';
                            html += '<div class="stl-ai-bubble-header"><span style="font-size:16px">🤖</span> <?php echo esc_js(__('AI-powered preview', 'shop-to-llm')); ?></div>';
                            html += '<div class="stl-ai-bubble-text">' + escapeHtml(aiReply) + '</div>';
                            html += '</div>';
                        }

                        if (products.length) {
                            html += '<div style="font-size:11px;font-weight:600;color:#92400e;margin:10px 0 4px">' + products.length + ' <?php echo esc_js(__('matching items found:', 'shop-to-llm')); ?></div>';
                            products.forEach(function(p){
                                html += '<div class="stl-product-card">';
                                if (p.image_url) {
                                    html += '<img class="stl-product-thumb" src="' + escapeHtml(p.image_url) + '" alt="" loading="lazy" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'">';
                                    html += '<div class="stl-product-thumb-placeholder" style="display:none">📦</div>';
                                } else {
                                    html += '<div class="stl-product-thumb-placeholder">📦</div>';
                                }
                                html += '<div class="stl-product-info">';
                                html += '<div class="stl-product-name">' + escapeHtml(p.name) + '</div>';
                                html += '<div class="stl-product-meta">';
                                if (p.price) {
                                    var priceText = String(p.price);
                                    if (p.currency) priceText = p.currency + ' ' + priceText;
                                    html += '<span class="stl-product-price">' + escapeHtml(priceText) + '</span>';
                                }
                                html += p.in_stock ? '<span class="stl-product-stock-in">● <?php echo esc_js(__('In Stock', 'shop-to-llm')); ?></span>' : '<span class="stl-product-stock-out">● <?php echo esc_js(__('Out of Stock', 'shop-to-llm')); ?></span>';
                                if (p.rating && p.rating > 0) {
                                    html += '<span class="stl-product-rating">★ ' + (Math.round(p.rating * 10) / 10) + '</span>';
                                }
                                html += '</div>';
                                if (p.short_description) {
                                    var desc = p.short_description.length > 120 ? p.short_description.substring(0, 120) + '...' : p.short_description;
                                    html += '<div class="stl-product-desc">' + escapeHtml(desc) + '</div>';
                                }
                                html += '</div>';
                                if (p.url) {
                                    html += '<a href="' + escapeHtml(p.url) + '" target="_blank" rel="noopener" style="font-size:11px;color:#78350f;text-decoration:none;align-self:center;white-space:nowrap"><?php echo esc_js(__('View', 'shop-to-llm')); ?> →</a>';
                                }
                                html += '</div>';
                            });
                        } else if (!aiReply) {
                            html += '<div class="stl-ai-bubble"><div class="stl-ai-bubble-text" style="color:#d97706"><?php echo esc_js(__('No matching items found. Try a different query.', 'shop-to-llm')); ?></div></div>';
                        }

                        heroResults.innerHTML = html;
                        heroResults.style.display = 'block';
                        if (heroResults.scrollIntoView) {
                            heroResults.scrollIntoView({behavior:'smooth', block:'nearest'});
                        }
                    })
                    .catch(function(){
                        heroLoading.style.display = 'none';
                        heroGoBtn.disabled = false;
                        heroGoBtn.style.opacity = '1';
                        heroResults.innerHTML = '<div class="stl-ai-bubble"><div class="stl-ai-bubble-text" style="color:#dc2626"><?php echo esc_js(__('Network error. Please try again.', 'shop-to-llm')); ?></div></div>';
                        heroResults.style.display = 'block';
                    });
            }

            heroGoBtn.addEventListener('click', runHeroSearch);
            heroInput.addEventListener('keydown', function(e){
                if (e.key === 'Enter' || e.keyCode === 13) {
                    e.preventDefault();
                    runHeroSearch();
                }
            });

            // Expose globally so the MCP score-card "Test Search" button can redirect here
            window.shop2llmFocusHeroSearch = function(query){
                if (query) heroInput.value = query;
                var heroBox = document.getElementById('stl-hero-test-search');
                if (heroBox && heroBox.scrollIntoView) {
                    heroBox.scrollIntoView({behavior:'smooth', block:'start'});
                }
                setTimeout(function(){ heroInput.focus(); }, 400);
            };
        })();
        </script>

        <!-- AI Visibility Score Breakdown (7 factors — Dashboard top, first card) -->
        <?php
        // Use canonical 7-factor visibility score — same as admin bar
        $pubCount = shop_to_llm_has_woocommerce()
            ? (int)(wp_count_posts('product')->publish ?? 0)
            : (int)(wp_count_posts('post')->publish ?? 0);

        $visScore = shop_to_llm_get_visibility_score();
        $totalScore = $visScore['total'];
        $freeMax = $visScore['free_max'];
        $scoreLabel = $visScore['label'];
        $scoreColor = $visScore['color'];
        $scoreBg = $visScore['bg'];
        $scoreRobots = $visScore['factors']['robots'];
        $scoreLlms = $visScore['factors']['llms'];
        $scoreSchema = $visScore['factors']['schema'];
        $scoreMcp = $visScore['factors']['mcp'];
        $scoreFeed = $visScore['factors']['feed'];
        $scoreQueries = $visScore['factors']['queries'];
        $scoreCrawlers = $visScore['factors']['crawlers'];
        $schemaHasConflict = $visScore['schema_has_conflict'];
        $dashboardScoreTitle = __('AI Score', 'shop-to-llm');

        // Pro-aligned: pass = score >= max * 0.7
        $scoreItems = [
            [
                'id' => 'robots', 'label' => __('Robots.txt', 'shop-to-llm'), 'score' => $scoreRobots, 'max' => 20,
                'pass' => $scoreRobots >= 14,
                'hint' => $scoreRobots >= 14 ? __('AI crawlers whitelisted — auto-configured', 'shop-to-llm') : __('AI crawlers blocked — cannot index your content', 'shop-to-llm'),
                'fix_action' => $scoreRobots < 14 ? 'fix_robots' : '', 'fix_label' => __('Fix robots.txt', 'shop-to-llm'),
            ],
            [
                'id' => 'llms', 'label' => __('Llms.txt', 'shop-to-llm'), 'score' => $scoreLlms, 'max' => 20,
                'pass' => $scoreLlms >= 14,
                'hint' => $scoreLlms >= 14 ? __('llms.txt accessible — AI can read your catalog', 'shop-to-llm') : __('llms.txt not accessible — flush permalinks or check caching', 'shop-to-llm'),
                'fix_action' => $scoreLlms < 14 ? 'flush_rewrite' : '', 'fix_label' => __('Flush Permalinks', 'shop-to-llm'),
            ],
            [
                'id' => 'schema', 'label' => __('Schema', 'shop-to-llm'), 'score' => $scoreSchema, 'max' => 15,
                'pass' => $scoreSchema >= 11,
                'hint' => $scoreSchema >= 11 ? __('JSON-LD schema active — full product markup', 'shop-to-llm')
                    : ($schemaHasConflict ? __('SEO plugin conflict — schema may be duplicated', 'shop-to-llm') : __('Missing schema — improve product descriptions and images', 'shop-to-llm')),
                'fix_action' => !$schemaHasConflict && $scoreSchema < 11 ? 'edit_products' : ($schemaHasConflict ? 'check_settings' : ''), 'fix_label' => $schemaHasConflict ? __('Check Settings', 'shop-to-llm') : __('Improve Products', 'shop-to-llm'),
            ],
            [
                'id' => 'mcp', 'label' => __('MCP', 'shop-to-llm'), 'score' => $scoreMcp, 'max' => 15,
                'pass' => $scoreMcp >= 11,
                'hint' => $scoreMcp >= 11 ? sprintf(/* translators: %d: number of API calls */ __('%d API calls — ChatGPT connected and active', 'shop-to-llm'), $todayCount)
                    : ($scoreMcp >= 5 ? __('Enabled but not tested — try the test search below', 'shop-to-llm') : __('MCP endpoint disabled — enable in Settings', 'shop-to-llm')),
                'fix_action' => $scoreMcp >= 5 ? 'test_mcp' : 'settings', 'fix_label' => $scoreMcp >= 5 ? __('Test Search', 'shop-to-llm') : __('Enable MCP', 'shop-to-llm'),
            ],
            [
                'id' => 'feed', 'label' => __('Feed', 'shop-to-llm'), 'score' => $scoreFeed, 'max' => 10,
                'pass' => $scoreFeed >= 7,
                'hint' => $scoreFeed >= 7 ? (shop_to_llm_has_woocommerce() ? __('Product Feed XML accessible — AI shopping platforms indexed', 'shop-to-llm') : __('Content feed ready — no WooCommerce needed', 'shop-to-llm'))
                    : __('Product Feed XML not accessible — regenerate rewrite rules', 'shop-to-llm'),
                'fix_action' => $scoreFeed < 7 ? 'flush_rewrite' : '', 'fix_label' => __('Flush Permalinks', 'shop-to-llm'),
            ],
            [
                'id' => 'queries', 'label' => __('Queries', 'shop-to-llm'), 'score' => $scoreQueries, 'max' => 10,
                'pass' => $scoreQueries >= 7,
                'hint' => $scoreQueries >= 7 ? sprintf(/* translators: %d: percentage of content with Focus Queries */ __('%d%% of content has Focus Queries', 'shop-to-llm'), round(($scoreQueries / 10) * 100)) : __('Set Focus Queries on posts/products to boost AI relevance', 'shop-to-llm'),
                'fix_action' => $scoreQueries < 7 ? 'edit_posts' : '', 'fix_label' => __('Edit Content', 'shop-to-llm'),
            ],
            [
                'id' => 'crawlers', 'label' => __('Crawlers', 'shop-to-llm'), 'score' => $scoreCrawlers, 'max' => 10,
                'pass' => $scoreCrawlers >= 7,
                'hint' => $scoreCrawlers >= 7 ? __('AI crawlers actively visiting — good visibility', 'shop-to-llm') : ($scoreCrawlers >= 5 ? __('Some AI crawlers detected — keep publishing fresh content', 'shop-to-llm') : __('No AI crawler visits yet — ensure robots.txt allows AI bots', 'shop-to-llm')),
                'fix_action' => $scoreCrawlers < 7 ? 'fix_robots' : '', 'fix_label' => __('Fix robots.txt', 'shop-to-llm'),
            ],
        ];

        // Badges (merged from Growth Progress)
        $badges = [
            ['icon' => '🛡️', 'name' => __('AI-Ready', 'shop-to-llm'), 'earned' => true, 'hint' => __('Schema + llms.txt active', 'shop-to-llm')],
            ['icon' => '📦', 'name' => __('Indexed', 'shop-to-llm'), 'earned' => $pubCount > 0, 'hint' => sprintf(/* translators: %d: number of discoverable items */ __('%d items discoverable', 'shop-to-llm'), $pubCount)],
            ['icon' => '🔗', 'name' => __('Connected', 'shop-to-llm'), 'earned' => $todayCount > 0, 'hint' => $todayCount > 0 ? sprintf(/* translators: %d: number of API calls today */ __('%d API calls today', 'shop-to-llm'), $todayCount) : __('Connect ChatGPT to earn', 'shop-to-llm')],
            ['icon' => '🔍', 'name' => __('Discovered', 'shop-to-llm'), 'earned' => $crawlerCount > 0, 'hint' => $crawlerCount > 0 ? sprintf(/* translators: %d: number of crawler visits */ __('%d crawler visits', 'shop-to-llm'), $crawlerCount) : __('AI crawlers indexing', 'shop-to-llm')],
        ];
        $earnedBadges = count(array_filter($badges, fn($b) => $b['earned']));
        ?>
        <div class="stl-card" id="stl-score-breakdown" style="border-left:4px solid <?php echo esc_attr($scoreColor); ?>">
            <h2 style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">
                <span style="display:flex;align-items:center;gap:6px">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" width="20" height="20"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
                    <?php echo esc_html($dashboardScoreTitle); ?><span class="s2ll-help" title="<?php echo esc_attr__('7-factor breakdown of how discoverable your site is by AI. Each item shows exactly what to fix.', 'shop-to-llm'); ?>"></span>
                </span>
                <span style="font-size:22px;font-weight:700;color:<?php echo esc_attr($scoreColor); ?>"><?php echo esc_html((string)$totalScore); ?><span style="font-size:12px;font-weight:400;color:#94a3b8">/100</span></span>
            </h2>

            <!-- Label badge + progress bar -->
            <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
                <span style="display:inline-block;padding:2px 10px;background:<?php echo esc_attr($scoreBg); ?>;color:<?php echo esc_attr($scoreColor); ?>;border-radius:10px;font-size:11px;font-weight:600"><?php echo esc_html($scoreLabel); ?></span>
            </div>
            <div style="height:6px;background:#e2e8f0;border-radius:3px;margin-bottom:4px">
                <div style="width:<?php echo esc_attr((string)$totalScore); ?>%;height:100%;background:<?php echo esc_attr($scoreColor); ?>;border-radius:3px;transition:width 0.5s"></div>
            </div>

            <!-- Badges row (merged from Growth Progress) -->
            <div style="display:flex;gap:8px;flex-wrap:wrap;margin:6px 0 10px">
                <?php foreach ($badges as $b) : ?>
                <div title="<?php echo esc_attr($b['hint']); ?>" style="display:flex;align-items:center;gap:4px;padding:3px 8px;border-radius:20px;border:1px solid <?php echo $b['earned'] ? '#c7d2fe' : '#e2e8f0'; ?>;background:<?php echo $b['earned'] ? '#f5f3ff' : '#f8fafc'; ?>;font-size:10px;<?php echo $b['earned'] ? '' : 'filter:grayscale(1);opacity:0.5;'; ?>">
                    <span style="font-size:11px"><?php echo esc_html($b['icon']); ?></span>
                    <span style="color:<?php echo $b['earned'] ? '#6366f1' : '#94a3b8'; ?>;font-weight:<?php echo $b['earned'] ? '600' : '400'; ?>"><?php echo esc_html($b['name']); ?></span>
                    <?php if (!$b['earned']) : ?><span style="color:#94a3b8;font-size:8px">🔒</span><?php endif; ?>
                </div>
                <?php endforeach; ?>
            </div>

            <!-- Score Items -->
            <div style="display:flex;flex-direction:column;gap:4px;margin-bottom:10px">
                <?php foreach ($scoreItems as $item) :
                    $passColor = $item['pass'] ? '#22c55e' : ($item['score'] >= ($item['max']*0.5) ? '#eab308' : '#ef4444');
                    $rowBg = $item['pass'] ? '#f0fdf4' : ($item['score'] >= ($item['max']*0.5) ? '#fefce8' : '#fef2f2');
                    $fixUrl = '';
                    if (!empty($item['fix_action']) && $item['fix_action'] === 'fix_robots') $fixUrl = wp_nonce_url(admin_url('admin.php?page=shop2llm&shop2llm_fix_robots=1'), 'shop2llm_fix_robots');
                    elseif (!empty($item['fix_action']) && $item['fix_action'] === 'flush_rewrite') $fixUrl = admin_url('options-permalink.php');
                    elseif (!empty($item['fix_action']) && $item['fix_action'] === 'edit_products') $fixUrl = admin_url('edit.php?post_type=product');
                    elseif (!empty($item['fix_action']) && $item['fix_action'] === 'edit_posts') $fixUrl = admin_url('edit.php');
                    elseif (!empty($item['fix_action']) && $item['fix_action'] === 'check_settings') $fixUrl = admin_url('admin.php?page=shop2llm');
                    elseif (!empty($item['fix_action']) && $item['fix_action'] === 'settings') $fixUrl = admin_url('admin.php?page=shop2llm');
                ?>
                <div class="stl-score-row" style="display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;background:<?php echo esc_attr($rowBg); ?>;font-size:11px">
                    <span style="flex-shrink:0;width:16px;text-align:center;font-size:12px;color:<?php echo esc_attr($passColor); ?>"><?php echo $item['pass'] ? '✓' : '✗'; ?></span>
                    <span style="flex:1;min-width:0;font-weight:600;color:#1e1e1e;white-space:nowrap"><?php echo esc_html($item['label']); ?></span>
                    <span style="font-size:10px;color:#94a3b8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:160px" title="<?php echo esc_attr($item['hint']); ?>"><?php echo esc_html($item['hint']); ?></span>
                    <span style="flex-shrink:0;font-weight:700;color:<?php echo esc_attr($passColor); ?>;font-size:12px;min-width:40px;text-align:right"><?php echo esc_html((string)$item['score']); ?><span style="font-weight:400;color:#94a3b8;font-size:9px">/<?php echo esc_html((string)$item['max']); ?></span></span>
                    <?php if (!$item['pass'] && !empty($item['fix_action'])) : ?>
                        <?php if ($item['fix_action'] === 'test_mcp') : ?>
                        <button type="button" class="stl-test-search-btn" style="flex-shrink:0;padding:2px 8px;background:<?php echo esc_attr($scoreColor); ?>;color:#fff;border:none;border-radius:4px;font-size:9px;font-weight:600;cursor:pointer"><?php echo esc_html($item['fix_label']); ?> →</button>
                        <?php elseif ($fixUrl) : ?>
                        <a href="<?php echo esc_url($fixUrl); ?>" class="stl-fix-link" data-fix-action="<?php echo esc_attr($item['fix_action']); ?>" style="flex-shrink:0;padding:2px 8px;background:<?php echo esc_attr($scoreColor); ?>;color:#fff;border-radius:4px;font-size:9px;font-weight:600;text-decoration:none"><?php echo esc_html($item['fix_label']); ?> →</a>
                        <?php endif; ?>
                    <?php endif; ?>
                </div>
                <?php endforeach; ?>
            </div>

            <!-- Community Trust (async loaded) -->
            <div id="stl-community-trust" style="margin:8px 0;padding:5px 10px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:6px;text-align:center;display:none">
                <span id="stl-community-text" style="font-size:10px;color:#166534;font-weight:600"></span>
            </div>

            <div style="display:flex;gap:8px;margin-top:10px">
                <?php if ($totalScore < $freeMax) : ?>
                <a href="#" id="stl-fix-all-btn" style="flex:1;text-align:center;padding:6px 0;background:<?php echo esc_attr($scoreColor); ?>;color:#fff;border-radius:6px;font-size:11px;font-weight:600;text-decoration:none"><?php echo esc_html__('Fix All Items', 'shop-to-llm'); ?> →</a>
                <?php endif; ?>
            </div>
        </div>

        <!-- v1.1.1: Test Search redirect + Fix All JS -->
        <script>
        (function(){
            // v1.1.1: Score-card "Test Search" button now scrolls to the Hero search box at the top
            var testSearchBtns = document.querySelectorAll('.stl-test-search-btn');
            if (testSearchBtns.length) {
                testSearchBtns.forEach(function(btn){
                    btn.addEventListener('click', function(){
                        if (window.shop2llmFocusHeroSearch) {
                            window.shop2llmFocusHeroSearch();
                        }
                    });
                });
            }
            var fixAllBtn = document.getElementById('stl-fix-all-btn');
            if (fixAllBtn) {
                fixAllBtn.addEventListener('click', function(e){
                    e.preventDefault();
                    fixAllBtn.disabled = true;
                    fixAllBtn.style.opacity = '0.7';

                    // Collect all fixable rows, track manual items separately
                    var allRows = document.querySelectorAll('.stl-score-row');
                    var fixQueue = [];
                    var manualItems = [];
                    allRows.forEach(function(row){
                        var btn = row.querySelector('.stl-test-search-btn');
                        var link = row.querySelector('.stl-fix-link');
                        if (!btn && !link) return; // nothing to fix
                        var action = link ? link.getAttribute('data-fix-action') : 'test_mcp';
                        var label = row.querySelector('span:nth-child(2)') ? row.querySelector('span:nth-child(2)').textContent.trim() : '';
                        // Actions that can be auto-fixed via AJAX
                        var autoFixable = ['test_mcp', 'fix_robots', 'flush_rewrite'];
                        if (autoFixable.indexOf(action) === -1) {
                            manualItems.push({row: row, label: label, action: action, link: link});
                            return;
                        }
                        fixQueue.push({row: row, btn: btn, link: link, action: action});
                    });

                    if (fixQueue.length === 0 && manualItems.length === 0) {
                        fixAllBtn.textContent = 'All items OK ✓';
                        fixAllBtn.disabled = false;
                        fixAllBtn.style.opacity = '1';
                        return;
                    }

                    var currentIdx = 0;
                    var nonce = '<?php echo esc_js(wp_create_nonce('shop2llm_admin_nonce')); ?>';
                    var ajaxurl = '<?php echo esc_js(admin_url('admin-ajax.php')); ?>';

                    function processNext() {
                        if (currentIdx >= fixQueue.length) {
                            // Build completion message
                            var msg = 'Auto-fixed ' + fixQueue.length + ' item(s)';
                            if (manualItems.length > 0) {
                                msg += ' · ' + manualItems.length + ' need manual action →';
                            } else {
                                msg += ' ✓';
                            }
                            fixAllBtn.textContent = msg;
                            fixAllBtn.style.opacity = '1';
                            fixAllBtn.style.background = manualItems.length > 0 ? '#eab308' : '#16a34a';
                            fixAllBtn.disabled = false;

                            // Highlight manual items with a pulse
                            if (manualItems.length > 0) {
                                manualItems.forEach(function(m){
                                    var origBg = m.row.style.background;
                                    m.row.style.transition = 'background 0.4s';
                                    m.row.style.background = '#fef3c7';
                                    setTimeout(function(){ m.row.style.background = origBg; }, 2000);
                                    // Scroll to first manual item
                                    if (manualItems[0] === m) {
                                        setTimeout(function(){ m.row.scrollIntoView({behavior:'smooth', block:'center'}); }, 300);
                                    }
                                });
                            }
                            return;
                        }

                        var item = fixQueue[currentIdx];
                        fixAllBtn.textContent = 'Fixing ' + (currentIdx + 1) + '/' + fixQueue.length + '...';

                        // Scroll to row
                        item.row.scrollIntoView({behavior: 'smooth', block: 'center'});

                        // Flash highlight
                        var origBg = item.row.style.background;
                        item.row.style.transition = 'background 0.3s';
                        item.row.style.background = '#dbeafe';

                        function markDone(success) {
                            item.row.style.background = success ? '#f0fdf4' : origBg;
                            // Update checkmark: find the first span (icon)
                            var iconSpan = item.row.querySelector('span');
                            if (iconSpan && success) {
                                iconSpan.textContent = '✓';
                                iconSpan.style.color = '#22c55e';
                            }
                            currentIdx++;
                            setTimeout(processNext, 350);
                        }

                        if (item.action === 'test_mcp') {
                            // v1.1.1: Scroll to Hero Test Search box at dashboard top
                            if (window.shop2llmFocusHeroSearch) {
                                window.shop2llmFocusHeroSearch();
                            }
                            fixAllBtn.textContent = '✓ Test search opened';
                            setTimeout(function(){ markDone(true); }, 600);
                        } else if (item.action === 'fix_robots' || item.action === 'flush_rewrite') {
                            // Call AJAX to fix
                            var fd = new FormData();
                            fd.append('action', 'shop2llm_fix_item');
                            fd.append('fix_action', item.action);
                            fd.append('nonce', nonce);
                            fetch(ajaxurl, {method:'POST', body: fd})
                            .then(function(r){ return r.json(); })
                            .then(function(d){
                                if (d.success) {
                                    fixAllBtn.textContent = '✓ Fixed: ' + (item.action === 'fix_robots' ? 'Robots.txt' : 'Permalinks');
                                    markDone(true);
                                } else {
                                    fixAllBtn.textContent = '✗ Failed';
                                    markDone(false);
                                }
                            })
                            .catch(function(){
                                fixAllBtn.textContent = '✗ Error';
                                markDone(false);
                            });
                        }
                    }

                    processNext();
                });
            }
        })();
        </script>

        <!-- AI Visibility Summary -->
        <div class="stl-card" style="border-left:4px solid #10b981">
          <h2><?php echo esc_html__('AI Visibility Summary', 'shop-to-llm'); ?><span class="s2ll-help" title="<?php echo esc_attr__("Today's AI activity at a glance: API calls, crawler bots, searches, and tools available.", 'shop-to-llm'); ?>"></span></h2>
          <!-- Status badge row -->
          <div style="margin-bottom:10px">
            <?php if ($enabled === 'yes'): ?>
              <span class="stl-badge stl-badge-ok"><?php echo esc_html__('Connected', 'shop-to-llm'); ?></span>
              <span style="font-size:11px;color:#94a3b8;margin-left:4px"><?php echo esc_html($storeName); ?></span>
            <?php else: ?>
              <span class="stl-badge stl-badge-off"><?php echo esc_html__('Disabled', 'shop-to-llm'); ?></span>
            <?php endif; ?>
          </div>
          <!-- 4 metric boxes -->
          <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px">
            <div style="text-align:center;padding:10px 6px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px">
              <div style="font-size:22px;font-weight:700;color:#166534"><?php echo esc_html($todayCount); ?></div>
              <div style="font-size:10px;color:#64748b;margin-top:2px"><?php echo esc_html__('API Calls', 'shop-to-llm'); ?></div>
            </div>
            <div style="text-align:center;padding:10px 6px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px">
              <div style="font-size:22px;font-weight:700;color:#6366f1"><?php echo esc_html($crawlerCount); ?></div>
              <div style="font-size:10px;color:#64748b;margin-top:2px"><?php echo esc_html__('AI Bots', 'shop-to-llm'); ?></div>
            </div>
            <div style="text-align:center;padding:10px 6px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px">
              <div style="font-size:22px;font-weight:700;color:#d97706"><?php echo esc_html($searchCount); ?></div>
              <div style="font-size:10px;color:#64748b;margin-top:2px"><?php echo esc_html__('Searches', 'shop-to-llm'); ?></div>
            </div>
            <div style="text-align:center;padding:10px 6px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px">
              <div style="font-size:22px;font-weight:700;color:#7c3aed">2</div>
              <div style="font-size:10px;color:#64748b;margin-top:2px"><?php echo esc_html__('AI Tools', 'shop-to-llm'); ?></div>
            </div>
          </div>
          <?php if ($todayCount === 0 && $crawlerCount === 0): ?>
          <div style="margin-top:8px;padding:6px 10px;background:#eef2ff;border:1px solid #c7d2fe;border-radius:6px;text-align:center;font-size:11px;color:#6366f1;font-weight:500">
            <?php echo shop_to_llm_has_woocommerce() ? esc_html__('👋 New here? Go to Settings → Connection Config → paste into ChatGPT', 'shop-to-llm') : esc_html__('👋 New here? Go to Settings → Connection Config → paste into ChatGPT', 'shop-to-llm'); ?>
          </div>
          <?php endif; ?>
          <div style="margin-top:10px;padding:10px 14px;background:#f0f9ff;border:1px solid #bae6fd;border-radius:8px;font-size:12px;line-height:1.7;color:#0c4a6e">
            <strong style="display:block;margin-bottom:4px;color:#0369a1"><?php echo esc_html__('💡 How to read this', 'shop-to-llm'); ?></strong>
            <?php if ($crawlerCount > 0 || $todayCount > 0) : ?>
              <?php echo esc_html__('Each API call = an AI assistant connected to your site.', 'shop-to-llm'); ?><br>
              <?php echo esc_html__('Each crawler visit = an AI search engine indexed your content.', 'shop-to-llm'); ?><br>
              <?php echo esc_html__('More activity → better AI visibility → more organic traffic.', 'shop-to-llm'); ?>
            <?php else : ?>
              <?php echo shop_to_llm_has_woocommerce() ? esc_html__('AI activity will appear here as ChatGPT and other AI assistants discover your store. Copy your connection config from the Settings tab and paste into ChatGPT to get started.', 'shop-to-llm') : esc_html__('AI activity will appear here as ChatGPT and other AI assistants discover your site. Copy your connection config from the Settings tab and paste into ChatGPT to get started.', 'shop-to-llm'); ?>
            <?php endif; ?>
          </div>
          <p class="s2ll-suggestion">💡 <?php echo esc_html__('Check the Analytics tab for detailed trends and top search terms', 'shop-to-llm'); ?></p>
        </div>

        <!-- AI-Driven Traffic -->
        <div class="stl-card" style="border-left:4px solid #10b981">
            <h2 style="display:flex;align-items:center;gap:8px;margin-bottom:12px">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" width="20" height="20"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
                <?php echo esc_html__('AI-Driven Traffic', 'shop-to-llm'); ?><span class="s2ll-help" title="<?php echo esc_attr__('Visitors who found your site through AI platforms like ChatGPT, Perplexity, and Copilot.', 'shop-to-llm'); ?>"></span>
            </h2>
            <?php shop_to_llm_render_ai_referral_section(); ?>
        </div>

        </div><!-- /tab-dashboard -->

        <!-- Analytics Tab -->
        <div class="stl-tab-panel" id="tab-analytics">
        <!-- AI Readiness Score (unified: 9-item product model for commerce, 25-item model for content) -->
        <?php
        $isCommerce = ($siteMode === 'commerce' && shop_to_llm_has_woocommerce());
        $scannedPost = null;
        $scannedProduct = null;
        $scoreResults = [];
        $totalPostScore = 0;
        $dimensions = [];
        $searchInput = '';
        $selectedPostId = isset($_POST['stl_post_select']) ? (int)$_POST['stl_post_select'] : 0;
        $isContentSearch = isset($_POST['stl_content_search']) && !empty($_POST['stl_search_input']);

        // Handle search/dropdown selection
        $triggerLookup = false;
        if ($isCommerce && ((isset($_POST['shop_to_llm_sku_lookup']) && !empty($_POST['stl_search_input'])) || $selectedPostId > 0)) {
            if ($selectedPostId > 0) {
                $searchInput = (string)$selectedPostId;
            } else {
                $searchInput = sanitize_text_field(wp_unslash($_POST['stl_search_input']));
            }
            $triggerLookup = true;
        } elseif (!$isCommerce && ($selectedPostId > 0 || $isContentSearch)) {
            if ($selectedPostId > 0) {
                $triggerLookup = true;
                $searchInput = (string)$selectedPostId;
            } elseif ($isContentSearch) {
                $searchInput = sanitize_text_field(wp_unslash($_POST['stl_search_input']));
                $triggerLookup = true;
            }
        }

        if ($triggerLookup) {
            if ($isCommerce) {
                // Commerce: search products
                if ($selectedPostId > 0) {
                    // Get by ID directly (from dropdown)
                    $product = wc_get_product($selectedPostId);
                    $products = $product ? [$product] : [];
                } else {
                    $products = wc_get_products(['sku' => $searchInput, 'limit' => 1]);
                    if (empty($products)) $products = wc_get_products(['s' => $searchInput, 'limit' => 1]);
                }
                if (!empty($products)) {
                    $scannedProduct = $products[0];
                    $scannedPost = get_post($scannedProduct->get_id());
                    $p = $scannedProduct;
                    $dimensions = [
                        ['name' => __('Product Name', 'shop-to-llm'), 'pass' => strlen($p->get_name()) > 3, 'weight' => 10],
                        ['name' => __('Short Description', 'shop-to-llm'), 'pass' => strlen($p->get_short_description()) > 20, 'weight' => 15],
                        ['name' => __('Full Description', 'shop-to-llm'), 'pass' => strlen($p->get_description()) > 50, 'weight' => 15],
                        ['name' => __('Product Images', 'shop-to-llm'), 'pass' => (int)$p->get_image_id() > 0, 'weight' => 15],
                        ['name' => __('Price Set', 'shop-to-llm'), 'pass' => (float)$p->get_regular_price() > 0, 'weight' => 10],
                        ['name' => __('Categories', 'shop-to-llm'), 'pass' => count(wp_get_post_terms($p->get_id(), 'product_cat')) > 0, 'weight' => 10],
                        ['name' => __('Tags', 'shop-to-llm'), 'pass' => count(wp_get_post_terms($p->get_id(), 'product_tag')) > 0, 'weight' => 5],
                        ['name' => __('Stock Managed', 'shop-to-llm'), 'pass' => $p->get_manage_stock(), 'weight' => 10],
                        ['name' => __('Attributes / Specs', 'shop-to-llm'), 'pass' => count($p->get_attributes()) > 0, 'weight' => 10],
                    ];
                    foreach ($dimensions as $c) {
                        if ($c['pass']) $totalPostScore += $c['weight'];
                    }
                }
            } else {
                // Content: search posts (by ID from dropdown, or title from text)
                $post = null;
                if ($selectedPostId > 0) {
                    $post = get_post($selectedPostId);
                } elseif ($isContentSearch) {
                    $posts = get_posts(['s' => $searchInput, 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 1]);
                    $post = !empty($posts) ? $posts[0] : null;
                }
                if ($post && $post->post_status === 'publish') {
                    $scannedPost = $post;
                    // Run the 25-item model
                    $scoreResults = shop_to_llm_score_single_post($post->ID);
                    $totalPostScore = $scoreResults['total'];
                    if (!empty($scoreResults['groups'])) {
                        $dimensions = $scoreResults['groups'];
                    }
                }
            }
        }

        // Build dropdown options
        $recentItems = [];
        if ($isCommerce) {
            $recentProducts = wc_get_products(['limit' => 50, 'status' => 'publish', 'orderby' => 'date']);
            foreach ($recentProducts as $rp) {
                $recentItems[] = ['id' => $rp->get_id(), 'title' => $rp->get_name()];
            }
            $dropdownLabel = __('Select a product', 'shop-to-llm');
            $searchPlaceholder = __('Or search by name/SKU...', 'shop-to-llm');
            $cardTitle = __('Product AI Score', 'shop-to-llm');
            $cardHelp = __('Check individual product readiness for AI discovery. 9-dimension product model.', 'shop-to-llm');
        } else {
            $recentPosts = get_posts(['post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 50, 'orderby' => 'date']);
            foreach ($recentPosts as $rp) {
                $recentItems[] = ['id' => $rp->ID, 'title' => $rp->post_title];
            }
            $dropdownLabel = __('Select a post', 'shop-to-llm');
            $searchPlaceholder = __('Or search by title...', 'shop-to-llm');
            $cardTitle = __('Content AI Score', 'shop-to-llm');
            $cardHelp = __('Check individual post readiness for AI discovery. 25-dimension 5-group model.', 'shop-to-llm');
        }
        ?>
        <div class="stl-card" id="stl-ai-readiness">
            <h2><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" width="20" height="20"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg><?php echo esc_html($cardTitle); ?><span class="s2ll-help" title="<?php echo esc_attr($cardHelp); ?>"></span></h2>

            <form method="post" style="margin-bottom:12px">
                <input type="hidden" name="stl_tab" value="analytics">
                <div style="display:flex;gap:8px;align-items:center">
                    <?php if (!empty($recentItems)): ?>
                    <select name="stl_post_select" style="border:1px solid #e2e8f0;border-radius:6px;padding:8px 10px;font-size:13px;min-width:180px;max-width:260px" onchange="this.form.querySelector('input[name=stl_search_input]').value='';this.form.submit()">
                        <option value=""><?php echo esc_html($dropdownLabel); ?>...</option>
                        <?php foreach ($recentItems as $ri): ?>
                        <option value="<?php echo esc_attr((string)$ri['id']); ?>" <?php selected($selectedPostId, $ri['id']); ?>><?php echo esc_html(mb_substr($ri['title'], 0, 55)); ?></option>
                        <?php endforeach; ?>
                    </select>
                    <?php endif; ?>
                    <input type="text" name="stl_search_input" value="<?php echo esc_attr($searchInput && !$selectedPostId ? $searchInput : ''); ?>" placeholder="<?php echo esc_attr($searchPlaceholder); ?>" style="flex:1;border:1px solid #e2e8f0;border-radius:6px;padding:8px 12px;font-size:13px" onchange="this.form.querySelector('select').value=''">
                    <button type="submit" name="<?php echo esc_attr($isCommerce ? 'shop_to_llm_sku_lookup' : 'stl_content_search'); ?>" style="padding:8px 16px;background:#6366f1;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer"><?php echo esc_html__('Check', 'shop-to-llm'); ?></button>
                </div>
            </form>

            <?php if ($scannedPost) : ?>
                <?php
                $scoreColor = $totalPostScore >= 70 ? '#16a34a' : ($totalPostScore >= 40 ? '#d97706' : '#dc2626');
                $editUrl = admin_url('post.php?post=' . $scannedPost->ID . '&action=edit');
                ?>
                <div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:14px">
                    <div style="display:flex;align-items:center;gap:14px;margin-bottom:10px">
                        <div style="font-size:26px;font-weight:700;color:<?php echo esc_attr($scoreColor); ?>"><?php echo esc_html((string)$totalPostScore); ?>/100</div>
                        <div>
                            <div style="font-size:13px;font-weight:600;color:#1e1e1e"><?php echo esc_html($scannedPost->post_title); ?></div>
                            <div style="font-size:11px;color:#64748b"><?php echo $isCommerce ? esc_html__('AI readiness score for this product', 'shop-to-llm') : esc_html__('AI readiness score — 25-dimension analysis', 'shop-to-llm'); ?></div>
                        </div>
                        <a href="<?php echo esc_url($editUrl); ?>" target="_blank" style="margin-left:auto;font-size:11px;color:#6366f1;font-weight:500;text-decoration:none;white-space:nowrap"><?php echo esc_html__('Edit →', 'shop-to-llm'); ?></a>
                    </div>

                    <?php if ($isCommerce) : ?>
                        <!-- Commerce: 9-item flat list -->
                        <div style="display:flex;flex-direction:column;gap:4px">
                            <?php foreach ($dimensions as $dim) : ?>
                            <div style="display:flex;align-items:center;gap:8px;font-size:12px;padding:3px 0">
                                <span style="font-size:10px;min-width:16px;text-align:center;padding:1px 3px;border-radius:2px;<?php echo $dim['pass'] ? 'background:#dcfce7;color:#16a34a' : 'background:#fef2f2;color:#dc2626'; ?>"><?php echo $dim['pass'] ? '✓' : '✗'; ?></span>
                                <span style="color:<?php echo $dim['pass'] ? '#1e1e1e' : '#94a3b8'; ?>"><?php echo esc_html($dim['name']); ?></span>
                                <span style="margin-left:auto;color:#94a3b8;font-size:10px">+<?php echo esc_html((string)$dim['weight']); ?></span>
                            </div>
                            <?php endforeach; ?>
                        </div>
                    <?php else : ?>
                        <!-- Content: 25-item grouped by L1-L5 -->
                        <?php
                        $groups = [
                            'L1' => ['label' => __('Discoverability', 'shop-to-llm'), 'max' => 30, 'color' => '#6366f1', 'items' => []],
                            'L2' => ['label' => __('Relevance', 'shop-to-llm'), 'max' => 25, 'color' => '#8b5cf6', 'items' => [], 'locked' => empty($scoreResults['has_focus'])],
                            'L3' => ['label' => __('Content Quality', 'shop-to-llm'), 'max' => 25, 'color' => '#10b981', 'items' => []],
                            'L4' => ['label' => __('Entity Authority', 'shop-to-llm'), 'max' => 15, 'color' => '#f59e0b', 'items' => [], 'locked' => empty($scoreResults['has_focus'])],
                            'L5' => ['label' => __('Activity', 'shop-to-llm'), 'max' => 5, 'color' => '#ec4899', 'items' => []],
                        ];
                        // Distribute dimensions into groups
                        $allItems = $scoreResults['items'] ?? [];
                        $groupBoundaries = [6, 12, 18, 21, 25]; // L1=1-6, L2=7-12, L3=13-18, L4=19-21, L5=22-25
                        $gi = 0;
                        $groupKeys = array_keys($groups);
                        foreach ($allItems as $idx => $item) {
                            while ($gi < 4 && $idx >= $groupBoundaries[$gi]) $gi++;
                            $gkey = $groupKeys[$gi] ?? 'L1';
                            $groups[$gkey]['items'][] = $item;
                        }
                        // Calculate group scores
                        foreach ($groups as $gkey => &$g) {
                            $g['score'] = 0;
                            foreach ($g['items'] as $it) {
                                if (!($it['locked'] ?? false) && ($it['pass'] ?? false)) $g['score'] += (int)($it['pts'] ?? 0);
                            }
                        }
                        unset($g);
                        ?>
                        <?php foreach ($groups as $g) :
                            $barPct = $g['max'] > 0 ? round($g['score'] / $g['max'] * 100) : 0;
                            $barColor = $barPct >= 70 ? '#16a34a' : ($barPct >= 40 ? '#d97706' : '#dc2626');
                        ?>
                        <div style="margin-bottom:10px">
                            <div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">
                                <span style="font-size:11px;font-weight:700;color:<?php echo esc_attr($g['color']); ?>"><?php echo esc_html($g['label']); ?></span>
                                <div style="flex:1;height:5px;background:#e2e8f0;border-radius:3px"><div style="width:<?php echo esc_attr($barPct); ?>%;height:5px;background:<?php echo esc_attr($barColor); ?>;border-radius:3px"></div></div>
                                <span style="font-size:11px;font-weight:600;color:<?php echo esc_attr($barColor); ?>"><?php echo esc_html((string)$g['score']); ?>/<?php echo esc_html((string)$g['max']); ?></span>
                                <?php if (!empty($g['locked'])): ?><span style="font-size:9px;color:#94a3b8">🔒</span><?php endif; ?>
                            </div>
                            <?php if (!empty($g['locked'])): ?>
                                <div style="padding:4px 8px;background:#fafafa;border:1px dashed #e2e8f0;border-radius:4px;font-size:10px;color:#94a3b8;text-align:center">
                                    <?php echo sprintf(/* translators: %s: category label */ esc_html__('Set a Focus Query in the post editor to unlock %s items', 'shop-to-llm'), esc_html($g['label'])); ?>
                                </div>
                            <?php else: ?>
                                <?php foreach ($g['items'] as $it): ?>
                                <div style="display:flex;align-items:center;gap:6px;font-size:10px;padding:1px 4px;margin:1px 0">
                                    <span style="color:<?php echo ($it['pass'] ?? false) ? '#16a34a' : '#94a3b8'; ?>;min-width:12px"><?php echo ($it['pass'] ?? false) ? '✓' : '✗'; ?></span>
                                    <span style="color:<?php echo ($it['pass'] ?? false) ? '#1e1e1e' : '#94a3b8'; ?>;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"><?php echo esc_html($it['name'] ?? ''); ?></span>
                                    <span style="margin-left:auto;color:#94a3b8"><?php echo ($it['pass'] ?? false) ? '+' : ''; ?><?php echo esc_html((string)($it['pts'] ?? 0)); ?></span>
                                </div>
                                <?php endforeach; ?>
                            <?php endif; ?>
                        </div>
                        <?php endforeach; ?>
                    <?php endif; ?>
                </div>
            <?php elseif ($triggerLookup) : ?>
                <p style="color:#94a3b8;font-size:13px;text-align:center;padding:16px 0"><?php echo esc_html__('Not found. Try selecting from the dropdown or use a different keyword.', 'shop-to-llm'); ?></p>
            <?php else : ?>
                <p style="color:#94a3b8;font-size:13px;margin:0;line-height:1.5">
                    <?php echo $isCommerce ? esc_html__('Select a product from the dropdown or type a name/SKU. A higher score means AI is more likely to recommend this product.', 'shop-to-llm') : esc_html__('Select a post from the dropdown or search by title. The 25-dimension model checks discoverability, relevance, quality, authority, and activity.', 'shop-to-llm'); ?>
                </p>
            <?php endif; ?>
            <?php if ($totalPostScore < 70 && $scannedPost) : ?>
            <p class="s2ll-suggestion">💡 <?php echo $isCommerce ? esc_html__('Click Edit to improve missing product data', 'shop-to-llm') : esc_html__('Click Edit → set Focus Query to unlock full 25-dimension analysis', 'shop-to-llm'); ?></p>
            <?php endif; ?>
        </div>
        <?php // end AI Readiness Score ?>


        <!-- AI Crawler Activity -->
        <div class="stl-card" style="border-left:4px solid #8b5cf6">
            <h2 style="display:flex;align-items:center;gap:8px;margin-bottom:12px">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" width="20" height="20"><circle cx="12" cy="12" r="3"/><path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/></svg>
                <?php echo esc_html__('AI Crawler Activity', 'shop-to-llm'); ?><span class="s2ll-help" title="<?php echo esc_attr__('Track when AI crawlers like GPTBot and ClaudeBot visit your site. More visits = better AI visibility.', 'shop-to-llm'); ?>"></span>
            </h2>
            <?php shop_to_llm_render_crawler_activity(); ?>
            <?php shop_to_llm_render_indexed_pages(); ?>
            <p class="s2ll-suggestion">💡 <?php echo esc_html__('Share your content regularly — AI crawlers return more often to sites that update frequently', 'shop-to-llm'); ?></p>
        </div>


        <!-- AI Search Activity -->
        <?php
        // Use $recentSearches and $searchCount from earlier (line ~394)
        ?>
        <div class="stl-card">
            <h2><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg><?php echo esc_html__('AI Search Activity', 'shop-to-llm'); ?><span class="s2ll-help" title="<?php echo esc_attr__('See what consumers are asking AI assistants about your content. Shows search terms, results, and timing.', 'shop-to-llm'); ?>"></span></h2>
            <?php if ($searchCount === 0) : ?>
                <p style="color:#94a3b8;font-size:13px;margin:0;line-height:1.6">
                    <?php echo shop_to_llm_has_woocommerce() ? esc_html__('No AI searches today yet. When a consumer asks an AI (like ChatGPT) about your products, the search terms and matched products will appear here — so you can see what consumers are asking AI about your store.', 'shop-to-llm') : esc_html__('No AI searches today yet. When a consumer asks an AI (like ChatGPT) about your content, the search terms and matched items will appear here — so you can see what consumers are asking AI about your site.', 'shop-to-llm'); ?>
                </p>
            <?php else : ?>
                <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px">
                    <p style="color:#64748b;font-size:12px;margin:0"><?php /* translators: %d: number of searches */ echo shop_to_llm_has_woocommerce() ? sprintf(esc_html__('What consumers asked AI about your store today (%d searches):', 'shop-to-llm'), absint($searchCount)) : sprintf(esc_html__('What consumers asked AI about your site today (%d searches):', 'shop-to-llm'), absint($searchCount)); ?></p>
                    <select id="stl-sort" style="font-size:12px;padding:3px 8px;border:1px solid #e2e8f0;border-radius:4px;background:#fff;color:#64748b">
                        <option value="time-desc"><?php echo esc_html__('Latest first', 'shop-to-llm'); ?></option>
                        <option value="time-asc"><?php echo esc_html__('Oldest first', 'shop-to-llm'); ?></option>
                        <option value="results-desc"><?php echo esc_html__('Most results', 'shop-to-llm'); ?></option>
                        <option value="results-asc"><?php echo esc_html__('Fewest results', 'shop-to-llm'); ?></option>
                    </select>
                </div>
                <table id="stl-search-table" style="width:100%;border-collapse:collapse;font-size:13px">
                    <thead>
                        <tr style="border-bottom:2px solid #e2e8f0">
                            <th style="text-align:left;padding:6px 8px;color:#64748b;font-weight:600"><?php echo esc_html__('Search Term', 'shop-to-llm'); ?></th>
                            <th style="text-align:left;padding:6px 8px;color:#64748b;font-weight:600"><?php echo esc_html__('Products Found', 'shop-to-llm'); ?></th>
                            <th style="text-align:center;padding:6px 8px;color:#64748b;font-weight:600"><?php echo esc_html__('Results', 'shop-to-llm'); ?></th>
                            <th style="text-align:right;padding:6px 8px;color:#64748b;font-weight:600"><?php echo esc_html__('Time', 'shop-to-llm'); ?></th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php foreach (array_slice($recentSearches, 0, 15) as $i => $s) : ?>
                        <tr data-idx="<?php echo esc_attr($i); ?>" data-results="<?php echo esc_attr((int)$s['results']); ?>" data-time="<?php echo esc_attr($s['time']); ?>" style="border-bottom:1px solid #f1f5f9">
                            <td style="padding:6px 8px;color:#1e1e1e;font-weight:500"><?php echo esc_html($s['query']); ?></td>
                            <td style="padding:6px 8px;color:#475569;font-size:12px">
                                <?php
                                $prods = $s['products'] ?? [];
                                if (!empty($prods)) {
                                    echo esc_html(implode(', ', $prods));
                                } else {
                                    echo '<span style="color:#94a3b8">—</span>';
                                }
                                ?>
                            </td>
                            <td style="padding:6px 8px;text-align:center">
                                <?php if ($s['results'] > 0) : ?>
                                    <span style="color:#166534;font-weight:600"><?php /* translators: %d: number of found results */ echo sprintf(esc_html__('%d found', 'shop-to-llm'), (int)$s['results']); ?></span>
                                <?php else : ?>
                                    <span style="color:#991b1b;font-weight:600"><?php echo esc_html__('No match', 'shop-to-llm'); ?></span>
                                <?php endif; ?>
                            </td>
                            <td style="padding:6px 8px;text-align:right;color:#94a3b8"><?php echo esc_html($s['time']); ?></td>
                        </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
                <?php if ($searchCount > 15) : ?>
                <p style="color:#94a3b8;font-size:12px;margin:8px 0 0"><?php /* translators: %d: number of searches */ echo sprintf(esc_html__('Showing latest 15 of %d searches.', 'shop-to-llm'), absint($searchCount)); ?></p>
                <?php endif; ?>
            <?php endif; ?>
            <p class="s2ll-suggestion">💡 <?php echo esc_html__('Use these search terms to improve your content and Focus Queries', 'shop-to-llm'); ?></p>
        </div>

        </div><!-- /tab-analytics -->

        <!-- Settings Tab -->
        <div class="stl-tab-panel" id="tab-settings">

        <!-- Settings Form -->
        <div class="stl-card">
            <h2><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg><?php echo esc_html__('Settings', 'shop-to-llm'); ?><span class="s2ll-help" title="<?php echo esc_attr__('Configure which features are active. All changes take effect immediately.', 'shop-to-llm'); ?>"></span></h2>
            <form method="post">
                <?php wp_nonce_field('shop_to_llm_settings'); ?>
                <table class="stl-form-table">
                    <tr>
                        <th><?php echo esc_html__('Enable', 'shop-to-llm'); ?></th>
                        <td>
                            <select name="shop_to_llm_enabled">
                                <option value="yes" <?php selected($enabled, 'yes'); ?>><?php echo esc_html__('Enabled — store is visible to AI agents', 'shop-to-llm'); ?></option>
                                <option value="no" <?php selected($enabled, 'no'); ?>><?php echo esc_html__('Disabled', 'shop-to-llm'); ?></option>
                            </select>
                        </td>
                    </tr>
                    <tr>
                        <th><?php echo esc_html__('Product Search', 'shop-to-llm'); ?></th>
                        <td>
                            <select name="shop_to_llm_catalog_search">
                                <option value="yes" <?php selected($catalogSearch, 'yes'); ?>><?php echo esc_html__('Enabled — AI can search your catalog', 'shop-to-llm'); ?></option>
                                <option value="no" <?php selected($catalogSearch, 'no'); ?>><?php echo esc_html__('Disabled', 'shop-to-llm'); ?></option>
                            </select>
                        </td>
                    </tr>
                    <tr>
                        <th><?php echo esc_html__('Analytics', 'shop-to-llm'); ?></th>
                        <td>
                            <select name="shop_to_llm_analytics">
                                <option value="no" <?php selected($analytics, 'no'); ?>><?php echo esc_html__('Disabled — no data sent', 'shop-to-llm'); ?></option>
                                <option value="yes" <?php selected($analytics, 'yes'); ?>><?php echo esc_html__('Enabled — share anonymized site metadata &amp; AI crawler visits for analytics', 'shop-to-llm'); ?></option>
                            </select>
                            <p style="color:#64748b;font-size:11px;margin:4px 0 0"><?php echo esc_html__('When enabled, basic site metadata and AI crawler visits are sent to our cloud service. No personal customer or order data is transmitted. Privacy Policy: https://shop2llm.com/privacy', 'shop-to-llm'); ?></p>
                        </td>
                    </tr>
                    <tr>
                        <th><?php echo esc_html__('AI Badge', 'shop-to-llm'); ?></th>
                        <td>
                            <select name="shop_to_llm_show_badge">
                                <option value="yes" <?php selected($showBadge, 'yes'); ?>><?php echo esc_html__('Show — display AI Discoverable badge on your site', 'shop-to-llm'); ?></option>
                                <option value="no" <?php selected($showBadge, 'no'); ?>><?php echo esc_html__('Hidden', 'shop-to-llm'); ?></option>
                            </select>
                            <p style="color:#64748b;font-size:11px;margin:4px 0 0"><?php echo esc_html__('Adds a small badge in the footer indicating your site is AI-discoverable. Links to your public /ai-visibility/ page.', 'shop-to-llm'); ?></p>
                        </td>
                    </tr>
                    <tr>
                        <th><?php echo esc_html__('Entity Checks', 'shop-to-llm'); ?></th>
                        <td>
                            <label>
                                <input type="checkbox" name="shop_to_llm_enable_entity_checks" value="1" <?php checked(get_option('shop_to_llm_enable_entity_checks', 0), 1); ?>>
                                <?php echo esc_html__('Enable external knowledge graph checks (Wikidata/Wikipedia)', 'shop-to-llm'); ?>
                            </label>
                            <p style="color:#64748b;font-size:11px;margin:4px 0 0"><?php echo esc_html__('This sends your focus query to public APIs to check entity presence. No personal data is transmitted.', 'shop-to-llm'); ?></p>
                        </td>
                    </tr>
                    <tr>
                        <th><?php echo esc_html__('Cart & Checkout', 'shop-to-llm'); ?></th>
                        <td>
                            <span class="stl-badge stl-badge-pro"><?php echo esc_html__('Pro', 'shop-to-llm'); ?></span>
                            <span style="color:#64748b;font-size:12px;margin-left:6px"><?php echo esc_html__('AI agents add to cart, checkout, and track orders', 'shop-to-llm'); ?></span>
                        </td>
                    </tr>
                </table>
                <p class="s2ll-suggestion">💡 <?php echo esc_html__('Enable Entity Checks for more accurate AI visibility scores', 'shop-to-llm'); ?></p>
                <?php submit_button(__('Save Settings', 'shop-to-llm'), 'primary', 'shop_to_llm_save'); ?>
            </form>
        </div>

        <!-- Test It Yourself / Connection Config -->
        <?php
        $configJson = json_encode([
            'mcpServers' => [
                'shop2llm' => [
                    'transport' => 'streamable-http',
                    'url' => $mcpEndpoint,
                    'headers' => ['Authorization' => 'Bearer ' . $apiKey],
                ],
            ],
        ], JSON_PRETTY_PRINT);
        ?>
        <div class="stl-card" id="stl-connection">
            <h2><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg><?php echo esc_html__('Test It Yourself', 'shop-to-llm'); ?><span class="s2ll-help" title="<?php echo esc_attr__('Connect your site to ChatGPT and other AI tools. Copy-paste this config to make your content AI-accessible.', 'shop-to-llm'); ?>"></span></h2>
            <div class="stl-row" style="margin-bottom:8px">
              <div class="stl-label"><?php echo esc_html__('Status', 'shop-to-llm'); ?></div>
              <div class="stl-value">
                <?php if ($enabled === 'yes'): ?>
                  <span class="stl-badge stl-badge-ok"><?php echo esc_html__('Connected', 'shop-to-llm'); ?></span>
                  <span style="font-size:11px;color:#94a3b8;margin-left:6px"><?php echo esc_html($storeName); ?> · <?php echo esc_html(shop_to_llm_tool_count()); ?></span>
                <?php else: ?>
                  <span class="stl-badge stl-badge-off"><?php echo esc_html__('Disabled', 'shop-to-llm'); ?></span>
                <?php endif; ?>
              </div>
            </div>
            <p style="color:#64748b;font-size:12px;margin:0 0 12px;line-height:1.5"><?php echo shop_to_llm_has_woocommerce() ? esc_html__('Paste this config into your ChatGPT to verify your store is AI-ready. This connects your ChatGPT to your store — a quick way to confirm everything works.', 'shop-to-llm') : esc_html__('Paste this config into your ChatGPT to verify your site is AI-ready. This connects your ChatGPT to your site — a quick way to confirm everything works.', 'shop-to-llm'); ?></p>
            <?php if ($todayCount === 0) : ?>
            <div style="background:#fef3c7;border:1px solid #fcd34d;border-radius:6px;padding:10px 14px;margin-bottom:16px;font-size:12px;color:#92400e;line-height:1.5">
                <?php echo esc_html__('💡 This config connects your own ChatGPT to verify everything works. Copy the config below and paste it into ChatGPT Desktop.', 'shop-to-llm'); ?>
            </div>
            <?php endif; ?>
            <button type="button" class="stl-config-toggle" onclick="var b=document.getElementById('stl-config-box');b.classList.toggle('stl-config-box--open');this.style.display='none'">
                📋 Show Connection Config
            </button>
            <div class="stl-config-box" id="stl-config-box">
                <div style="position:relative;margin-bottom:14px">
                    <pre id="stl-config" style="margin:0;padding:12px 14px;background:#1e1e1e;color:#e2e8f0;border-radius:6px;font-size:11px;line-height:1.6;overflow-x:auto;white-space:pre-wrap;word-break:break-all"><?php echo esc_html($configJson); ?></pre>
                    <button type="button" class="stl-copy-btn" onclick="stlCopy('stl-config',this)" style="position:absolute;top:8px;right:8px"><?php echo esc_html__('Copy Config', 'shop-to-llm'); ?></button>
                </div>
            </div>
            <p style="color:#64748b;font-size:12px;margin:0 0 12px;line-height:1.5"><?php echo esc_html__('Then: ChatGPT Desktop → Settings → Developer → Connect App → paste and save.', 'shop-to-llm'); ?></p>
            <?php if ($todayCount === 0 && $crawlerCount === 0) : ?>
            <div style="padding:6px 12px;background:#eef2ff;border:1px solid #c7d2fe;border-radius:6px;margin-bottom:12px;text-align:center">
                <span style="font-size:11px;color:#6366f1;font-weight:500">
                    <?php echo shop_to_llm_has_woocommerce() ? esc_html__('👋 First time here? Copy the config above → paste into ChatGPT → your store goes live', 'shop-to-llm') : esc_html__('👋 First time here? Copy the config above → paste into ChatGPT → your site goes live', 'shop-to-llm'); ?>
                </span>
            </div>
            <?php endif; ?>
            <div class="stl-row">
                <div class="stl-label"><?php echo esc_html__('API Key', 'shop-to-llm'); ?></div>
                <div class="stl-value" style="display:flex;align-items:center;gap:8px">
                    <code id="stl-apikey" class="stl-mono" style="max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"><?php echo esc_html($apiKey); ?></code>
                    <button type="button" class="stl-copy-btn" onclick="stlCopy('stl-apikey',this)"><?php echo esc_html__('Copy', 'shop-to-llm'); ?></button>
                </div>
            </div>
            <div class="stl-row">
                <div class="stl-label"><?php echo esc_html__('AI Endpoint', 'shop-to-llm'); ?></div>
                <div class="stl-value" style="display:flex;align-items:center;gap:8px">
                    <code id="stl-endpoint" class="stl-mono" style="max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"><?php echo esc_url($mcpEndpoint); ?></code>
                    <button type="button" class="stl-copy-btn" onclick="stlCopy('stl-endpoint',this)"><?php echo esc_html__('Copy', 'shop-to-llm'); ?></button>
                </div>
            </div>
            <p class="s2ll-suggestion">💡 <?php echo esc_html__('After connecting, check the Dashboard tab to see AI activity', 'shop-to-llm'); ?></p>
        </div>

        <!-- AI Platforms -->
        <div class="stl-card">
            <h2><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8"/><path d="M12 17v4"/></svg><?php echo esc_html__('AI Platforms', 'shop-to-llm'); ?><span class="s2ll-help" title="<?php echo esc_attr__('AI platforms your content can appear on. Free supports ChatGPT — Pro unlocks all 6 platforms.', 'shop-to-llm'); ?>"></span></h2>
            <div class="stl-grid">
                <div class="stl-platform" style="">
                    <svg viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#10a37f"/><path d="M7 8h10M7 12h10M7 16h6" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/></svg>
                    <span class="stl-pname">ChatGPT</span>
                    <span class="stl-badge stl-badge-ok" style="margin-left:auto"><?php echo esc_html__('Free', 'shop-to-llm'); ?></span>
                </div>
                <div class="stl-platform" style="filter:grayscale(1);opacity:0.5">
                    <svg viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#d97706"/><path d="M8 7l4 5-4 5M13 7l4 5-4 5" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
                    <span class="stl-pname">Claude</span>
                    <span class="stl-badge stl-badge-pro" style="margin-left:auto"><?php echo esc_html__('Pro', 'shop-to-llm'); ?></span>
                </div>
                <div class="stl-platform" style="filter:grayscale(1);opacity:0.5">
                    <svg viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#4285f4"/><path d="M12 7v10M7 12h10" stroke="#fff" stroke-width="2" stroke-linecap="round"/></svg>
                    <span class="stl-pname">Gemini</span>
                    <span class="stl-badge stl-badge-pro" style="margin-left:auto"><?php echo esc_html__('Pro', 'shop-to-llm'); ?></span>
                </div>
                <div class="stl-platform" style="filter:grayscale(1);opacity:0.5">
                    <svg viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#7c3aed"/><path d="M8 12l3 3 5-6" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
                    <span class="stl-pname">Copilot</span>
                    <span class="stl-badge stl-badge-pro" style="margin-left:auto"><?php echo esc_html__('Pro', 'shop-to-llm'); ?></span>
                </div>
                <div class="stl-platform" style="filter:grayscale(1);opacity:0.5">
                    <svg viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#1877f2"/><circle cx="12" cy="12" r="4" stroke="#fff" stroke-width="1.5"/><path d="M12 2v4M12 18v4M2 12h4M18 12h4" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/></svg>
                    <span class="stl-pname">Meta AI</span>
                    <span class="stl-badge stl-badge-pro" style="margin-left:auto"><?php echo esc_html__('Pro', 'shop-to-llm'); ?></span>
                </div>
                <div class="stl-platform" style="filter:grayscale(1);opacity:0.5">
                    <svg viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#ff9900"/><path d="M7 17l5-10 5 10M9 14h6" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
                    <span class="stl-pname">Bedrock</span>
                    <span class="stl-badge stl-badge-pro" style="margin-left:auto"><?php echo esc_html__('Pro', 'shop-to-llm'); ?></span>
                </div>
            </div>
        </div>

        </div><!-- /tab-settings -->

        <?php $proCtaDismissed = get_user_meta(get_current_user_id(), 'shop2llm_pro_cta_dismissed', true); ?>
        <?php $proCtaDismissedAt = (int) get_user_meta(get_current_user_id(), 'shop2llm_pro_cta_dismissed_at', true); ?>
        <?php if (!$proCtaDismissed || (time() - $proCtaDismissedAt) > 30 * DAY_IN_SECONDS) : ?>
        <!-- Pro CTA (dismissible for 30 days) -->
        <div class="stl-pro-cta" id="stl-pro-cta">
          <button type="button" id="stl-pro-cta-dismiss" style="position:absolute;top:8px;right:12px;background:none;border:none;color:rgba(255,255,255,0.6);cursor:pointer;font-size:18px" title="<?php echo esc_attr__('Dismiss for 30 days', 'shop-to-llm'); ?>">&times;</button>
          <div class="stl-cta-text">
            <h3><?php echo esc_html__('Upgrade to Shop2LLM Pro', 'shop-to-llm'); ?></h3>
            <p><?php echo esc_html__('6 AI platforms, always-on connection, unlimited API calls, 30-day analytics — $9/mo', 'shop-to-llm'); ?></p>
          </div>
          <a href="https://shop2llm.com/fincore/v1/page/prolanding?site=<?php echo urlencode(get_site_url()); ?>" target="_blank"><?php echo esc_html__('Upgrade to Pro', 'shop-to-llm'); ?> →</a>
        </div>
        <?php endif; ?>
    </div>

    <script>
    // Async community stats loader — non-blocking, caches count for review notice
    (function(){
        var el = document.getElementById('stl-community-trust');
        if (!el) return;
        var t = el.getAttribute('data-stl-loaded');
        if (t === '1') { el.style.display = ''; return; }
        var u = '<?php echo esc_js(rest_url('shop2llm/v1/community')); ?>';
        fetch(u).then(function(r){return r.json()}).then(function(d){
            if (d && d.weekly_downloads > 0) {
                document.getElementById('stl-community-text').textContent =
                    '\uD83D\uDD25 ' + d.weekly_downloads + ' <?php echo esc_js(__('stores added this week', 'shop-to-llm')); ?>';
                el.style.display = '';
                el.setAttribute('data-stl-loaded', '1');
                // Cache count server-side via AJAX for review notice usage
                var fd = new FormData();
                fd.append('action', 'shop2llm_cache_community_count');
                fd.append('count', d.weekly_downloads);
                fd.append('nonce', '<?php echo esc_js(wp_create_nonce('shop2llm_admin_nonce')); ?>');
                fetch('<?php echo esc_js(admin_url('admin-ajax.php')); ?>', {method:'POST',body:fd}).catch(function(){});
            }
        }).catch(function(){});
    })();

    // Pro CTA dismiss handler
    (function(){
        var cta = document.getElementById('stl-pro-cta');
        var dismissBtn = document.getElementById('stl-pro-cta-dismiss');
        if (!cta || !dismissBtn) return;
        dismissBtn.addEventListener('click', function(){
            cta.style.display = 'none';
            var fd = new FormData();
            fd.append('action', 'shop2llm_dismiss_pro_cta');
            fd.append('nonce', '<?php echo esc_js(wp_create_nonce('shop2llm_admin_nonce')); ?>');
            fetch('<?php echo esc_js(admin_url('admin-ajax.php')); ?>', {method:'POST',body:fd}).catch(function(){});
        });
    })();
    // Auto-open wizard on first visit after activation
    var stlJustActivated = <?php echo $justActivated ? 'true' : 'false'; ?>;
    if (stlJustActivated) {
        document.addEventListener("DOMContentLoaded", function() {
            var w = document.getElementById("stl-openai-guide");
            if (w) w.style.display = "";
            var b = document.getElementById("stl-wizard-toggle-btn");
            if (b) b.classList.add("stl-wizard-toggle--active");
        });
    }
    // Tab switching
    document.querySelectorAll('.stl-tab').forEach(function(tab) {
      tab.addEventListener('click', function() {
        document.querySelectorAll('.stl-tab').forEach(function(t) { t.classList.remove('stl-tab-active'); });
        document.querySelectorAll('.stl-tab-panel').forEach(function(p) { p.classList.remove('stl-tab-panel-active'); });
        this.classList.add('stl-tab-active');
        document.getElementById('tab-' + this.getAttribute('data-tab')).classList.add('stl-tab-panel-active');
      });
    });
    // Auto-activate Analytics tab if form was submitted from it, scroll to card
    <?php if (isset($_POST['stl_tab']) && $_POST['stl_tab'] === 'analytics') : ?>
    document.addEventListener('DOMContentLoaded', function() {
      var analyticsTab = document.querySelector('.stl-tab[data-tab="analytics"]');
      if (analyticsTab) analyticsTab.click();
      var card = document.getElementById('stl-ai-readiness');
      if (card) card.scrollIntoView({behavior:'smooth', block:'start'});
    });
    <?php endif; ?>
    </script>
<?php }
function shop_to_llm_tool_count(): string
{
    return __('2 AI tools (search, product detail) — 5 total with Pro', 'shop-to-llm');
}

function shop_to_llm_community_stats_endpoint(): \WP_REST_Response
{
    $cached = get_transient('shop2llm_community_stats');
    if ($cached !== false) {
        return new \WP_REST_Response($cached);
    }

    // Serve default immediately to avoid blocking this request
    $default = ['weekly_downloads' => 0, 'active_installs' => '—', 'rating' => '—', 'reviews' => 0];

    // Prevent concurrent fetches
    $fetching = get_transient('shop2llm_community_stats_fetching');
    if ($fetching) {
        return new \WP_REST_Response($default);
    }
    set_transient('shop2llm_community_stats_fetching', 1, 30);

    // Defer actual fetch to shutdown hook so response is sent immediately
    add_action('shutdown', function () use ($default) {
        $resp = wp_remote_get('https://api.shop2llm.com/fincore/v1/stats', ['timeout' => 5]);
        if (is_wp_error($resp) || wp_remote_retrieve_response_code($resp) !== 200) {
            set_transient('shop2llm_community_stats', $default, HOUR_IN_SECONDS);
            return;
        }
        $body = json_decode(wp_remote_retrieve_body($resp), true);
        if (!is_array($body)) {
            set_transient('shop2llm_community_stats', $default, HOUR_IN_SECONDS);
            return;
        }
        $stats = array_merge($default, $body);
        set_transient('shop2llm_community_stats', $stats, DAY_IN_SECONDS);
    });

    return new \WP_REST_Response($default);
}

function shop_to_llm_get_community_stats(): array
{
    $cached = get_transient('shop2llm_community_stats');
    if ($cached !== false) {
        return $cached;
    }
    // Return empty - async JS will populate via REST endpoint
    return ['weekly_downloads' => 0, 'active_installs' => '—', 'rating' => '—', 'reviews' => 0];
}

// ─── Cloud Heartbeat (monthly, SHA256-hashed site ID) ──────────────

add_action('shop2llm_cloud_heartbeat', 'shop_to_llm_cloud_heartbeat');
function shop_to_llm_cloud_heartbeat(): void
{
    if (get_option('shop_to_llm_analytics', 'no') !== 'yes') {
        return;
    }
    $site_url = get_site_url();
    wp_remote_post('https://api.shop2llm.com/fincore/v1/free/ping', [
        'timeout' => 15,
        'blocking' => false,
        'headers' => ['Content-Type' => 'application/json'],
        'body' => json_encode([
            'site_url' => $site_url,
            'site_hash' => hash('sha256', wp_parse_url($site_url, PHP_URL_HOST) ?: $site_url),
            'platform' => shop_to_llm_has_woocommerce() ? 'woocommerce' : 'wordpress',
            'plugin_version' => SHOP_TO_LLM_VERSION,
            'php_version' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION,
            'wc_version' => class_exists('WooCommerce') ? WC()->version : '',
            'wp_version' => get_bloginfo('version'),
            'behavior' => shop_to_llm_get_behavior_summary_for_upload(),
        ]),
    ]);
}

/**
 * Push product catalog to SaaS search index during daily heartbeat.
 * Sends lightweight metadata (title, price, category, image) — no customer/order data.
 * Tier is determined server-side from the store's license.
 */
function shop_to_llm_sync_product_feed(): void
{
    // Opt-out filter for site owners who don't want product indexing
    if (apply_filters('shop2llm_disable_product_sync', false)) {
        return;
    }
    if (!shop_to_llm_has_woocommerce()) {
        return;
    }
    $site_url = get_site_url();
    $products = wc_get_products([
        'limit'   => 500,
        'status'  => 'publish',
        'orderby' => 'date',
    ]);

    $payload = [];
    foreach ($products as $p) {
        $pid     = $p->get_id();
        $imgId   = $p->get_image_id();
        $imgUrl  = $imgId ? wp_get_attachment_url($imgId) : '';
        $cats    = wp_get_post_terms($pid, 'product_cat', ['fields' => 'names']);
        $payload[] = [
            'id'           => (string)$pid,
            'title'        => wp_strip_all_tags($p->get_name()),
            'price'        => (float)$p->get_price(),
            'description'  => wp_strip_all_tags($p->get_short_description() ?: $p->get_description()),
            'image_url'    => $imgUrl,
            'product_url'  => get_permalink($pid),
            'category'     => !empty($cats) ? implode(', ', $cats) : null,
            'brand'        => get_bloginfo('name'),
            'availability' => $p->is_in_stock() ? 'in stock' : 'out of stock',
        ];
    }

    if (empty($payload)) {
        return;
    }

    wp_remote_post('https://api.shop2llm.com/fincore/v1/store/feed/sync', [
        'timeout'  => 30,
        'blocking' => false,
        'headers'  => ['Content-Type' => 'application/json'],
        'body'     => json_encode([
            'site_url'     => $site_url,
            'platform'     => 'woocommerce',
            'version'      => SHOP_TO_LLM_VERSION,
            'mcp_endpoint' => rest_url('shop2llm/v1/mcp'),
            'products'     => $payload,
        ]),
    ]);
}
add_action('shop2llm_cloud_heartbeat', 'shop_to_llm_sync_product_feed', 20);

// ─── Behavior Tracking (local summary, opt-in upload) ─────────────────

/**
 * Log a user behavior event to the local behavior_summary option.
 * Pure local storage — no network call, no PII, no opt-in required.
 * Only the final state is kept (not a time-series log) to keep option size bounded.
 */
function shop_to_llm_log_event(string $event, array $data = []): void
{
    $summary = get_option('shop2llm_behavior_summary', []);
    if (!is_array($summary)) {
        $summary = [];
    }
    $now = time();
    switch ($event) {
        case 'activated':
            if (empty($summary['activated'])) {
                $summary['activated'] = $now;
            }
            break;
        case 'welcome_shown':
            if (empty($summary['welcome_shown'])) {
                $summary['welcome_shown'] = $now;
            }
            break;
        case 'welcome_completed':
            $summary['welcome_completed'] = $now;
            $summary['welcome_skipped_step'] = null;
            break;
        case 'welcome_skipped':
            $summary['welcome_skipped_step'] = isset($data['step']) ? (int) $data['step'] : 1;
            break;
        case 'url_copied':
            if (empty($summary['url_copied'])) {
                $summary['url_copied'] = $now;
            }
            break;
        case 'first_search':
            if (empty($summary['first_search'])) {
                $summary['first_search'] = $now;
                $summary['first_search_ok'] = !empty($data['success']);
            }
            break;
        case 'dashboard_visit':
            $summary['dashboard_visits'] = (int) ($summary['dashboard_visits'] ?? 0) + 1;
            break;
    }
    update_option('shop2llm_behavior_summary', $summary, false);
}

/**
 * Get the behavior summary as an anonymized, bucketed array safe for upload.
 * No PII, no search terms, no URLs — only bool/int/bucket values.
 */
function shop_to_llm_get_behavior_summary_for_upload(): array
{
    $s = get_option('shop2llm_behavior_summary', []);
    if (!is_array($s)) {
        $s = [];
    }

    // Content count (bucketed to avoid fingerprinting)
    $postCount = (int) wp_count_posts('post')->publish ?? 0;
    $productCount = 0;
    if (shop_to_llm_has_woocommerce()) {
        $productCount = (int) wp_count_posts('product')->publish ?? 0;
    }
    $totalContent = $postCount + $productCount;
    if ($totalContent <= 0) $contentBucket = '0';
    elseif ($totalContent < 5) $contentBucket = '1-4';
    elseif ($totalContent <= 20) $contentBucket = '5-20';
    elseif ($totalContent <= 100) $contentBucket = '21-100';
    else $contentBucket = '100+';

    // Dashboard visits bucketed
    $visits = (int) ($s['dashboard_visits'] ?? 0);
    if ($visits <= 1) $visitBucket = '1';
    elseif ($visits <= 5) $visitBucket = '2-5';
    else $visitBucket = '6+';

    // Days since activation
    $daysSince = 0;
    if (!empty($s['activated'])) {
        $daysSince = (int) floor((time() - (int) $s['activated']) / DAY_IN_SECONDS);
    }

    return [
        'has_content' => $totalContent > 0,
        'content_count' => $contentBucket,
        'welcome_shown' => !empty($s['welcome_shown']),
        'welcome_completed' => !empty($s['welcome_completed']),
        'welcome_skipped_step' => isset($s['welcome_skipped_step']) ? (int) $s['welcome_skipped_step'] : null,
        'url_copied' => !empty($s['url_copied']),
        'first_search_done' => !empty($s['first_search']),
        'first_search_ok' => !empty($s['first_search_ok']),
        'first_crawler_done' => !empty($s['first_crawler']) || get_option('shop_to_llm_first_crawler_notified'),
        'days_since_activation' => $daysSince,
        'dashboard_visits' => $visitBucket,
    ];
}

function shop_to_llm_handle_log_event(): void
{
    if (!current_user_can('manage_options')) {
        wp_die();
    }
    check_ajax_referer('shop2llm_admin_nonce', 'nonce');
    $event = sanitize_text_field(wp_unslash($_POST['event'] ?? ''));
    $data = [];
    if (isset($_POST['step'])) {
        $data['step'] = (int) $_POST['step'];
    }
    if (isset($_POST['success'])) {
        $data['success'] = $_POST['success'] === '1';
    }
    if ($event) {
        shop_to_llm_log_event($event, $data);
    }
    wp_send_json_success();
}

function shop_to_llm_handle_enable_analytics(): void
{
    if (!current_user_can('manage_options')) {
        wp_die();
    }
    check_ajax_referer('shop2llm_admin_nonce', 'nonce');
    update_option('shop_to_llm_analytics', 'yes');

    // Send immediate ping to cloud — include behavior summary so data appears right away
    $site_url = get_site_url();
    wp_remote_post('https://api.shop2llm.com/fincore/v1/free/ping', [
        'timeout' => 5,
        'blocking' => false,
        'headers' => ['Content-Type' => 'application/json'],
        'body' => json_encode([
            'site_url' => $site_url,
            'site_hash' => hash('sha256', wp_parse_url($site_url, PHP_URL_HOST) ?: $site_url),
            'platform' => shop_to_llm_has_woocommerce() ? 'woocommerce' : 'wordpress',
            'plugin_version' => SHOP_TO_LLM_VERSION,
            'php_version' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION,
            'wc_version' => class_exists('WooCommerce') ? WC()->version : '',
            'wp_version' => get_bloginfo('version'),
            'behavior' => shop_to_llm_get_behavior_summary_for_upload(),
        ]),
    ]);
    wp_send_json_success();
}

// ─── MCP JSON-RPC Handler ──────────────────────────────────────────────

function shop_to_llm_handle_mcp(\WP_REST_Request $request): \WP_REST_Response
{
    // Rate limit: max 60 requests per minute
    $rlKey = 'shop_to_llm_mcp_rate_' . gmdate('Y-m-d-H-i');
    $rlCount = (int) get_transient($rlKey);
    if ($rlCount > 60) {
        return new \WP_REST_Response([
            'jsonrpc' => '2.0', 'id' => null,
            'error' => ['code' => -32000, 'message' => 'Rate limit exceeded'],
        ], 429);
    }
    set_transient($rlKey, $rlCount + 1, 60);

    if (function_exists('WC') && WC()->session) {
        WC()->session->set('shop_to_llm_ai_session', true);
    }

    // Batch counter writes — lock-based to avoid concurrent read-write races
    $lockKey = 'shop_to_llm_mcp_lock';
    if (!get_transient($lockKey)) {
        set_transient($lockKey, 1, 15); // Lock for 15 seconds
        $pending = (int) get_transient('shop_to_llm_mcp_pending');
        if ($pending > 0) {
            $key = 'shop_to_llm_count_' . gmdate('Y-m-d');
            $today = (int) get_transient($key) + $pending;
            set_transient($key, $today, DAY_IN_SECONDS);
            $k7d = 'shop_to_llm_count_7d';
            $sum7d = (int) get_transient($k7d) + $pending;
            set_transient($k7d, $sum7d, DAY_IN_SECONDS);
            delete_transient('shop_to_llm_mcp_pending');
        }
    }
    set_transient('shop_to_llm_mcp_pending', (int) get_transient('shop_to_llm_mcp_pending') + 1, 60);

    $body = $request->get_json_params();
    if (!is_array($body)) {
        return new \WP_REST_Response([
            'jsonrpc' => '2.0', 'id' => null,
            'error' => ['code' => -32700, 'message' => 'Parse error'],
        ], 200);
    }
    $method = $body['method'] ?? '';
    $params = $body['params'] ?? [];

    switch ($method) {
        case 'tools/list':
            $result = shop_to_llm_tools_list();
            break;
        case 'tools/call':
            $result = shop_to_llm_tools_call($params);
            break;
        case 'initialize':
            $result = [
                'protocolVersion' => '2025-11-25',
                'capabilities' => ['tools' => ['listChanged' => true]],
                'serverInfo' => ['name' => get_bloginfo('name') . ' — Shop2LLM', 'version' => SHOP_TO_LLM_VERSION],
            ];
            break;
        case 'prompts/list':
            $result = ['prompts' => []];
            break;
        default:
            $result = null;
    }

    if ($result === null) {
        return new \WP_REST_Response([
            'jsonrpc' => '2.0', 'id' => $body['id'] ?? null,
            'error' => ['code' => -32601, 'message' => "Method not found: {$method}"],
        ], 200);
    }

    return new \WP_REST_Response([
        'jsonrpc' => '2.0', 'id' => $body['id'] ?? null, 'result' => $result,
    ], 200);
}

// ─── Tool Definitions ──────────────────────────────────────────────────

function shop_to_llm_tools_list(): array
{
    $hasWC = shop_to_llm_has_woocommerce();
    $tools = [
        [
            'name' => 'search_products',
            'description' => 'Search the ' . get_bloginfo('name') . ($hasWC ? ' product catalog.' : ' website content.') . ' Returns matching ' . ($hasWC ? 'products with prices, stock status, ratings, and purchase URLs.' : 'posts and pages with titles, excerpts, and links.'),
            'inputSchema' => [
                'type' => 'object',
                'properties' => [
                    'query' => ['type' => 'string', 'description' => 'Search keywords (e.g., "wireless headphones", "summer dress")'],
                    'category' => ['type' => 'string', 'description' => 'Filter by product category slug'],
                    'max_price' => ['type' => 'number', 'description' => 'Maximum price in store currency'],
                    'in_stock_only' => ['type' => 'boolean', 'default' => true, 'description' => 'Only show in-stock items'],
                    'limit' => ['type' => 'integer', 'default' => 10, 'maximum' => 50],
                ],
                'required' => ['query'],
            ],
            'idempotent' => true,
        ],
        [
            'name' => 'get_product',
            'description' => 'Get full details for a specific ' . ($hasWC ? 'product including description, price, variants, and availability.' : 'post or page including title, full content, and metadata.'),
            'inputSchema' => [
                'type' => 'object',
                'properties' => ['product_id' => ['type' => 'integer', 'description' => 'WooCommerce product ID']],
                'required' => ['product_id'],
            ],
            'idempotent' => true,
        ],
    ];

    return apply_filters('shop_to_llm_tools_list', ['tools' => $tools]);
}

function shop_to_llm_tools_call(array $params): array
{
    $name = $params['name'] ?? '';
    $args = $params['arguments'] ?? [];

    switch ($name) {
        case 'search_products':
            $result = shop_to_llm_search_products($args);
            break;
        case 'get_product':
            $result = shop_to_llm_get_product($args);
            break;
        default:
            $result = ['error' => ['code' => 'TOOL_NOT_FOUND', 'message' => 'Tool not found: ' . $name]];
    }

    return apply_filters('shop_to_llm_tools_call', $result, $name, $args);
}

// ─── Tool Implementations ──────────────────────────────────────────────

if (!function_exists('shop_to_llm_search_products')) :
    function shop_to_llm_search_products(array $args): array
    {
        $query = $args['query'] ?? '';
        $limit = min((int)($args['limit'] ?? 10), 50);

        if (shop_to_llm_has_woocommerce()) {
            $wcArgs = [
            'status' => 'publish',
            's' => $query,
            'limit' => $limit,
            ];

            if (!empty($args['category'])) {
                $wcArgs['category'] = [$args['category']];
            }
            if (!empty($args['max_price'])) {
                $wcArgs['max_price'] = (float)$args['max_price'];
            }
            if ($args['in_stock_only'] ?? true) {
                $wcArgs['stock_status'] = 'instock';
            }

            $products = wc_get_products($wcArgs);

            $results = array_map(function ($p) {
                return [
                'id' => $p->get_id(),
                'name' => $p->get_name(),
                'price' => $p->get_price(),
                'currency' => get_woocommerce_currency(),
                'in_stock' => $p->is_in_stock(),
                'rating' => (float)$p->get_average_rating(),
                'short_description' => wp_strip_all_tags($p->get_short_description()),
                'url' => $p->get_permalink(),
                'image_url' => wp_get_attachment_url($p->get_image_id()) ?: '',
                ];
            }, $products);

            if (!empty($query)) {
                $productNames = array_map(function ($p) {
                    return $p->get_name();
                }, $products);
                shop_to_llm_log_search($query, count($results), $productNames);
            }

            return ['products' => $results];
        }

        // Non-WooCommerce: search WordPress posts (exclude password-protected, limit to post/page)
        $postArgs = [
        's' => $query,
        'post_type' => ['post', 'page'],
        'post_status' => 'publish',
        'posts_per_page' => $limit,
        'has_password' => false,
        ];
        $posts = get_posts($postArgs);

        $results = array_map(function ($p) {
            return [
            'id' => $p->ID,
            'name' => $p->post_title,
            'price' => '',
            'currency' => '',
            'in_stock' => true,
            'rating' => 0,
            'short_description' => wp_strip_all_tags(get_the_excerpt($p) ?: wp_trim_words($p->post_content, 30)),
            'url' => get_permalink($p->ID),
            'image_url' => get_the_post_thumbnail_url($p->ID, 'medium') ?: '',
            ];
        }, $posts);

        if (!empty($query)) {
            $names = array_map(function ($p) {
                return $p->post_title;
            }, $posts);
            shop_to_llm_log_search($query, count($results), $names);
        }

        return ['products' => $results];
    }
endif; // function_exists guard for Pro compatibility

if (!function_exists('shop_to_llm_get_product')) :
function shop_to_llm_get_product(array $args): array
{
    $id = (int)($args['product_id'] ?? 0);
    if ($id <= 0) {
        return ['error' => ['code' => 'INVALID_ARGUMENT', 'message' => 'Valid product_id required']];
    }

    if (shop_to_llm_has_woocommerce()) {
        $product = wc_get_product($id);
        if (!$product || $product->get_status() !== 'publish') {
            return ['error' => ['code' => 'NOT_FOUND', 'message' => 'Product not found']];
        }

        return [
            'product' => [
                'id' => $product->get_id(),
                'name' => $product->get_name(),
                'description' => wp_strip_all_tags($product->get_description()),
                'short_description' => wp_strip_all_tags($product->get_short_description()),
                'price' => $product->get_price(),
                'regular_price' => $product->get_regular_price(),
                'sale_price' => $product->get_sale_price(),
                'currency' => get_woocommerce_currency(),
                'in_stock' => $product->is_in_stock(),
                'stock_quantity' => $product->get_stock_quantity(),
                'rating' => (float)$product->get_average_rating(),
                'review_count' => $product->get_review_count(),
                'categories' => wp_get_post_terms($id, 'product_cat', ['fields' => 'names']),
                'url' => $product->get_permalink(),
                'image_url' => wp_get_attachment_url($product->get_image_id()) ?: '',
            ],
        ];
    }

    // Non-WooCommerce: get WordPress post
    $post = get_post($id);
    if (!$post || $post->post_status !== 'publish') {
        return ['error' => ['code' => 'NOT_FOUND', 'message' => 'Content not found']];
    }

    return [
        'product' => [
            'id' => $post->ID,
            'name' => $post->post_title,
            'description' => wp_strip_all_tags($post->post_content),
            'short_description' => wp_strip_all_tags(get_the_excerpt($post) ?: wp_trim_words(wp_strip_all_tags($post->post_content), 30)),
            'price' => '',
            'regular_price' => '',
            'sale_price' => '',
            'currency' => '',
            'in_stock' => true,
            'stock_quantity' => null,
            'rating' => 0,
            'review_count' => 0,
            'categories' => wp_get_post_terms($id, 'category', ['fields' => 'names']),
            'url' => get_permalink($post->ID),
            'image_url' => get_the_post_thumbnail_url($post->ID, 'full') ?: '',
        ],
    ];
}
endif; // function_exists guard for Pro compatibility

if (!function_exists('shop_to_llm_log_search')) :
function shop_to_llm_log_search(string $query, int $resultCount, array $productNames = []): void
{
    $key = 'shop_to_llm_searches_' . gmdate('Y-m-d');
    $searches = get_transient($key);
    if (!is_array($searches)) {
        $searches = [];
    }
    array_unshift($searches, [
        'query' => sanitize_text_field($query),
        'results' => $resultCount,
        'products' => array_slice(array_map('sanitize_text_field', $productNames), 0, 5),
        'time' => current_time('H:i'),
    ]);
    set_transient($key, array_slice($searches, 0, 50), DAY_IN_SECONDS);

    // Review request: trigger after 20+ API calls AND 10+ searches AND 7+ days since first crawler
    if (!get_option('shop_to_llm_review_request_sent') && get_option('shop_to_llm_first_crawler_notified')) {
        $todayCount = shop_to_llm_get_today_api_count();
        $searchCount = count(shop_to_llm_get_recent_searches());
        $firstCrawlerTime = get_option('shop_to_llm_first_crawler_time');
        $daysSinceFirstCrawler = $firstCrawlerTime ? floor((time() - (int)$firstCrawlerTime) / DAY_IN_SECONDS) : 0;
        if ($todayCount >= 20 && $searchCount >= 10 && $daysSinceFirstCrawler >= 7) {
            update_option('shop_to_llm_review_request_sent', current_time('mysql'));
            $crawlerPlatform = get_option('shop_to_llm_first_crawler_platform', 'AI');
            set_transient('shop_to_llm_review_request', $crawlerPlatform, DAY_IN_SECONDS);
        }
    }
}

endif; // function_exists guard for Pro compatibility

if (!function_exists('shop_to_llm_get_recent_searches')) :
function shop_to_llm_get_recent_searches(): array
{
    $key = 'shop_to_llm_searches_' . gmdate('Y-m-d');
    $searches = get_transient($key);
    return is_array($searches) ? $searches : [];
}
endif; // function_exists guard for Pro compatibility

function shop_to_llm_log_crawler(string $crawlerType): void
{
    // Record first crawler timestamp for review request logic
    if (!get_option('shop_to_llm_first_crawler_time')) {
        update_option('shop_to_llm_first_crawler_time', time());
        update_option('shop_to_llm_first_crawler_platform', $crawlerType);
    }

    $key = 'shop_to_llm_crawlers_' . gmdate('Y-m-d');
    $crawlers = get_transient($key);
    if (!is_array($crawlers)) {
        $crawlers = [];
    }
    $label = [
        'oai-search' => 'ChatGPT Search', 'chatgpt-user' => 'ChatGPT User',
        'gptbot' => 'GPTBot', 'claude-bot' => 'ClaudeBot',
        'perplexity-bot' => 'Perplexity', 'google-extended' => 'Google AI',
        'bingbot' => 'Bing', 'meta-external' => 'Meta AI',
        'facebook-bot' => 'Facebook', 'amazonbot' => 'Amazon Bot',
        'bytespider' => 'ByteDance', 'applebot-extended' => 'Apple Bot',
    ];
    array_unshift($crawlers, [
        'crawler' => $label[$crawlerType] ?? $crawlerType,
        'time' => current_time('H:i'),
    ]);
    set_transient($key, array_slice($crawlers, 0, 50), DAY_IN_SECONDS);

    // Persist to DB for 30-day historical tracking (feature anchor)
    shop_to_llm_log_crawler_to_db($crawlerType, $label[$crawlerType] ?? $crawlerType);
}

function shop_to_llm_get_recent_crawlers(): array
{
    $key = 'shop_to_llm_crawlers_' . gmdate('Y-m-d');
    $crawlers = get_transient($key);
    return is_array($crawlers) ? $crawlers : [];
}

function shop_to_llm_get_today_api_count(): int
{
    $key = 'shop_to_llm_count_' . gmdate('Y-m-d');
    return (int) get_transient($key);
}

// ─── Discovery Endpoints ───────────────────────────────────────────────

function shop_to_llm_mcp_discovery(): \WP_REST_Response
{
    $cached = get_transient('shop_to_llm_mcp_discovery');
    if (is_array($cached)) {
        return new \WP_REST_Response($cached);
    }

    $data = [
        'mcp' => [
            'version' => '2025-11-25',
            'transport' => 'streamable-http',
            'endpoint' => rest_url('shop2llm/v1/mcp'),
            'capabilities' => ['tools' => ['listChanged' => true]],
        ],
        'registry' => [
            'name' => get_bloginfo('name') . ' via Shop2LLM',
            'description' => shop_to_llm_has_woocommerce()
                ? 'WooCommerce store connected to AI assistants'
                : 'WordPress site connected to AI assistants',
        ],
        'server_info' => [
            'name' => get_bloginfo('name') . ' — Shop2LLM',
            'version' => SHOP_TO_LLM_VERSION,
        ],
    ];

    set_transient('shop_to_llm_mcp_discovery', $data, HOUR_IN_SECONDS);

    return new \WP_REST_Response($data);
}

// ─── Auth ──────────────────────────────────────────────────────────────

// Bypass WordPress REST auth for Shop2LLM routes.
// Priority 99999 ensures this runs after core auth checks.
add_filter('rest_authentication_errors', function ($result) {
    global $wp;
    $route = (isset($wp->query_vars) && is_array($wp->query_vars)) ? ($wp->query_vars['rest_route'] ?? '') : '';
    if ($route === '' && isset($_SERVER['REQUEST_URI'])) {
        $parsed = wp_parse_url(sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])), PHP_URL_PATH);
        $restPrefix = rest_get_url_prefix();
        if (is_string($parsed) && str_starts_with($parsed, '/' . $restPrefix . '/')) {
            $route = substr($parsed, strlen('/' . $restPrefix));
        }
    }
    if (str_starts_with($route, '/shop2llm/') || $route === '/shop2llm') {
        return true;
    }
    return $result;
}, 99999);

function shop_to_llm_check_auth(\WP_REST_Request $request): bool
{
    $storedKey = get_option('shop_to_llm_api_key', '');

    if ($storedKey === '') {
        return false;
    }

    $authHeader = $request->get_header('Authorization');
    if ($authHeader !== null && $authHeader !== '' && str_starts_with($authHeader, 'Bearer ')) {
        $token = substr($authHeader, 7);
        return hash_equals($storedKey, $token);
    }

    $apiKeyHeader = $request->get_header('X-Shop-To-LLm-Key');
    if ($apiKeyHeader !== null && $apiKeyHeader !== '') {
        if (hash_equals($storedKey, $apiKeyHeader)) {
            return true;
        }
        // Also accept Pro license key (used by cloud proxy)
        $proKey = get_option('shop_to_llm_pro_license_key', '');
        if ($proKey !== '' && hash_equals($proKey, $apiKeyHeader)) {
            return true;
        }
    }

    return false;
}

// ─── AI Crawler Detection ──────────────────────────────────────────────

function shop_to_llm_detect_ai_crawler(): void
{
    // Only run on frontend, not admin/AJAX/REST
    if (is_admin() || wp_doing_ajax() || defined('REST_REQUEST')) {
        return;
    }
    $ua = sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'] ?? ''));
    $crawlers = [
        'OAI-SearchBot' => 'oai-search',
        'ChatGPT-User' => 'chatgpt-user',
        'GPTBot' => 'gptbot',
        'ClaudeBot' => 'claude-bot',
        'PerplexityBot' => 'perplexity-bot',
        'Google-Extended' => 'google-extended',
        'bingbot' => 'bingbot',
        'Meta-ExternalAgent' => 'meta-external',
        'FacebookBot' => 'facebook-bot',
        'Amazonbot' => 'amazonbot',
        'Bytespider' => 'bytespider',
        'Applebot-Extended' => 'applebot-extended',
    ];
    foreach ($crawlers as $pattern => $type) {
        if (stripos($ua, $pattern) !== false) {
            // Dedup: skip if same crawler type reported in last 10 minutes
            $dedupKey = 'shop_to_llm_crawler_dedup_' . $type;
            if (get_transient($dedupKey)) {
                return;
            }
            set_transient($dedupKey, 1, 10 * MINUTE_IN_SECONDS);
            shop_to_llm_report_crawler($type);
            break;
        }
    }
}

function shop_to_llm_report_crawler(string $crawlerType): void
{
    shop_to_llm_log_crawler($crawlerType);

    // First crawler notification — show admin notice on next page load + email the admin
    if (!get_option('shop_to_llm_first_crawler_notified')) {
        $label = [
            'oai-search' => 'ChatGPT Search', 'chatgpt-user' => 'ChatGPT User',
            'gptbot' => 'GPTBot', 'claude-bot' => 'ClaudeBot',
            'perplexity-bot' => 'Perplexity', 'google-extended' => 'Google AI',
            'bingbot' => 'Bing', 'meta-external' => 'Meta AI',
            'facebook-bot' => 'Facebook', 'amazonbot' => 'Amazon Bot',
            'bytespider' => 'ByteDance', 'applebot-extended' => 'Apple Bot',
        ];
        $platformName = $label[$crawlerType] ?? $crawlerType;
        set_transient('shop_to_llm_first_crawler_admin_notice', $platformName, DAY_IN_SECONDS);
        update_option('shop_to_llm_first_crawler_notified', current_time('mysql'));
        update_option('shop_to_llm_first_crawler_platform', $platformName);

        // Email the admin so they come back to the dashboard even if not logged in
        $adminEmail = get_option('admin_email');
        if ($adminEmail) {
            $siteName = get_bloginfo('name');
            /* translators: 1: Site name, 2: AI platform name (e.g. ChatGPT) */
            $subject = sprintf(__('[%1$s] %2$s just discovered your site!', 'shop-to-llm'), $siteName, $platformName);
            $dashboardUrl = admin_url('admin.php?page=shop2llm');
            /* translators: 1: AI platform name, 2: Dashboard URL */
            $message = sprintf(
                __("Hi there,\n\nGreat news — %1\$s just crawled your site and can now read your content via Shop2LLM.\n\nThis means AI assistants like ChatGPT can now search and recommend your content to their users.\n\nSee your AI visibility dashboard:\n%2\$s\n\n— Shop2LLM", 'shop-to-llm'),
                $platformName,
                $dashboardUrl
            );
            wp_mail($adminEmail, $subject, $message);
        }
    }

    if (get_option('shop_to_llm_analytics', 'no') !== 'yes') {
        return;
    }

    // Circuit breaker: skip reporting if external API was failing
    if (get_transient('shop_to_llm_crawler_report_backoff')) {
        return;
    }

    $apiKey = get_option('shop_to_llm_api_key', '');
    $siteUrl = get_site_url();
    $requestUri = sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'] ?? ''));
    $ua = sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'] ?? ''));
    $result = wp_remote_post('https://api.shop2llm.com/fincore/v1/crawler/log', [
        'timeout' => 1,
        'blocking' => false,
        'headers' => ['Content-Type' => 'application/json'],
        'body' => json_encode([
            'license_key' => 'free_' . md5($siteUrl . $apiKey),
            'crawler_type' => $crawlerType,
            'page_url' => $siteUrl . $requestUri,
            'user_agent' => substr($ua, 0, 500),
            'response_code' => http_response_code() ?: 200,
        ]),
    ]);
    if (is_wp_error($result)) {
        $fails = (int) get_transient('shop_to_llm_crawler_report_fails');
        if ($fails > 3) {
            set_transient('shop_to_llm_crawler_report_backoff', 1, 5 * MINUTE_IN_SECONDS);
        } else {
            set_transient('shop_to_llm_crawler_report_fails', $fails + 1, MINUTE_IN_SECONDS);
        }
        if (defined('WP_DEBUG') && WP_DEBUG) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- guarded by WP_DEBUG, production-safe
            error_log('[Shop2LLM] Crawler report failed: ' . $result->get_error_message());
        }
    } else {
        // Reset fail counter on success
        delete_transient('shop_to_llm_crawler_report_fails');
    }
}

// ─── Product Schema (JSON-LD) ──────────────────────────────────────────

function shop_to_llm_inject_product_schema(): void
{
    // Skip if a known SEO plugin is active to avoid duplicate schema
    static $has_seo_plugin = null;
    if ($has_seo_plugin === null) {
        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- 'active_plugins' is a WordPress core hook
        $active = apply_filters('active_plugins', get_option('active_plugins', []));
        $seo_slugs = ['wordpress-seo/wp-seo.php', 'seo-by-rank-math/rank-math.php', 'all-in-one-seo-pack/all_in_one_seo_pack.php', 'wp-seopress/seopress.php'];
        $has_seo_plugin = (bool) array_intersect($seo_slugs, $active);
    }
    if ($has_seo_plugin) {
        return;
    }

    // WooCommerce product schema
    if (shop_to_llm_has_woocommerce() && is_product()) {
        global $product;
        if (!$product instanceof WC_Product) {
            return;
        }
        $schema = [
            '@context' => 'https://schema.org',
            '@type' => 'Product',
            'name' => $product->get_name(),
            'description' => wp_strip_all_tags($product->get_short_description() ?: $product->get_description()),
            'image' => wp_get_attachment_url($product->get_image_id()),
            'sku' => $product->get_sku(),
            'offers' => [
                '@type' => 'Offer',
                'url' => get_permalink($product->get_id()),
                'priceCurrency' => get_woocommerce_currency(),
                'price' => $product->get_price(),
                'availability' => $product->is_in_stock()
                    ? 'https://schema.org/InStock'
                    : 'https://schema.org/OutOfStock',
                'seller' => [
                    '@type' => 'Organization',
                    'name' => get_bloginfo('name'),
                ],
            ],
        ];
        if ($product->get_average_rating() > 0) {
            $schema['aggregateRating'] = [
                '@type' => 'AggregateRating',
                'ratingValue' => $product->get_average_rating(),
                'reviewCount' => $product->get_review_count(),
            ];
        }
        echo '<script type="application/ld+json">' . wp_json_encode($schema) . '</script>' . "\n";
        return;
    }

    // Non-WooCommerce: Article/BlogPosting schema on single posts
    if (!shop_to_llm_has_woocommerce() && is_singular('post')) {
        global $post;
        $schema = [
            '@context' => 'https://schema.org',
            '@type' => 'BlogPosting',
            'headline' => get_the_title($post),
            'description' => wp_strip_all_tags(get_the_excerpt($post) ?: wp_trim_words(wp_strip_all_tags($post->post_content), 30)),
            'datePublished' => get_the_date('c', $post),
            'dateModified' => get_the_modified_date('c', $post),
            'author' => [
                '@type' => 'Person',
                'name' => get_the_author_meta('display_name', $post->post_author),
            ],
            'publisher' => [
                '@type' => 'Organization',
                'name' => get_bloginfo('name'),
            ],
        ];
        $image = get_the_post_thumbnail_url($post, 'full');
        if ($image) {
            $schema['image'] = $image;
        }
        echo '<script type="application/ld+json">' . wp_json_encode($schema) . '</script>' . "\n";
    }
}

// ─── llms.txt Generation ───────────────────────────────────────────────

function shop_to_llm_generate_llms_txt(): void
{
    add_rewrite_rule('^llms\.txt$', 'index.php?shop_to_llm_llms_txt=1', 'top');
}

function shop_to_llm_llms_txt_query_var(array $vars): array
{
    $vars[] = 'shop_to_llm_llms_txt';
    return $vars;
}

function shop_to_llm_render_llms_txt(): void
{
    if (!get_query_var('shop_to_llm_llms_txt')) {
        return;
    }

    $cached = get_transient('shop_to_llm_llms_txt_output');
    if ($cached !== false) {
        header('Content-Type: text/plain; charset=UTF-8');
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
        echo $cached;
        exit;
    }

    $siteName = get_bloginfo('name');
    $siteUrl = get_site_url();
    $desc = get_bloginfo('description');
    $output = "# {$siteName}\n\n";
    $output .= "> {$desc}\n\n";
    $output .= shop_to_llm_has_woocommerce()
        ? "This store is connected to AI assistants via Shop2LLM.\n\n"
        : "This site is connected to AI assistants via Shop2LLM.\n\n";
    $output .= "## Products\n\n";
    if (shop_to_llm_has_woocommerce()) {
        $products = wc_get_products(['limit' => 100, 'status' => 'publish', 'orderby' => 'popularity']);
        foreach ($products as $product) {
            $price = wp_strip_all_tags($product->get_price_html());
            $stock = $product->is_in_stock() ? 'In Stock' : 'Out of Stock';
            $url = get_permalink($product->get_id());
            $output .= '- [' . esc_html($product->get_name()) . '](' . esc_url($url) . '): ' . esc_html($price) . ' — ' . esc_html($stock) . "\n";
        }
        $total = wp_count_posts('product')->publish ?? 0;
        if ($total > 100) {
            $output .= "\n... and " . ($total - 100) . " more products. Full catalog at " . esc_url($siteUrl . '/shop/') . "\n";
        }
    } else {
        $posts = get_posts(['numberposts' => 50, 'post_status' => 'publish', 'orderby' => 'date']);
        $output .= "## Posts\n\n";
        foreach ($posts as $post) {
            $title = esc_html($post->post_title);
            $url = esc_url(get_permalink($post->ID));
            $date = esc_html(get_the_date('', $post));
            $output .= "- [{$title}]({$url}) — {$date}\n";
        }
        $total = wp_count_posts('post')->publish ?? 0;
        if ($total > 50) {
            $output .= "\n... and " . ($total - 50) . " more posts.\n";
        }
    }
    $output .= "\n## AI Access\n\n";
    $output .= "- MCP Endpoint: " . esc_url($siteUrl . '/wp-json/shop2llm/v1/mcp') . "\n";
    $output .= "- AI Discovery: " . esc_url($siteUrl . '/wp-json/shop2llm/v1/.well-known/mcp') . "\n";
    $output .= "- Product Feed (XML): " . esc_url($siteUrl . '/product-feed.xml') . "\n";
    $output .= "- Product Feed (URL): " . esc_url($siteUrl . '/products.xml') . "\n";

    set_transient('shop_to_llm_llms_txt_output', $output, HOUR_IN_SECONDS);

    header('Content-Type: text/plain; charset=UTF-8');
    // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- plain text, all variables pre-escaped
    echo $output;
    exit;
}

// Invalidate llms.txt cache on product changes (WooCommerce only)
if (shop_to_llm_has_woocommerce()) {
    add_action('save_post_product', function () {
        delete_transient('shop_to_llm_llms_txt_output');
    });
    add_action('woocommerce_update_product', function () {
        delete_transient('shop_to_llm_llms_txt_output');
    });

    // Invalidate product quality cache on product changes
    add_action('save_post_product', function () {
        delete_transient('shop_to_llm_product_quality');
    });
    add_action('woocommerce_update_product', function () {
        delete_transient('shop_to_llm_product_quality');
    });
}

// ─── robots.txt Optimization ───────────────────────────────────────────

function shop_to_llm_robots_txt(string $output): string
{
    $lines = [
        'User-agent: OAI-SearchBot', 'Allow: /', '',
        'User-agent: ChatGPT-User', 'Allow: /', '',
        'User-agent: GPTBot', 'Allow: /', '',
        'User-agent: ClaudeBot', 'Allow: /', '',
        'User-agent: PerplexityBot', 'Allow: /', '',
        'User-agent: Google-Extended', 'Allow: /', '',
        'User-agent: bingbot', 'Allow: /', '',
        'User-agent: Meta-ExternalAgent', 'Allow: /', '',
        'User-agent: FacebookBot', 'Allow: /', '',
        'User-agent: Amazonbot', 'Allow: /', '',
        'User-agent: Bytespider', 'Allow: /', '',
        'User-agent: Applebot-Extended', 'Allow: /', '',
    ];
    $custom = implode("\n", $lines) . "\n";
    return $custom . $output;
}

// ─── AI Product Feed (Bing/ChatGPT/Google/Perplexity) ───────────────────

function shop_to_llm_product_feed_xml(): void
{
    if (!shop_to_llm_has_woocommerce()) {
        status_header(404);
        exit;
    }
    $products = wc_get_products(['limit' => 100, 'status' => 'publish', 'orderby' => 'date']);
    $storeUrl = get_site_url();
    $storeName = get_bloginfo('name');

    header('Content-Type: application/xml; charset=utf-8');
    echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
    echo '<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">' . "\n";
    echo '<channel>' . "\n";
    echo '<title>' . esc_xml($storeName) . '</title>' . "\n";
    echo '<link>' . esc_xml($storeUrl) . '</link>' . "\n";
    echo '<description>' . esc_xml(__('Shop2LLM product feed for ChatGPT, Bing Copilot, Google AI Overview, and Perplexity', 'shop-to-llm')) . '</description>' . "\n";

    foreach ($products as $p) {
        $pid = $p->get_id();
        $title = esc_xml(wp_strip_all_tags($p->get_name()));
        $desc = esc_xml(wp_strip_all_tags($p->get_short_description() ?: $p->get_description()));
        $link = esc_url(get_permalink($pid));
        $imgId = $p->get_image_id();
        $imgUrl = $imgId ? esc_url(wp_get_attachment_url($imgId)) : '';
        $price = $p->get_price();
        $currency = get_woocommerce_currency();
        $avail = esc_xml($p->is_in_stock() ? 'in stock' : 'out of stock');
        $sku = esc_xml($p->get_sku() ?: 'WC-' . $pid);
        $brand = esc_xml($storeName);
        $gtin = esc_xml($p->get_meta('_gtin') ?: $p->get_meta('_barcode') ?: '');

        echo '<item>' . "\n";
        echo '<g:id>' . $sku . '</g:id>' . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $sku already escaped via esc_xml()
        echo '<title>' . $title . '</title>' . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $title already escaped via esc_xml()
        echo '<description>' . ($desc ?: $title) . '</description>' . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $desc/$title already escaped via esc_xml()
        echo '<link>' . $link . '</link>' . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $link already escaped via esc_url()
        if ($imgUrl) {
            echo '<g:image_link>' . $imgUrl . '</g:image_link>' . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $imgUrl already escaped via esc_url()
        }
        if ($price) {
            echo '<g:price>' . esc_xml($price . ' ' . $currency) . '</g:price>' . "\n";
        }
        echo '<g:availability>' . $avail . '</g:availability>' . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $avail already escaped via esc_xml()
        if ($brand) {
            echo '<g:brand>' . $brand . '</g:brand>' . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $brand already escaped via esc_xml()
        }
        if ($gtin) {
            echo '<g:gtin>' . $gtin . '</g:gtin>' . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $gtin already escaped via esc_xml()
        }
        echo '<g:condition>new</g:condition>' . "\n";
        echo '</item>' . "\n";
    }

    echo '</channel>' . "\n";
    echo '</rss>' . "\n";
    exit;
}

function shop_to_llm_register_feed_rewrite(): void
{
    add_rewrite_rule('^product-feed\.xml$', 'index.php?shop_to_llm_feed=1', 'top');
    add_rewrite_rule('^products\.xml$', 'index.php?shop_to_llm_feed=1', 'top');
}
add_action('init', 'shop_to_llm_register_feed_rewrite');

add_filter('query_vars', function (array $vars): array {
    $vars[] = 'shop_to_llm_feed';
    return $vars;
});

add_action('template_redirect', function () {
    if (get_query_var('shop_to_llm_feed')) {
        shop_to_llm_product_feed_xml();
    }
});

// ─── AI Crawler Activity Monitor (Feature Anchor) ─────────────────────

/**
 * Create the crawler activity DB table for persistent 30-day tracking.
 * Called on plugin activation.
 */
function shop_to_llm_create_crawler_table(): void
{
    global $wpdb;
    $table = $wpdb->prefix . 'shop2llm_crawler_log';
    $charset = $wpdb->get_charset_collate();
    $sql = "CREATE TABLE IF NOT EXISTS $table (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        bot_name VARCHAR(50) NOT NULL,
        bot_label VARCHAR(50) NOT NULL,
        page_url VARCHAR(500) NOT NULL DEFAULT '',
        post_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
        visited_at DATETIME NOT NULL,
        PRIMARY KEY (id),
        KEY idx_bot (bot_name),
        KEY idx_date (visited_at),
        KEY idx_post (post_id)
    ) $charset;";
    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta($sql);

    // Add post_id column to existing installations
    $col = $wpdb->get_results("SHOW COLUMNS FROM $table LIKE 'post_id'"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    if (empty($col)) {
        $wpdb->query("ALTER TABLE $table ADD COLUMN post_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 AFTER page_url, ADD INDEX idx_post (post_id)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    }
}

/**
 * Persist a crawler visit to the DB table.
 */
function shop_to_llm_log_crawler_to_db(string $crawlerType, string $label): void
{
    global $wpdb;
    $table = $wpdb->prefix . 'shop2llm_crawler_log';
    $requestUri = sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'] ?? ''));
    $pageUrl = home_url($requestUri);
    // Truncate to fit column
    if (strlen($pageUrl) > 500) {
        $pageUrl = substr($pageUrl, 0, 500);
    }
    $postId = (int) url_to_postid($pageUrl);
    $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
        $table,
        [
            'bot_name' => $crawlerType,
            'bot_label' => $label,
            'page_url' => $pageUrl,
            'post_id' => $postId,
            'visited_at' => current_time('mysql'),
        ],
        ['%s', '%s', '%s', '%d', '%s']
    );
}

/**
 * Get crawler activity stats for the dashboard widget.
 */
function shop_to_llm_get_crawler_stats(): array
{
    global $wpdb;
    $table = $wpdb->prefix . 'shop2llm_crawler_log';

    // Check if table exists
    $tableExists = $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        "SHOW TABLES LIKE %s",
        $table
    ));
    if (!$tableExists) {
        return [
            'today_bots' => 0,
            'today_pages' => 0,
            'total_30d' => 0,
            'unique_bots_30d' => 0,
            'today_list' => [],
            'daily_counts' => [],
        ];
    }

    // Today's stats
    $today = current_time('Y-m-d');
    // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
    $todayRows = $wpdb->get_results($wpdb->prepare(
        "SELECT bot_label, COUNT(*) as cnt, MAX(visited_at) as last_visit
         FROM $table
         WHERE visited_at >= %s
         GROUP BY bot_label
         ORDER BY cnt DESC",
        $today . ' 00:00:00'
    ), ARRAY_A);
    // phpcs:enable

    $todayBots = count($todayRows);
    $todayPages = (int) $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        "SELECT COUNT(DISTINCT page_url) FROM $table WHERE visited_at >= %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $today . ' 00:00:00'
    ));

    // 30-day totals
    $thirtyDaysAgo = gmdate('Y-m-d H:i:s', time() - 30 * DAY_IN_SECONDS);
    $total30d = (int) $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        "SELECT COUNT(*) FROM $table WHERE visited_at >= %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $thirtyDaysAgo
    ));
    $uniqueBots30d = (int) $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        "SELECT COUNT(DISTINCT bot_name) FROM $table WHERE visited_at >= %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $thirtyDaysAgo
    ));

    // Daily counts for 7-day sparkline
    // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
    $dailyCounts = $wpdb->get_results($wpdb->prepare(
        "SELECT DATE(visited_at) as day, COUNT(*) as cnt
         FROM $table
         WHERE visited_at >= %s
         GROUP BY DATE(visited_at)
         ORDER BY day ASC",
        gmdate('Y-m-d H:i:s', time() - 7 * DAY_IN_SECONDS)
    ), ARRAY_A);
    // phpcs:enable

    // Fill missing days with 0
    $sparkline = [];
    for ($i = 6; $i >= 0; $i--) {
        $day = gmdate('Y-m-d', time() - $i * DAY_IN_SECONDS);
        $cnt = 0;
        foreach ($dailyCounts as $dc) {
            if ($dc['day'] === $day) {
                $cnt = (int) $dc['cnt'];
                break;
            }
        }
        $sparkline[] = $cnt;
    }

    return [
        'today_bots' => $todayBots,
        'today_pages' => $todayPages,
        'total_30d' => $total30d,
        'unique_bots_30d' => $uniqueBots30d,
        'today_list' => $todayRows,
        'sparkline' => $sparkline,
    ];
}

/**
 * Render the AI Crawler Activity section for the plugin page.
 */
function shop_to_llm_render_crawler_activity(): void
{
    $stats = shop_to_llm_get_crawler_stats();
    $maxVal = max($stats['sparkline'] ?: [1]);
    if ($maxVal === 0) {
        $maxVal = 1;
    }

    echo '<style>
    .s2ll-widget{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;box-sizing:border-box}
    .s2ll-widget *{box-sizing:border-box}
    .s2ll-summary{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
    .s2ll-big{font-size:28px;font-weight:700;color:#1e293b}
    .s2ll-label{font-size:13px;color:#64748b;margin-left:6px}
    .s2ll-link{font-size:12px;color:#6366f1;text-decoration:none;font-weight:500}
    .s2ll-sparkline{display:flex;align-items:flex-end;gap:4px;height:48px;margin-bottom:4px;padding:0 2px}
    .s2ll-bar{flex:1;border-radius:3px 3px 0 0;position:relative;min-width:0}
    .s2ll-bar-val{position:absolute;top:-18px;left:50%;transform:translateX(-50%);font-size:10px;color:#64748b;white-space:nowrap}
    .s2ll-axis{display:flex;justify-content:space-between;font-size:10px;color:#94a3b8;margin-bottom:14px}
    .s2ll-divider{border-top:1px solid #e2e8f0;padding-top:10px;margin-top:12px}
    .s2ll-bot-row{display:flex;align-items:center;gap:8px;margin-bottom:8px}
    .s2ll-bot-name{font-size:12px;font-weight:500;color:#1e293b;min-width:100px;flex-shrink:0}
    .s2ll-bot-track{flex:1;height:6px;background:#f1f5f9;border-radius:3px;overflow:hidden;min-width:40px}
    .s2ll-bot-fill{height:100%;background:linear-gradient(90deg,#6366f1,#8b5cf6);border-radius:3px}
    .s2ll-bot-cnt{font-size:11px;color:#64748b;min-width:50px;text-align:right;flex-shrink:0}
    .s2ll-footer{display:flex;justify-content:space-between;align-items:center}
    .s2ll-footer-text{font-size:11px;color:#64748b}
    .s2ll-footer-text strong{color:#1e293b}
    .s2ll-warning{margin-top:8px;padding:6px 10px;background:#fef2f2;border-radius:5px;font-size:11px;color:#dc2626}
    .s2ll-empty{text-align:center;padding:16px 0;color:#94a3b8;font-size:13px}
    .s2ll-empty-sub{font-size:11px}
    </style>';

    echo '<div class="s2ll-widget">';

    // Summary line
    echo '<div class="s2ll-summary">';
    echo '<div>';
    echo '<span class="s2ll-big">' . (int) $stats['today_bots'] . '</span>';
    echo '<span class="s2ll-label">bots today</span>';
    echo '<span class="s2ll-big" style="margin-left:20px">' . (int) $stats['today_pages'] . '</span>';
    echo '<span class="s2ll-label">pages crawled</span>';
    echo '</div>';
    echo '</div>';

    // 7-day sparkline
    echo '<div class="s2ll-sparkline">';
    foreach ($stats['sparkline'] as $val) {
        $height = $val > 0 ? max(8, ($val / $maxVal) * 48) : 2;
        $color = $val > 0 ? '#6366f1' : '#e2e8f0';
        echo '<div class="s2ll-bar" style="height:' . esc_attr($height) . 'px;background:' . esc_attr($color) . '" title="' . (int) $val . ' crawls">';
        if ($val > 0) {
            echo '<span class="s2ll-bar-val">' . (int) $val . '</span>';
        }
        echo '</div>';
    }
    echo '</div>';
    echo '<div class="s2ll-axis"><span>7 days ago</span><span>Today</span></div>';

    // Today's bot list
    if (!empty($stats['today_list'])) {
        echo '<div class="s2ll-divider">';
        foreach ($stats['today_list'] as $row) {
            $cnt = (int) $row['cnt'];
            $bar = min(100, $cnt * 10);
            echo '<div class="s2ll-bot-row">';
            echo '<span class="s2ll-bot-name">' . esc_html($row['bot_label']) . '</span>';
            echo '<div class="s2ll-bot-track"><div class="s2ll-bot-fill" style="width:' . esc_attr($bar) . '%"></div></div>';
            echo '<span class="s2ll-bot-cnt">' . esc_html($cnt) . ' pages</span>';
            echo '</div>';
        }
        echo '</div>';
    } else {
        echo '<div class="s2ll-empty">';
        echo '⏳ Waiting for AI crawlers to visit your site...';
        echo '<br><span class="s2ll-empty-sub">GPTBot, ClaudeBot, PerplexityBot and 9 others will appear here.</span>';
        echo '</div>';
    }

    // 30-day summary
    echo '<div class="s2ll-divider s2ll-footer">';
    echo '<div class="s2ll-footer-text">';
    echo '30-day total: <strong>' . (int) $stats['total_30d'] . '</strong> crawls · ';
    echo '<strong>' . (int) $stats['unique_bots_30d'] . '</strong> unique bots';
    echo '</div>';
    echo '</div>';

    // Uninstall deterrence
    if ($stats['total_30d'] > 0) {
        echo '<div class="s2ll-warning">⚠ This data is lost if you uninstall Shop2LLM.</div>';
    }

    echo '</div>';
}

function shop_to_llm_render_indexed_pages(): void
{
    global $wpdb;
    $table = $wpdb->prefix . 'shop2llm_crawler_log';
    $exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    if (!$exists) return;

    // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
    $recent = $wpdb->get_results(
        "SELECT cl.post_id, cl.bot_label, cl.page_url, MAX(cl.visited_at) as last_visit
         FROM {$table} cl
         WHERE cl.post_id > 0
         GROUP BY cl.post_id, cl.bot_label
         ORDER BY last_visit DESC
         LIMIT 10"
    );
    // phpcs:enable

    if (empty($recent)) {
        echo '<p style="color:#94a3b8;font-size:12px;margin:0;line-height:1.5">'
            . esc_html__('No pages indexed yet. AI crawlers will discover your content over time.', 'shop-to-llm')
            . '</p>';
        return;
    }

    echo '<div style="margin-top:12px;padding-top:10px;border-top:1px solid #e2e8f0">';
    echo '<strong style="font-size:12px;color:#64748b;display:block;margin-bottom:8px">'
        . esc_html__('Recently Indexed Pages', 'shop-to-llm') . '</strong>';
    echo '<table style="width:100%;border-collapse:collapse;font-size:12px">';
    echo '<thead><tr style="border-bottom:1px solid #e2e8f0">'
        . '<th style="text-align:left;padding:4px;color:#64748b;font-weight:600">' . esc_html__('Page', 'shop-to-llm') . '</th>'
        . '<th style="text-align:left;padding:4px;color:#64748b;font-weight:600">' . esc_html__('Crawler', 'shop-to-llm') . '</th>'
        . '<th style="text-align:right;padding:4px;color:#64748b;font-weight:600">' . esc_html__('Last Visit', 'shop-to-llm') . '</th>'
        . '</tr></thead><tbody>';

    foreach ($recent as $row) {
        $title = $row->post_id ? get_the_title($row->post_id) : wp_parse_url($row->page_url, PHP_URL_PATH);
        if (empty($title) || $title === '') $title = '/';
        echo '<tr style="border-bottom:1px solid #f1f5f9">';
        echo '<td style="padding:4px;color:#1e1e1e;font-weight:500;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'
            . esc_html(mb_strlen($title) > 40 ? mb_substr($title, 0, 37) . '...' : $title) . '</td>';
        echo '<td style="padding:4px;color:#475569">' . esc_html($row->bot_label) . '</td>';
        echo '<td style="padding:4px;text-align:right;color:#94a3b8">'
            . esc_html(human_time_diff(strtotime($row->last_visit), time()) . ' ' . __('ago', 'shop-to-llm')) . '</td>';
        echo '</tr>';
    }
    echo '</tbody></table></div>';
}

/**
 * Daily cleanup of crawler logs older than 30 days.
 */
function shop_to_llm_cleanup_old_crawler_logs(): void
{
    global $wpdb;
    $cutoff = gmdate('Y-m-d H:i:s', time() - 30 * DAY_IN_SECONDS);

    // Clean crawler log
    $table = $wpdb->prefix . 'shop2llm_crawler_log';
    $wpdb->query($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        "DELETE FROM $table WHERE visited_at < %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $cutoff
    ));

    // Clean AI referral log
    $refTable = $wpdb->prefix . 'shop2llm_ai_referrals';
    $refTableExists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $refTable)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    if ($refTableExists) {
        $wpdb->query($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
            "DELETE FROM $refTable WHERE visited_at < %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
            $cutoff
        ));
    }
}

// ─── P0: Admin Bar Real-time Badge ─────────────────────────────────────

function shop_to_llm_admin_bar_crawler_badge(\WP_Admin_Bar $wpAdminBar): void
{
    if (!current_user_can('manage_options')) {
        return;
    }

    // Get the cached 7-factor visibility score
    $scoreTransient = 'shop2llm_admin_bar_score';
    $score = get_transient($scoreTransient);
    if ($score === false) {
        $score = shop_to_llm_get_visibility_score();
        set_transient($scoreTransient, $score, 15 * MINUTE_IN_SECONDS);
    }

    $total = $score['total'];
    $color = $score['color'];
    $bg = $score['bg'];
    $label = $score['label'];

    // Infinity icon — Free always purple (#7c3aed), Pro has its own badge
    $icon = '<span style="display:inline-block;vertical-align:middle;margin-right:3px"><svg width="12" height="12" viewBox="0 0 20 20" style="vertical-align:middle"><path d="M5.5 7C3.5 7 2 8.5 2 10s1.5 3 3.5 3c1.2 0 2-.5 3-1.5L10 10l1.5 1.5c1 1 1.8 1.5 3 1.5 2 0 3.5-1.5 3.5-3S16.5 7 14.5 7c-1.2 0-2 .5-3 1.5L10 10 8.5 8.5C7.5 7.5 6.7 7 5.5 7z" fill="#7c3aed"/></svg></span>';

    $wpAdminBar->add_node([
        'id' => 'shop2llm-crawler-badge',
        'title' => $icon . ' <span style="font-weight:700;color:#fff;background:' . esc_attr($color) . ';border-radius:3px;padding:0 5px;font-size:11px;line-height:18px;display:inline-block;vertical-align:middle">' . (int) $total . '/100</span> <span style="font-size:10px;color:#94a3b8;vertical-align:middle">' . esc_html($label) . '</span>',
        'href' => admin_url('admin.php?page=shop2llm'),
        'meta' => [
            'title' => __('Shop2LLM AI Score', 'shop-to-llm'),
        ],
    ]);
}

// ─── P0: Dashboard Widget Recovery ─────────────────────────────────────

function shop_to_llm_register_dashboard_widget(): void
{
    wp_add_dashboard_widget(
        'shop2llm_crawler_activity',
        'Shop2LLM Visibility',
        'shop_to_llm_render_dashboard_widget_compact',
        null,
        null,
        'normal',
        'high'
    );

    // Move widget to top of dashboard main column
    global $wp_meta_boxes;
    if (isset($wp_meta_boxes['dashboard']['normal']['high']['shop2llm_crawler_activity'])) {
        $widget = $wp_meta_boxes['dashboard']['normal']['high']['shop2llm_crawler_activity'];
        unset($wp_meta_boxes['dashboard']['normal']['high']['shop2llm_crawler_activity']);
        $wp_meta_boxes['dashboard']['normal']['high'] = array_merge(
            ['shop2llm_crawler_activity' => $widget],
            $wp_meta_boxes['dashboard']['normal']['high']
        );
    }
}

function shop_to_llm_render_dashboard_widget_compact(): void
{
    $stats = shop_to_llm_get_crawler_stats();
    $maxVal = max($stats['sparkline'] ?: [1]);
    if ($maxVal === 0) {
        $maxVal = 1;
    }

    echo '<div class="s2ll-widget">';

    // Compact summary
    echo '<div class="s2ll-summary">';
    echo '<div>';
    echo '<span class="s2ll-big">' . (int) $stats['today_bots'] . '</span>';
    echo '<span class="s2ll-label">bots</span>';
    echo '<span class="s2ll-big" style="margin-left:16px">' . (int) $stats['today_pages'] . '</span>';
    echo '<span class="s2ll-label">pages today</span>';
    echo '</div>';
    echo '</div>';

    // 7-day sparkline
    echo '<div class="s2ll-sparkline">';
    foreach ($stats['sparkline'] as $val) {
        $height = $val > 0 ? max(8, ($val / $maxVal) * 48) : 2;
        $color = $val > 0 ? '#6366f1' : '#e2e8f0';
        echo '<div class="s2ll-bar" style="height:' . esc_attr($height) . 'px;background:' . esc_attr($color) . '" title="' . (int) $val . ' crawls"></div>';
    }
    echo '</div>';
    echo '<div class="s2ll-axis"><span>7d ago</span><span>Today</span></div>';

    // View details link
    echo '<div style="text-align:right;margin-top:8px">';
    echo '<a href="' . esc_url(admin_url('admin.php?page=shop2llm')) . '" class="s2ll-link">View Details →</a>';
    echo '</div>';

    // AI referral compact
    $refStats = shop_to_llm_get_ai_referral_stats();
    $refThisMonth = (int) $refStats['this_month'];
    if ($refThisMonth > 0) {
        echo '<div class="s2ll-divider" style="padding-top:8px;margin-top:8px">';
        echo '<span style="font-size:11px;color:#64748b">🚀 <strong style="color:#10b981">' . esc_html($refThisMonth) . '</strong> AI-driven visits this month</span>';
        echo '</div>';
    }

    echo '</div>';
}

// ─── P0: Uninstall Confirmation Dialog ─────────────────────────────────

function shop_to_llm_uninstall_confirmation_js(): void
{
    $pluginFile = plugin_basename(__FILE__);

    // Gather quantitative loss data
    global $wpdb;
    $crawlerRecords = 0;
    $refRecords = 0;
    $historyWeeks = 0;
    $focusQueries = 0;

    $cTable = $wpdb->prefix . 'shop2llm_crawler_log';
    if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $cTable))) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $crawlerRecords = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$cTable}"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
    }
    $rTable = $wpdb->prefix . 'shop2llm_ai_referrals';
    if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $rTable))) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $refRecords = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$rTable}"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
    }
    $fqCount = (int) $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value != ''", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        '_shop2llm_focus_query'
    ));

    // Build quantified warning
    $lossItems = [];
    if ($crawlerRecords > 0) {
        $lossItems[] = sprintf(/* translators: %d: number of AI crawler visit records */ __('%d AI crawler visit records', 'shop-to-llm'), $crawlerRecords);
    }
    if ($refRecords > 0) {
        $lossItems[] = sprintf(/* translators: %d: number of AI traffic tracking records */ __('%d AI traffic tracking records', 'shop-to-llm'), $refRecords);
    }
    if ($fqCount > 0) {
        $lossItems[] = sprintf(/* translators: %d: number of Focus Query configurations */ __('%d Focus Query configurations', 'shop-to-llm'), $fqCount);
    }
    $lossItems[] = __('all AI visibility scores and trend history', 'shop-to-llm');

    $quantifiedMsg = '';
    if (count($lossItems) > 1) {
        $last = array_pop($lossItems);
        $quantifiedMsg = implode(', ', $lossItems) . ' ' . __('and', 'shop-to-llm') . ' ' . $last;
    } else {
        $quantifiedMsg = $lossItems[0];
    }

    $msg = sprintf(
        /* translators: %s: list of data that will be lost */
        __('⚠ You will permanently lose: %s. This data cannot be recovered. Are you sure you want to uninstall?', 'shop-to-llm'),
        $quantifiedMsg
    );
    ?>
    <script>
    (function() {
        var links = document.querySelectorAll('a.delete');
        links.forEach(function(link) {
            if (link.href && link.href.indexOf('<?php echo esc_js($pluginFile); ?>') !== -1) {
                link.addEventListener('click', function(e) {
                    if (!confirm(<?php echo json_encode($msg); ?>)) {
                        e.preventDefault();
                        e.stopPropagation();
                        return false;
                    }
                });
            }
        });
    })();
    </script>
    <?php
}

// ─── P1: AI Visibility Score Meta Box ──────────────────────────────────

function shop_to_llm_add_visibility_meta_box(): void
{
    add_meta_box(
        'shop2llm_visibility_score',
        __('Shop2LLM Visibility Score', 'shop-to-llm'),
        'shop_to_llm_render_visibility_meta_box',
        'post',
        'side',
        'high'
    );
    add_meta_box(
        'shop2llm_visibility_score',
        __('Shop2LLM Visibility Score', 'shop-to-llm'),
        'shop_to_llm_render_visibility_meta_box',
        'product',
        'side',
        'high'
    );
}

function shop_to_llm_visibility_meta_box_css(): void
{
    $screen = get_current_screen();
    if (!$screen || !in_array($screen->base, ['post', 'edit'], true)) {
        return;
    }
    echo '<style>
    /* Score ring gauge */
    .s2ll-score-ring{width:64px;height:64px;border-radius:50%;display:flex;align-items:center;justify-content:center;position:relative;margin:0 auto 8px}
    .s2ll-score-ring::before{content:"";position:absolute;inset:0;border-radius:50%;background:conic-gradient(var(--s2ll-ring-color, #16a34a) calc(var(--s2ll-ring-pct, 0) * 1%), #e2e8f0 0)}
    .s2ll-score-ring::after{content:"";position:absolute;inset:6px;border-radius:50%;background:#fff}
    .s2ll-score-ring-value{position:relative;z-index:1;font-size:18px;font-weight:700;color:#1e293b}
    .s2ll-score-ring--red{--s2ll-ring-color:#dc2626}
    .s2ll-score-ring--yellow{--s2ll-ring-color:#f59e0b}
    .s2ll-score-ring--green{--s2ll-ring-color:#16a34a}
    .s2ll-subtitle{font-size:10px;color:#94a3b8;text-align:center;margin-bottom:10px;display:block}

    /* Severity grouping */
    .s2ll-severity-group{margin:8px 0}
    .s2ll-severity-header{display:flex;align-items:center;gap:6px;font-size:13px;font-weight:600;cursor:pointer;padding:4px 0;user-select:none}
    .s2ll-severity-header:hover{opacity:0.85}
    .s2ll-severity-arrow{transition:transform 0.2s;font-size:10px;color:#94a3b8}
    .s2ll-severity-group[open] .s2ll-severity-arrow{transform:rotate(90deg)}
    .s2ll-severity-count{font-size:11px;font-weight:400;color:#94a3b8;margin-left:4px}
    .s2ll-severity-problems .s2ll-severity-header{color:#dc2626}
    .s2ll-severity-improvements .s2ll-severity-header{color:#d97706}
    .s2ll-severity-good .s2ll-severity-header{color:#16a34a}

    .s2ll-finding{display:flex;align-items:flex-start;gap:6px;padding:4px 0 4px 16px;font-size:12px;line-height:1.5}
    .s2ll-finding-icon{flex-shrink:0;margin-top:1px;font-size:13px}
    .s2ll-finding-body{flex:1;min-width:0}
    .s2ll-finding-text{color:#475569}
    .s2ll-finding-fix{display:inline-block;font-size:11px;color:#6366f1;cursor:pointer;text-decoration:underline;margin-top:2px}
    .s2ll-finding-fix:hover{color:#4f46e5}
    .s2ll-finding-pts{flex-shrink:0;font-size:10px;color:#94a3b8;margin-top:1px;text-align:right;min-width:24px}
    .s2ll-finding--locked{opacity:0.5}

    /* Dimension bars */
    .s2ll-dim-section{margin:10px 0;padding-top:8px;border-top:1px solid #e2e8f0}
    .s2ll-dim-section-title{font-size:11px;color:#94a3b8;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:6px}
    .s2ll-dimension-bar{display:flex;align-items:center;gap:6px;margin:3px 0;font-size:12px}
    .s2ll-dimension-label{width:105px;color:#64748b;flex-shrink:0;font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
    .s2ll-dimension-track{flex:1;height:6px;background:#e2e8f0;border-radius:3px;overflow:hidden}
    .s2ll-dimension-fill{height:100%;border-radius:3px;transition:width 0.3s}
    .s2ll-dimension-score{width:42px;text-align:right;font-weight:600;flex-shrink:0;font-size:11px}

    /* Focus query */
    .s2ll-focus-query-wrap{margin-bottom:10px}
    .s2ll-focus-query-help{display:block;font-size:11px;color:#94a3b8;margin-top:2px}
    .s2ll-unlock-hint{display:block;font-size:11px;color:#6366f1;margin:1px 0 0 105px}

    /* Trend sparkline */
    .s2ll-sparkline{display:flex;align-items:flex-end;gap:3px;height:28px}
    .s2ll-sparkline-bar{width:6px;border-radius:2px 2px 0 0;background:#6366f1;min-height:2px;transition:height 0.3s}

    /* Trend section */
    .s2ll-trend-section{margin-top:10px;padding-top:8px;border-top:1px solid #e2e8f0}
    .s2ll-trend-header{display:flex;align-items:center;gap:8px;margin-bottom:4px}
    .s2ll-trend-title{font-size:12px;color:#64748b;font-weight:600;flex-shrink:0}
    .s2ll-trend-values{display:flex;align-items:center;gap:8px;font-size:12px;flex-wrap:wrap;margin-top:4px}
    .s2ll-trend-current{color:#334155}
    .s2ll-trend-prev{color:#94a3b8}
    .s2ll-trend-change{font-size:12px}
    .s2ll-trend-warning{margin-top:4px;font-size:11px;color:#dc2626;font-weight:600}
    .s2ll-trend-pending{font-size:11px;color:#94a3b8;margin-top:4px}

    /* Crawler info */
    .s2ll-crawler-info{margin-top:8px;padding:6px 8px;background:#f0f9ff;border:1px solid #bae6fd;border-radius:5px;font-size:11px;color:#0369a1;line-height:1.5}

    /* Auth list */
    .s2ll-auth-list-wrap{margin:4px 0}

    /* Tooltip */
    .s2ll-help{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:50%;background:#7c3aed;color:#fff;font-size:10px;font-weight:700;cursor:help;flex-shrink:0;vertical-align:middle;margin-left:6px}
    .s2ll-help::before{content:"?"}

    .s2ll-suggestion{font-size:11px;color:#6366f1;margin-top:10px;padding-top:10px;border-top:1px solid #f1f5f9;line-height:1.5}

    /* Criterion (legacy - keep for settings page) */
    .s2ll-criterion{display:flex;align-items:center;gap:8px;font-size:13px;margin-bottom:6px}
    .s2ll-criterion-icon{font-size:14px;flex-shrink:0}
    .s2ll-criterion-text{color:#475569}
    .s2ll-criterion-pts{margin-left:auto;color:#94a3b8;font-size:11px}
    </style>';
}

function shop_to_llm_render_visibility_meta_box(\WP_Post $post): void
{
    $focusQuery = get_post_meta($post->ID, '_shop2llm_focus_query', true);
    $hasFocusQuery = !empty(trim((string) $focusQuery));
    $onAuthList = get_post_meta($post->ID, '_shop2llm_on_authoritative_list', true);

    // Focus Query input
    wp_nonce_field('shop2llm_save_focus_query', 'shop2llm_focus_query_nonce');
    echo '<div class="s2ll-focus-query-wrap">';
    echo '<label for="shop2llm_focus_query" style="font-size:13px;font-weight:600;display:block;margin-bottom:4px">' . esc_html__('Focus Query', 'shop-to-llm') . '</label>';
    echo '<input type="text" id="shop2llm_focus_query" name="shop2llm_focus_query" value="' . esc_attr($focusQuery) . '" placeholder="' . esc_attr__('e.g., best wireless earbuds', 'shop-to-llm') . '" style="width:100%;margin-bottom:4px" />';
    echo '<span class="s2ll-focus-query-help">' . esc_html__('What should AI assistants recommend you for?', 'shop-to-llm') . '</span>';
    echo '</div>';

    // Authoritative list checkbox
    echo '<div class="s2ll-auth-list-wrap">';
    echo '<label style="font-size:12px;display:flex;align-items:center;gap:4px">';
    echo '<input type="checkbox" name="shop2llm_on_authoritative_list" value="1" ' . checked($onAuthList, '1', false) . ' /> ';
    echo esc_html__('My brand appears on authoritative lists', 'shop-to-llm');
    echo '</label>';
    echo '</div>';

    $score = 0;

    // ─── Use canonical 25-item scoring function ──
    $scoring = shop_to_llm_score_single_post($post->ID);
    $allItems = $scoring['items'];
    $totalScore = $scoring['total'];
    $hasFocusQuery = $scoring['has_focus'];

    // Split items into L1-L5 groups
    $l1Items = array_slice($allItems, 0, 6);
    $l2Items = array_slice($allItems, 6, 6);
    $l3Items = array_slice($allItems, 12, 6);
    $l4Items = array_slice($allItems, 18, 3);
    $l5Items = array_slice($allItems, 21, 4);

    $l1 = array_sum(array_column($l1Items, 'pts'));
    $l2 = array_sum(array_column($l2Items, 'pts'));
    $l3 = array_sum(array_column($l3Items, 'pts'));
    $l4 = array_sum(array_column($l4Items, 'pts'));
    $l5 = array_sum(array_column($l5Items, 'pts'));

    $score = $totalScore;
    $content = $post->post_content; // kept for downstream rendering

    echo '<div class="s2ll-widget">';

    // ─── Ring Gauge Score ────────────────────────────────────────────
    $ringClass = $score >= 71 ? 's2ll-score-ring--green' : ($score >= 41 ? 's2ll-score-ring--yellow' : 's2ll-score-ring--red');
    echo '<div class="s2ll-score-ring ' . esc_attr($ringClass) . '" style="--s2ll-ring-pct:' . (int) $score . '">';
    echo '<span class="s2ll-score-ring-value">' . (int) $score . '</span>';
    echo '</div>';
    echo '<span class="s2ll-subtitle">Shop2LLM™ 5-Dimension Analysis</span>';

    // SEO plugin score integration
    $seoScore = 0;
    $seoPlugin = '';
    if (get_post_meta($post->ID, '_yoast_wpseo_linkdex', true) !== '') {
        $seoScore = (int) get_post_meta($post->ID, '_yoast_wpseo_linkdex', true);
        $seoPlugin = 'Yoast SEO';
    } elseif (get_post_meta($post->ID, 'rank_math_seo_score', true) !== '') {
        $rankMathScore = (int) get_post_meta($post->ID, 'rank_math_seo_score', true);
        $seoScore = min(100, max(0, $rankMathScore));
        $seoPlugin = 'Rank Math';
    }
    if ($seoScore > 0 && $seoPlugin) {
        $seoColor = $seoScore >= 71 ? '#16a34a' : ($seoScore >= 41 ? '#f59e0b' : '#dc2626');
        echo '<div style="display:flex;align-items:center;justify-content:center;gap:6px;margin-bottom:4px;font-size:11px">';
        echo '<span style="color:#94a3b8">' . esc_html($seoPlugin) . ':</span>';
        echo '<span style="color:' . esc_attr($seoColor) . ';font-weight:600">' . (int) $seoScore . '/100</span>';
        echo '</div>';
    } else {
        // No SEO plugin installed — show helpful hint
        echo '<div style="text-align:center;margin-bottom:4px;font-size:10px;color:#94a3b8;line-height:1.4">';
        echo esc_html__('Install Yoast SEO or Rank Math for a complete content health score', 'shop-to-llm');
        echo '</div>';
    }

    // ─── Fix guidance lookup ─────────────────────────────────────────
    $settingsUrl = admin_url('admin.php?page=shop2llm');

    $fixGuidance = [
        1  => ['fail' => __('llms.txt is empty', 'shop-to-llm'),                               'fix' => __('Go to Settings', 'shop-to-llm'),   'url' => $settingsUrl],
        2  => ['fail' => __('robots.txt may block AI crawlers', 'shop-to-llm'),                 'fix' => __('Fix robots.txt', 'shop-to-llm'),   'url' => $settingsUrl],
        3  => ['fail' => __('Schema markup missing content', 'shop-to-llm'),                    'fix' => __('Add structured data', 'shop-to-llm'), 'url' => ''],
        4  => ['fail' => __('No AI tools connected — store invisible to AI', 'shop-to-llm'),   'fix' => __('Connect AI tools', 'shop-to-llm'), 'url' => $settingsUrl],
        5  => ['fail' => __('Page has heavy JS — may be unreadable to AI', 'shop-to-llm'),     'fix' => __('Check page source', 'shop-to-llm'), 'url' => ''],
        6  => ['fail' => __('No FAQ or Q&A section found', 'shop-to-llm'),                     'fix' => __('Add FAQ block', 'shop-to-llm'),    'url' => ''],
        7  => ['fail' => __('Search keyword not in title', 'shop-to-llm'),                     'fix' => __('Edit title', 'shop-to-llm'),       'url' => ''],
        8  => ['fail' => __('Search keyword not in heading (H1/H2)', 'shop-to-llm'),           'fix' => __('Add heading with keyword', 'shop-to-llm'), 'url' => ''],
        9  => ['fail' => __('Keyword not in opening paragraph', 'shop-to-llm'),                'fix' => __('Add keyword near top', 'shop-to-llm'), 'url' => ''],
        10 => ['fail' => __('Keyword not in URL/slug', 'shop-to-llm'),                         'fix' => __('Edit slug', 'shop-to-llm'),        'url' => ''],
        11 => ['fail' => __('Keyword not in meta description', 'shop-to-llm'),                 'fix' => __('Set SEO description', 'shop-to-llm'), 'url' => ''],
        12 => ['fail' => __('No alt text with keyword on images', 'shop-to-llm'),              'fix' => __('Add alt text', 'shop-to-llm'),     'url' => ''],
        13 => ['fail' => __('Content too short (<150 words)', 'shop-to-llm'),                  'fix' => __('Add more content', 'shop-to-llm'), 'url' => ''],
        14 => ['fail' => __('No statistics or data found — AI prefers data-backed content', 'shop-to-llm'), 'fix' => __('Add numbers & data', 'shop-to-llm'), 'url' => ''],
        15 => ['fail' => __('No citations or references — AI prefers verifiable content', 'shop-to-llm'), 'fix' => __('Add quotes & sources', 'shop-to-llm'), 'url' => ''],
        16 => ['fail' => __('Content was updated >90 days ago — AI prefers fresh content', 'shop-to-llm'), 'fix' => __('Update content', 'shop-to-llm'), 'url' => ''],
        17 => ['fail' => __('Missing title/thumbnail/excerpt for schema', 'shop-to-llm'),      'fix' => __('Fill in missing fields', 'shop-to-llm'), 'url' => ''],
        18 => ['fail' => __('Too few internal links (need 3+)', 'shop-to-llm'),                'fix' => __('Link to related content', 'shop-to-llm'), 'url' => ''],
        19 => ['fail' => __('Brand not found in knowledge graph', 'shop-to-llm'),              'fix' => __('Enable entity checks', 'shop-to-llm'), 'url' => $settingsUrl],
        20 => ['fail' => __('Not mentioned on Wikipedia', 'shop-to-llm'),                      'fix' => __('Enable entity checks', 'shop-to-llm'), 'url' => $settingsUrl],
        21 => ['fail' => __('Not marked as on authoritative lists', 'shop-to-llm'),            'fix' => __('Check box above', 'shop-to-llm'), 'url' => ''],
        22 => ['fail' => __('No recent AI crawler visits', 'shop-to-llm'),                     'fix' => __('Wait for crawler activity', 'shop-to-llm'), 'url' => ''],
        23 => ['fail' => __('No reviews yet', 'shop-to-llm'),                                  'fix' => __('Encourage reviews', 'shop-to-llm'), 'url' => ''],
        24 => ['fail' => __('Seen by <4 AI platforms', 'shop-to-llm'),                         'fix' => __('Connect more AI tools', 'shop-to-llm'), 'url' => $settingsUrl],
        25 => ['fail' => __('Keyword density outside 0.5-2.5%', 'shop-to-llm'),                'fix' => __('Adjust keyword usage', 'shop-to-llm'), 'url' => ''],
    ];

    // User-friendly item names (replacing technical labels)
    $itemUserNames = [
        1  => __('llms.txt file is accessible', 'shop-to-llm'),
        2  => __('robots.txt allows AI crawlers', 'shop-to-llm'),
        3  => __('Structured data (Schema) present', 'shop-to-llm'),
        4  => __('AI tools connected to store', 'shop-to-llm'),
        5  => __('Page content readable without JavaScript', 'shop-to-llm'),
        6  => __('FAQ or Q&A section included', 'shop-to-llm'),
        7  => __('Search keyword appears in title', 'shop-to-llm'),
        8  => __('Search keyword appears in heading', 'shop-to-llm'),
        9  => __('Search keyword in first paragraph', 'shop-to-llm'),
        10 => __('Search keyword in URL address', 'shop-to-llm'),
        11 => __('Search keyword in meta description', 'shop-to-llm'),
        12 => __('Search keyword in image alt text', 'shop-to-llm'),
        13 => __('Content length over 300 words', 'shop-to-llm'),
        14 => __('Includes statistics and data', 'shop-to-llm'),
        15 => __('Includes citations and references', 'shop-to-llm'),
        16 => __('Content updated within 30 days', 'shop-to-llm'),
        17 => __('Complete metadata (title, image, excerpt)', 'shop-to-llm'),
        18 => __('Internal links to related pages', 'shop-to-llm'),
        19 => __('Brand recognized in knowledge graph', 'shop-to-llm'),
        20 => __('Brand mentioned on Wikipedia', 'shop-to-llm'),
        21 => __('Appears on authoritative lists', 'shop-to-llm'),
        22 => __('Recent AI crawler activity', 'shop-to-llm'),
        23 => __('Customer reviews exist', 'shop-to-llm'),
        24 => __('Indexed by multiple AI platforms', 'shop-to-llm'),
        25 => __('Keyword density in optimal range', 'shop-to-llm'),
    ];

    // Tooltip explanations for technical terms
    $tooltips = [
        5  => __('AI reads raw HTML, not rendered pages. Heavy JavaScript can hide content.', 'shop-to-llm'),
        4  => __('MCP (Model Context Protocol) lets ChatGPT and other AI tools directly query your store.', 'shop-to-llm'),
        1  => __('llms.txt is a standard file that tells AI models what your website contains.', 'shop-to-llm'),
    ];

    // ─── Build all 25 items with metadata ────────────────────────────
    $allIndexed = [];
    $idx = 1;
    foreach ([$l1Items, $l2Items, $l3Items, $l4Items, $l5Items] as $dimItems) {
        foreach ($dimItems as $item) {
            $allIndexed[$idx] = $item;
            $idx++;
        }
    }

    // ─── Classify into Problems / Improvements / Good ─────────────────
    $problems = [];     // fail + has fix (no lock)
    $improvements = []; // pass but pts < max, or locked items
    $good = [];         // pass with full pts

    foreach ($allIndexed as $i => $item) {
        $locked = !empty($item['locked']);
        if ($locked) {
            $improvements[] = ['index' => $i, 'item' => $item, 'locked' => true];
        } elseif (!$item['pass']) {
            $problems[] = ['index' => $i, 'item' => $item, 'locked' => false];
        } elseif ($item['pts'] < 1) {
            // Passed but scored 0 pts — improvement opportunity
            $improvements[] = ['index' => $i, 'item' => $item, 'locked' => false];
        } else {
            $good[] = ['index' => $i, 'item' => $item, 'locked' => false];
        }
    }

    // Helper to render a single finding row
    $renderFinding = function (array $entry) use ($fixGuidance, $itemUserNames, $tooltips, $settingsUrl) {
        $i = $entry['index'];
        $item = $entry['item'];
        $locked = $entry['locked'];
        $name = $itemUserNames[$i] ?? $item['name'];
        $guidance = $fixGuidance[$i] ?? null;
        $tooltip = $tooltips[$i] ?? '';

        echo '<div class="s2ll-finding' . ($locked ? ' s2ll-finding--locked' : '') . '">';

        // Icon
        if ($locked) {
            echo '<span class="s2ll-finding-icon">🔒</span>';
        } elseif ($item['pass']) {
            echo '<span class="s2ll-finding-icon" style="color:#16a34a">✓</span>';
        } else {
            echo '<span class="s2ll-finding-icon" style="color:#dc2626">✗</span>';
        }

        // Body
        echo '<span class="s2ll-finding-body">';
        if ($locked) {
            echo '<span class="s2ll-finding-text" style="color:#94a3b8">' . esc_html($name) . '</span>';
        } elseif (!$item['pass'] && $guidance) {
            echo '<span class="s2ll-finding-text">' . esc_html($guidance['fail']) . '</span>';
            if (!empty($guidance['fix'])) {
                $url = !empty($guidance['url']) ? esc_url($guidance['url']) : '';
                echo ' ';
                if ($url) {
                    echo '<a href="' . $url . '" class="s2ll-finding-fix" target="_blank">' . esc_html($guidance['fix']) . ' →</a>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                } else {
                    echo '<span class="s2ll-finding-fix">' . esc_html($guidance['fix']) . '</span>';
                }
            }
        } else {
            echo '<span class="s2ll-finding-text" style="color:#16a34a">' . esc_html($name) . '</span>';
        }

        // Tooltip
        if ($tooltip) {
            echo '<span class="s2ll-help" title="' . esc_attr($tooltip) . '">?</span>';
        }
        echo '</span>';

        // Points
        $ptsStr = $locked ? '—' : '+' . (int) $item['pts'];
        echo '<span class="s2ll-finding-pts">' . esc_html($ptsStr) . '</span>';

        echo '</div>';
    };

    // ─── Render severity groups ──────────────────────────────────────
    // Problems: default OPEN
    if (!empty($problems)) {
        echo '<details class="s2ll-severity-group s2ll-severity-problems" open>';
        echo '<summary class="s2ll-severity-header"><span class="s2ll-severity-arrow">▶</span>' . esc_html__('Problems', 'shop-to-llm') . '<span class="s2ll-severity-count">(' . count($problems) . ')</span></summary>';
        foreach ($problems as $entry) {
            $renderFinding($entry);
        }
        echo '</details>';
    }

    // Improvements: default CLOSED
    if (!empty($improvements)) {
        echo '<details class="s2ll-severity-group s2ll-severity-improvements">';
        echo '<summary class="s2ll-severity-header"><span class="s2ll-severity-arrow">▶</span>' . esc_html__('To Improve', 'shop-to-llm') . '<span class="s2ll-severity-count">(' . count($improvements) . ')</span></summary>';
        foreach ($improvements as $entry) {
            $renderFinding($entry);
        }
        echo '</details>';
    }

    // Good: default CLOSED
    if (!empty($good)) {
        echo '<details class="s2ll-severity-group s2ll-severity-good">';
        echo '<summary class="s2ll-severity-header"><span class="s2ll-severity-arrow">▶</span>' . esc_html__('Good', 'shop-to-llm') . '<span class="s2ll-severity-count">(' . count($good) . ')</span></summary>';
        foreach ($good as $entry) {
            $renderFinding($entry);
        }
        echo '</details>';
    }

    // If all 25 passed (unlikely), show all-green message
    if (empty($problems) && empty($improvements)) {
        echo '<div style="text-align:center;padding:8px;color:#16a34a;font-size:13px;font-weight:600">' . esc_html__('Perfect AI Visibility!', 'shop-to-llm') . '</div>';
    }

    // ─── Dimension Summary ──────────────────────────────────────────
    $l2Locked = !$hasFocusQuery;
    $l4Locked = !$hasFocusQuery;
    $dimensions = [
        ['label' => __('Can AI find me?', 'shop-to-llm'),       'score' => $l1, 'max' => 30, 'color' => '#3b82f6', 'locked' => false,  'tooltip' => __('Technical discoverability: llms.txt, robots.txt, Schema, MCP', 'shop-to-llm')],
        ['label' => $l2Locked ? __('Relevance 🔒', 'shop-to-llm') : __('Search Relevance', 'shop-to-llm'), 'score' => $l2, 'max' => 25, 'color' => $l2Locked ? '#94a3b8' : '#8b5cf6', 'locked' => $l2Locked, 'tooltip' => __('How well your content matches the search query', 'shop-to-llm')],
        ['label' => __('Content Quality', 'shop-to-llm'),       'score' => $l3, 'max' => 25, 'color' => '#f59e0b', 'locked' => false,  'tooltip' => __('Length, statistics, citations, freshness, structure', 'shop-to-llm')],
        ['label' => $l4Locked ? __('Authority 🔒', 'shop-to-llm') : __('Brand Authority', 'shop-to-llm'), 'score' => $l4, 'max' => 15, 'color' => $l4Locked ? '#94a3b8' : '#06b6d4', 'locked' => $l4Locked, 'tooltip' => __('Knowledge graph, Wikipedia, authoritative lists', 'shop-to-llm')],
        ['label' => __('AI Activity', 'shop-to-llm'),           'score' => $l5, 'max' => 5,  'color' => '#16a34a', 'locked' => false,  'tooltip' => __('Crawler visits, reviews, multi-platform reach', 'shop-to-llm')],
    ];

    echo '<div class="s2ll-dim-section">';
    echo '<span class="s2ll-dim-section-title">' . esc_html__('Dimension Breakdown', 'shop-to-llm') . '</span>';

    foreach ($dimensions as $dim) {
        $pct = $dim['max'] > 0 ? round(($dim['score'] / $dim['max']) * 100) : 0;
        $barOpacity = $dim['locked'] ? '0.5' : '1';
        echo '<div class="s2ll-dimension-bar" style="opacity:' . esc_attr($barOpacity) . '">';
        echo '<span class="s2ll-dimension-label">' . esc_html($dim['label']);
        if (!empty($dim['tooltip'])) {
            echo '<span class="s2ll-help" title="' . esc_attr($dim['tooltip']) . '">?</span>';
        }
        echo '</span>';
        echo '<span class="s2ll-dimension-track"><span class="s2ll-dimension-fill" style="width:' . esc_attr((string)$pct) . '%;background:' . esc_attr($dim['color']) . '"></span></span>';
        echo '<span class="s2ll-dimension-score">' . (int) $dim['score'] . '/' . (int) $dim['max'] . '</span>';
        echo '</div>';
        if ($dim['locked']) {
            echo '<span class="s2ll-unlock-hint">' . esc_html__('Enter a Focus Query to unlock', 'shop-to-llm') . '</span>';
        }
    }
    echo '</div>';

    // ─── AI Visibility Trend with Mini Sparkline ────────────────────
    echo '<div class="s2ll-trend-section">';
    echo '<div class="s2ll-trend-header">';
    echo '<span class="s2ll-trend-title">' . esc_html__('AI Visibility Trend', 'shop-to-llm') . '</span>';
    echo '</div>';

    $history = get_post_meta($post->ID, '_shop2llm_visibility_history', true);
    $sparkData = [];
    if (is_array($history) && count($history) >= 2) {
        $currentScore = $score;
        $prevEntry = $history[count($history) - 2];
        $prevScore = (int) $prevEntry['score'];

        // Build sparkline data (last up to 8 weeks)
        $sparkData = array_map(function ($e) { return (int) $e['score']; }, $history);
        // Normalize to 0-28px height range
        if (!empty($sparkData)) {
            $maxVal = max($sparkData);
            $minVal = min($sparkData);
            $range = $maxVal - $minVal ?: 1;
            echo '<div class="s2ll-sparkline">';
            foreach ($sparkData as $val) {
                $h = max(2, round((($val - $minVal) / $range) * 28));
                echo '<span class="s2ll-sparkline-bar" style="height:' . (int) $h . 'px" title="' . (int) $val . '"></span>';
            }
            echo '</div>';
        }

        if ($prevScore > 0) {
            $changePct = round((($currentScore - $prevScore) / $prevScore) * 100, 1);
        } else {
            $changePct = $currentScore > 0 ? 100 : 0;
        }

        $arrow = $changePct >= 0 ? '↑' : '↓';
        $color = $changePct >= 0 ? '#16a34a' : '#dc2626';

        echo '<div class="s2ll-trend-values">';
        echo '<span class="s2ll-trend-current">' . esc_html__('This week:', 'shop-to-llm') . ' <strong>' . (int) $currentScore . '</strong></span>';
        echo '<span class="s2ll-trend-prev">' . esc_html__('Last:', 'shop-to-llm') . ' ' . (int) $prevScore . '</span>';
        echo '<span class="s2ll-trend-change" style="color:' . esc_attr($color) . ';font-weight:600">' . esc_html($arrow . ' ' . abs($changePct) . '%') . '</span>';
        echo '</div>';

        if ($changePct < -10) {
            echo '<div class="s2ll-trend-warning">⚠ ' . esc_html__('AI visibility dropping', 'shop-to-llm') . '</div>';
        }
    } elseif (is_array($history) && count($history) === 1) {
        echo '<div class="s2ll-trend-values">';
        echo '<span class="s2ll-trend-current">' . esc_html__('This week:', 'shop-to-llm') . ' <strong>' . (int) $score . '</strong></span>';
        echo '</div>';
        echo '<div class="s2ll-trend-pending">📊 ' . esc_html__('Trend data will appear after next week', 'shop-to-llm') . '</div>';
    } else {
        echo '<div class="s2ll-trend-pending">📊 ' . esc_html__('Trend data will appear after next week', 'shop-to-llm') . '</div>';
    }

    echo '</div>'; // .s2ll-trend-section

    // ─── Crawler info ──────────────────────────────────────────────
    if (!empty($crawlerStats['total_crawls']) && $crawlerStats['total_crawls'] > 0) {
        echo '<div class="s2ll-crawler-info">';
        echo esc_html(sprintf(/* translators: %d: number of unique AI bots */ __('%d AI bots visited', 'shop-to-llm'), (int) $crawlerStats['unique_bots']));
        echo ' · ' . esc_html(implode(', ', array_slice($crawlerStats['bot_list'], 0, 3)));
        if (count($crawlerStats['bot_list']) > 3) {
            echo ' +' . (count($crawlerStats['bot_list']) - 3);
        }
        echo '<br>' . esc_html(sprintf(/* translators: %s: last visit date/time */ __('Last: %s', 'shop-to-llm'), $crawlerStats['last_visit']));
        echo '</div>';
    }

    echo '</div>'; // .s2ll-widget
}

// ─── P2: AI Visibility Column in Post/Product List ─────────────────────

function shop_to_llm_add_visibility_column(array $columns): array
{
    $new = [];
    foreach ($columns as $key => $label) {
        $new[$key] = $label;
        if ($key === 'title') {
            $new['s2ll_visibility'] = __('S2LL Score', 'shop-to-llm');
        }
    }
    return $new;
}

function shop_to_llm_render_visibility_column(string $column, int $postId): void
{
    if ($column !== 's2ll_visibility') {
        return;
    }

    // Use bulk-cached crawled post IDs
    static $crawledPosts = null;
    if ($crawledPosts === null) {
        $crawledPosts = shop_to_llm_get_bulk_crawled_post_ids();
    }

    $post = get_post($postId);
    if (!$post) {
        return;
    }

    $score = shop_to_llm_calculate_visibility_score($postId);
    $focusQuery = get_post_meta($postId, '_shop2llm_focus_query', true);
    $hasFocusQuery = !empty(trim((string) $focusQuery));

    $color = $score >= 71 ? '#16a34a' : ($score >= 41 ? '#f59e0b' : '#dc2626');
    $emoji = $score >= 71 ? '🟢' : ($score >= 41 ? '🟡' : '🔴');
    $title = sprintf(/* translators: %d: visibility score */ __('Shop2LLM Visibility Score: %d/100', 'shop-to-llm'), $score);

    echo '<span style="display:inline-flex;align-items:center;gap:4px;cursor:default" title="' . esc_attr($title) . '">';
    echo '<span style="font-size:11px">' . esc_html($emoji) . '</span>';
    echo '<span style="font-size:11px;color:' . esc_attr($color) . ';font-weight:600">' . esc_html((string)$score) . '/100</span>';

    // Focus query in quotes
    if ($hasFocusQuery) {
        echo ' <span style="font-size:11px;color:#64748b">"' . esc_html($focusQuery) . '"</span>';
    }

    // Trend indicator
    $history = get_post_meta($postId, '_shop2llm_visibility_history', true);
    if (is_array($history) && count($history) >= 2) {
        $prevScore = (int) $history[count($history) - 2]['score'];
        if ($prevScore > 0) {
            $changePct = round((($score - $prevScore) / $prevScore) * 100, 1);
        } else {
            $changePct = $score > 0 ? 100 : 0;
        }
        if ($changePct > 0) {
            echo ' <span style="font-size:11px;color:#16a34a;font-weight:600">↑</span>';
        } elseif ($changePct < 0) {
            echo ' <span style="font-size:11px;color:#dc2626;font-weight:600">↓</span>';
        }
    }

    echo '</span>';
}

function shop_to_llm_get_bulk_crawled_post_ids(): array
{
    global $wpdb;
    $table = $wpdb->prefix . 'shop2llm_crawler_log';
    $tableExists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    if (!$tableExists) {
        return [];
    }

    $transientKey = 'shop2llm_crawled_post_ids_30d';
    $cached = get_transient($transientKey);
    if ($cached !== false && is_array($cached)) {
        return $cached;
    }

    $thirtyDaysAgo = gmdate('Y-m-d H:i:s', time() - 30 * DAY_IN_SECONDS);

    // Check if post_id column exists
    $col = $wpdb->get_results("SHOW COLUMNS FROM $table LIKE 'post_id'"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
    if (!empty($col)) {
        $ids = $wpdb->get_col($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
            "SELECT DISTINCT post_id FROM $table WHERE visited_at >= %s AND post_id > 0", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
            $thirtyDaysAgo
        ));
    } else {
        // Fallback: match by URL
        $urls = $wpdb->get_col($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
            "SELECT DISTINCT page_url FROM $table WHERE visited_at >= %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
            $thirtyDaysAgo
        ));
        $ids = [];
        foreach ($urls as $url) {
            $pid = (int) url_to_postid($url);
            if ($pid > 0) {
                $ids[] = $pid;
            }
        }
        $ids = array_unique($ids);
    }

    set_transient($transientKey, $ids, HOUR_IN_SECONDS);
    return $ids;
}

// ─── P3: Per-Post AI Crawler Visit Stats ───────────────────────────────

function shop_to_llm_get_post_crawler_stats(int $postId): array
{
    global $wpdb;
    $table = $wpdb->prefix . 'shop2llm_crawler_log';

    $default = [
        'total_crawls' => 0,
        'unique_bots' => 0,
        'last_visit' => '',
        'bot_list' => [],
    ];

    $tableExists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    if (!$tableExists) {
        return $default;
    }

    // Check if post_id column exists
    $col = $wpdb->get_results("SHOW COLUMNS FROM $table LIKE 'post_id'"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter

    if (!empty($col)) {
        // Use post_id column for efficient lookup
        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $row = $wpdb->get_row($wpdb->prepare(
            "SELECT COUNT(*) as total, COUNT(DISTINCT bot_name) as unique_bots, MAX(visited_at) as last_visit
             FROM $table WHERE post_id = %d",
            $postId
        ), ARRAY_A);
        // phpcs:enable

        if ($row && (int) $row['total'] > 0) {
            $botList = $wpdb->get_col($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
                "SELECT DISTINCT bot_label FROM $table WHERE post_id = %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
                $postId
            ));
            return [
                'total_crawls' => (int) $row['total'],
                'unique_bots' => (int) $row['unique_bots'],
                'last_visit' => $row['last_visit'] ?: '',
                'bot_list' => $botList ?: [],
            ];
        }
    }

    // Fallback: match by URL
    $postUrl = get_permalink($postId);
    if (!$postUrl) {
        return $default;
    }

    $like = '%' . $wpdb->esc_like(wp_parse_url($postUrl, PHP_URL_PATH)) . '%';
    // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
    $row = $wpdb->get_row($wpdb->prepare(
        "SELECT COUNT(*) as total, COUNT(DISTINCT bot_name) as unique_bots, MAX(visited_at) as last_visit
         FROM $table WHERE page_url LIKE %s",
        $like
    ), ARRAY_A);
    // phpcs:enable

    if ($row && (int) $row['total'] > 0) {
        $botList = $wpdb->get_col($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
            "SELECT DISTINCT bot_label FROM $table WHERE page_url LIKE %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
            $like
        ));
        return [
            'total_crawls' => (int) $row['total'],
            'unique_bots' => (int) $row['unique_bots'],
            'last_visit' => $row['last_visit'] ?: '',
            'bot_list' => $botList ?: [],
        ];
    }

    return $default;
}

// ─── Feature 1: AI Discovery Badge ─────────────────────────────────────

function shop_to_llm_render_footer_badge(): void
{
    if (is_admin() || wp_doing_ajax() || defined('REST_REQUEST')) {
        return;
    }
    if (get_option('shop_to_llm_enabled', 'yes') !== 'yes') {
        return;
    }
    if (!apply_filters('shop_to_llm_show_badge', true)) {
        return;
    }
    if (get_option('shop_to_llm_show_badge', 'yes') !== 'yes') {
        return;
    }
    $visibilityUrl = esc_url(home_url('/ai-visibility/'));
    // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $visibilityUrl already escaped
    echo '<a href="' . $visibilityUrl . '" id="s2ll-badge" style="position:fixed;bottom:12px;right:12px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;padding:4px 10px;font-family:-apple-system,sans-serif;font-size:11px;color:#64748b;box-shadow:0 1px 3px rgba(0,0,0,0.08);z-index:9999;text-decoration:none;display:flex;align-items:center;gap:4px"><span style="font-size:13px">🛡</span><span>AI Discoverable</span></a>';
}

// ─── Feature 1b: /ai-visibility/ Verification Page ─────────────────────

function shop_to_llm_add_visibility_rewrite(): void
{
    add_rewrite_rule('^ai-visibility/?$', 'index.php?s2ll_visibility=1', 'top');
}

function shop_to_llm_visibility_query_var(array $vars): array
{
    $vars[] = 's2ll_visibility';
    return $vars;
}

function shop_to_llm_render_visibility_page(): void
{
    if (!get_query_var('s2ll_visibility')) {
        return;
    }

    $siteName = get_bloginfo('name');
    $siteUrl = get_site_url();
    $siteDesc = get_bloginfo('description');

    // llms.txt status
    $llmsTxtUrl = esc_url($siteUrl . '/llms.txt');
    $llmsTxtStatus = 'Active';

    // Schema markup count
    $schemaCount = 0;
    if (shop_to_llm_has_woocommerce()) {
        $schemaCount = (int) (wp_count_posts('product')->publish ?? 0);
    } else {
        $schemaCount = (int) (wp_count_posts('post')->publish ?? 0);
    }

    // AI crawler visits (unique bots in last 30 days)
    $crawlerStats = shop_to_llm_get_crawler_stats();
    $uniqueBots30d = (int) $crawlerStats['unique_bots_30d'];
    $totalCrawls30d = (int) $crawlerStats['total_30d'];

    // MCP tools status
    $mcpActive = get_option('shop_to_llm_api_key', '') !== '' ? 'Active' : 'Inactive';

    // AI referral stats
    $referralStats = shop_to_llm_get_ai_referral_stats();
    $totalReferrals = (int) $referralStats['this_month'];

    // Known AI platforms
    $platforms = [
        ['name' => 'ChatGPT', 'icon' => '🤖'],
        ['name' => 'Perplexity', 'icon' => '🔍'],
        ['name' => 'Google AI', 'icon' => '🌐'],
        ['name' => 'Bing Copilot', 'icon' => '💬'],
        ['name' => 'Claude', 'icon' => '🧠'],
        ['name' => 'Brave Search', 'icon' => '🦁'],
    ];

    header('Content-Type: text/html; charset=utf-8');
    ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title><?php echo esc_html($siteName); ?> — AI Visibility</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#f8fafc;color:#1e293b;line-height:1.6}
.wrap{max-width:680px;margin:0 auto;padding:40px 20px}
.card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:24px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,0.04)}
h1{font-size:24px;font-weight:700;color:#1e293b;margin-bottom:4px}
.subtitle{font-size:14px;color:#64748b;margin-bottom:24px}
.status-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.status-item{padding:16px;border-radius:8px;background:#f8fafc;border:1px solid #e2e8f0}
.status-item h3{font-size:12px;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:4px}
.status-item .val{font-size:22px;font-weight:700;color:#1e293b}
.status-item .val.active{color:#16a34a}
.status-item .val.inactive{color:#94a3b8}
.platforms{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}
.platform-tag{display:inline-flex;align-items:center;gap:4px;padding:6px 12px;background:#f0f0ff;border:1px solid #e2e8f0;border-radius:20px;font-size:13px;color:#4338ca;font-weight:500}
.branding{text-align:center;margin-top:24px;font-size:12px;color:#94a3b8}
.branding a{color:#6366f1;text-decoration:none;font-weight:600}
</style>
</head>
<body>
<div class="wrap">
<div class="card" style="text-align:center;border-left:4px solid #6366f1">
<h1>🛡 <?php echo esc_html($siteName); ?></h1>
<p class="subtitle"><?php echo esc_html($siteDesc); ?> · <?php echo esc_url($siteUrl); ?></p>
<p style="font-size:14px;color:#16a34a;font-weight:600">✓ This site is AI-Discoverable</p>
</div>

<div class="card">
<h2 style="font-size:16px;font-weight:600;margin-bottom:16px">AI Visibility Status</h2>
<div class="status-grid">
<div class="status-item">
<h3>llms.txt</h3>
<div class="val active"><?php echo esc_html($llmsTxtStatus); ?></div>
</div>
<div class="status-item">
<h3>Schema Markup</h3>
<div class="val"><?php echo esc_html((string)$schemaCount); ?> <span style="font-size:12px;color:#64748b;font-weight:400">pages</span></div>
</div>
<div class="status-item">
<h3>AI Crawler Visits (30d)</h3>
<div class="val"><?php echo esc_html((string)$totalCrawls30d); ?> <span style="font-size:12px;color:#64748b;font-weight:400">from <?php echo esc_html((string)$uniqueBots30d); ?> bots</span></div>
</div>
<div class="status-item">
<h3>MCP Tools</h3>
<div class="val <?php echo $mcpActive === 'Active' ? 'active' : 'inactive'; ?>"><?php echo esc_html($mcpActive); ?></div>
</div>
<div class="status-item">
<h3>AI-Driven Traffic (This Month)</h3>
<div class="val"><?php echo esc_html((string)$totalReferrals); ?> <span style="font-size:12px;color:#64748b;font-weight:400">visits</span></div>
</div>
<div class="status-item">
<h3>Plugin Version</h3>
<div class="val" style="font-size:16px"><?php echo esc_html(SHOP_TO_LLM_VERSION); ?></div>
</div>
</div>
</div>

<div class="card">
<h2 style="font-size:16px;font-weight:600;margin-bottom:8px">AI Platforms That Can Discover This Site</h2>
<p style="font-size:13px;color:#64748b;margin-bottom:12px">This site is optimized for discovery by the following AI assistants and search engines.</p>
<div class="platforms">
<?php foreach ($platforms as $p) : ?>
<span class="platform-tag"><?php echo esc_html($p['icon']); ?> <?php echo esc_html($p['name']); ?></span>
<?php endforeach; ?>
</div>
</div>

<div class="branding">
Powered by <a href="https://shop2llm.com/" target="_blank">Shop2LLM</a> — AI Visibility for Your Website
</div>
</div>
</body>
</html>
    <?php
    exit;
}

// ─── Feature 2: AI Referral Tracker ────────────────────────────────────

function shop_to_llm_create_ai_referrals_table(): void
{
    global $wpdb;
    $table = $wpdb->prefix . 'shop2llm_ai_referrals';
    $charset = $wpdb->get_charset_collate();
    $sql = "CREATE TABLE IF NOT EXISTS $table (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        source VARCHAR(50) NOT NULL,
        source_label VARCHAR(50) NOT NULL,
        landing_url VARCHAR(500) NOT NULL DEFAULT '',
        post_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
        visited_at DATETIME NOT NULL,
        PRIMARY KEY (id),
        KEY idx_source (source),
        KEY idx_date (visited_at)
    ) $charset;";
    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta($sql);
}

function shop_to_llm_detect_ai_referral(): void
{
    if (is_admin() || wp_doing_ajax() || defined('REST_REQUEST')) {
        return;
    }

    $referer = sanitize_text_field(wp_unslash($_SERVER['HTTP_REFERER'] ?? ''));
    if (empty($referer)) {
        return;
    }

    $aiReferrers = [
        'perplexity.ai'         => ['source' => 'perplexity', 'label' => 'Perplexity'],
        'chat.openai.com'       => ['source' => 'chatgpt', 'label' => 'ChatGPT'],
        'copilot.microsoft.com' => ['source' => 'copilot', 'label' => 'Copilot'],
        'bing.com/chat'         => ['source' => 'bing-chat', 'label' => 'Bing Chat'],
        'you.com'               => ['source' => 'you', 'label' => 'You.com'],
        'search.brave.com'      => ['source' => 'brave', 'label' => 'Brave Search'],
        'phind.com'             => ['source' => 'phind', 'label' => 'Phind'],
        'kagi.com'              => ['source' => 'kagi', 'label' => 'Kagi'],
    ];

    // Also check UTM parameters
    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- UTM params are public tracking values, no nonce needed
    $utmSource = sanitize_text_field(wp_unslash($_GET['utm_source'] ?? ''));
    $utmMap = [
        'chatgpt'    => ['source' => 'chatgpt', 'label' => 'ChatGPT'],
        'perplexity' => ['source' => 'perplexity', 'label' => 'Perplexity'],
        'copilot'    => ['source' => 'copilot', 'label' => 'Copilot'],
        'bing-chat'  => ['source' => 'bing-chat', 'label' => 'Bing Chat'],
        'you'        => ['source' => 'you', 'label' => 'You.com'],
        'brave'      => ['source' => 'brave', 'label' => 'Brave Search'],
        'phind'      => ['source' => 'phind', 'label' => 'Phind'],
        'kagi'       => ['source' => 'kagi', 'label' => 'Kagi'],
    ];

    $matched = null;

    // Check referer
    foreach ($aiReferrers as $domain => $info) {
        if (stripos($referer, $domain) !== false) {
            $matched = $info;
            break;
        }
    }

    // Check UTM if no referer match
    if (!$matched && !empty($utmSource) && isset($utmMap[$utmSource])) {
        $matched = $utmMap[$utmSource];
    }

    if (!$matched) {
        return;
    }

    // Dedup: 10-minute TTL per source+IP
    $ip = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'] ?? ''));
    $dedupKey = 's2ll_referral_' . $matched['source'] . '_' . md5($ip);
    if (get_transient($dedupKey)) {
        return;
    }
    set_transient($dedupKey, 1, 10 * MINUTE_IN_SECONDS);

    // Log to DB
    global $wpdb;
    $table = $wpdb->prefix . 'shop2llm_ai_referrals';
    $tableExists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    if (!$tableExists) {
        shop_to_llm_create_ai_referrals_table();
    }

    $requestUri = sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'] ?? ''));
    $landingUrl = home_url($requestUri);
    if (strlen($landingUrl) > 500) {
        $landingUrl = substr($landingUrl, 0, 500);
    }
    $postId = (int) url_to_postid($landingUrl);

    $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
        $table,
        [
            'source' => $matched['source'],
            'source_label' => $matched['label'],
            'landing_url' => $landingUrl,
            'post_id' => $postId,
            'visited_at' => current_time('mysql'),
        ],
        ['%s', '%s', '%s', '%d', '%s']
    );
}

function shop_to_llm_get_ai_referral_stats(): array
{
    global $wpdb;
    $table = $wpdb->prefix . 'shop2llm_ai_referrals';

    $default = [
        'this_month' => 0,
        'last_month' => 0,
        'change_pct' => 0,
        'by_source' => [],
        'daily_counts' => array_fill(0, 7, 0),
    ];

    $tableExists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    if (!$tableExists) {
        return $default;
    }

    // This month
    $thisMonthStart = gmdate('Y-m-01 00:00:00');
    $thisMonth = (int) $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        "SELECT COUNT(*) FROM $table WHERE visited_at >= %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $thisMonthStart
    ));

    // Last month
    $lastMonthStart = gmdate('Y-m-01 00:00:00', strtotime('first day of last month'));
    $lastMonthEnd = gmdate('Y-m-01 00:00:00');
    $lastMonth = (int) $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        "SELECT COUNT(*) FROM $table WHERE visited_at >= %s AND visited_at < %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $lastMonthStart,
        $lastMonthEnd
    ));

    // Change percentage
    $changePct = 0;
    if ($lastMonth > 0) {
        $changePct = round((($thisMonth - $lastMonth) / $lastMonth) * 100, 1);
    } elseif ($thisMonth > 0) {
        $changePct = 100;
    }

    // By source
    $bySource = $wpdb->get_results($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        "SELECT source, source_label, COUNT(*) as cnt FROM $table WHERE visited_at >= %s GROUP BY source, source_label ORDER BY cnt DESC", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $thisMonthStart
    ), ARRAY_A);
    if (!is_array($bySource)) {
        $bySource = [];
    }

    // Daily counts for last 7 days
    $dailyRows = $wpdb->get_results($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        "SELECT DATE(visited_at) as day, COUNT(*) as cnt FROM $table WHERE visited_at >= %s GROUP BY DATE(visited_at) ORDER BY day ASC", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        gmdate('Y-m-d H:i:s', time() - 7 * DAY_IN_SECONDS)
    ), ARRAY_A);

    $dailyCounts = [];
    for ($i = 6; $i >= 0; $i--) {
        $day = gmdate('Y-m-d', time() - $i * DAY_IN_SECONDS);
        $cnt = 0;
        if (is_array($dailyRows)) {
            foreach ($dailyRows as $dr) {
                if ($dr['day'] === $day) {
                    $cnt = (int) $dr['cnt'];
                    break;
                }
            }
        }
        $dailyCounts[] = $cnt;
    }

    return [
        'this_month' => $thisMonth,
        'last_month' => $lastMonth,
        'change_pct' => $changePct,
        'by_source' => $bySource,
        'daily_counts' => $dailyCounts,
    ];
}

function shop_to_llm_render_ai_referral_section(): void
{
    $stats = shop_to_llm_get_ai_referral_stats();
    $maxVal = max($stats['daily_counts'] ?: [1]);
    if ($maxVal === 0) {
        $maxVal = 1;
    }

    $changeText = '';
    if ($stats['this_month'] > 0 || $stats['last_month'] > 0) {
        $arrow = $stats['change_pct'] >= 0 ? '↑' : '↓';
        $color = $stats['change_pct'] >= 0 ? '#16a34a' : '#dc2626';
        $changeText = '<span style="font-size:12px;color:' . esc_attr($color) . ';font-weight:600">' . esc_html($arrow . ' ' . abs($stats['change_pct']) . '%') . '</span>';
    }

    echo '<div class="s2ll-widget">';

    // Summary
    echo '<div class="s2ll-summary">';
    echo '<div>';
    echo '<span class="s2ll-big">' . (int) $stats['this_month'] . '</span>';
    echo '<span class="s2ll-label">AI visits this month</span>';
    echo esc_html($changeText);
    echo '</div>';
    echo '</div>';

    // 7-day sparkline
    if (max($stats['daily_counts']) > 0) {
        echo '<div class="s2ll-sparkline">';
        foreach ($stats['daily_counts'] as $val) {
            $height = $val > 0 ? max(8, ($val / $maxVal) * 48) : 2;
            $color = $val > 0 ? '#10b981' : '#e2e8f0';
            echo '<div class="s2ll-bar" style="height:' . esc_attr($height) . 'px;background:' . esc_attr($color) . '" title="' . (int) $val . ' visits">';
            if ($val > 0) {
                echo '<span class="s2ll-bar-val">' . (int) $val . '</span>';
            }
            echo '</div>';
        }
        echo '</div>';
        echo '<div class="s2ll-axis"><span>7 days ago</span><span>Today</span></div>';
    }

    // Per-source breakdown
    if (!empty($stats['by_source'])) {
        echo '<div class="s2ll-divider">';
        $maxCnt = (int) $stats['by_source'][0]['cnt'];
        if ($maxCnt === 0) {
            $maxCnt = 1;
        }
        foreach ($stats['by_source'] as $row) {
            $cnt = (int) $row['cnt'];
            $bar = min(100, ($cnt / $maxCnt) * 100);
            echo '<div class="s2ll-bot-row">';
            echo '<span class="s2ll-bot-name">' . esc_html($row['source_label']) . '</span>';
            echo '<div class="s2ll-bot-track"><div class="s2ll-bot-fill" style="width:' . esc_attr($bar) . '%;background:linear-gradient(90deg,#10b981,#059669)"></div></div>';
            echo '<span class="s2ll-bot-cnt">' . esc_html($cnt) . ' visits</span>';
            echo '</div>';
        }
        echo '</div>';
    } else {
        echo '<div class="s2ll-empty">';
        echo '⏳ Waiting for AI-driven traffic...';
        echo '<br><span class="s2ll-empty-sub">Visitors from Perplexity, ChatGPT, Copilot and others will appear here.</span>';
        echo '</div>';
    }

    echo '</div>';
}

function shop_to_llm_admin_bar_referral_suffix(): string
{
    $transientKey = 'shop2llm_admin_bar_referrals';
    $referralCount = get_transient($transientKey);
    if ($referralCount === false) {
        $referralStats = shop_to_llm_get_ai_referral_stats();
        $referralCount = (int) $referralStats['this_month'];
        set_transient($transientKey, $referralCount, 5 * MINUTE_IN_SECONDS);
    }
    if ($referralCount > 0) {
        return ' · ' . (int) $referralCount . ' AI visits';
    }
    return '';
}

// ─── Focus Query & Extended Scoring ────────────────────────────────────

function shop_to_llm_calculate_visibility_score(int $postId): int
{
    // Check transient cache (1 hour)
    $cached = get_transient('shop2llm_score_' . $postId);
    if ($cached !== false) {
        return (int) $cached;
    }

    // Delegate to canonical 25-item scoring function
    $score = shop_to_llm_score_single_post($postId);
    $result = $score['total'];

    // Cache result
    set_transient('shop2llm_score_' . $postId, $result, HOUR_IN_SECONDS);
    return (int) $result;
}
function shop_to_llm_check_robots_txt_allows_ai(): bool
{
    $transientKey = 'shop2llm_robots_allows_ai';
    $cached = get_transient($transientKey);
    if ($cached !== false) return $cached === 'yes';

    $robotsUrl = home_url('/robots.txt');
    $response = wp_remote_get($robotsUrl, ['timeout' => 5]);
    if (is_wp_error($response)) {
        set_transient($transientKey, 'yes', DAY_IN_SECONDS);
        return true;
    }
    $content = wp_remote_retrieve_body($response);
    $aiBots = ['GPTBot', 'ChatGPT-User', 'ClaudeBot', 'PerplexityBot', 'Google-Extended', 'CCBot', 'Applebot', 'Bytespider'];
    foreach ($aiBots as $bot) {
        if (preg_match('/User-agent:\s*' . preg_quote($bot, '/') . '/i', $content)) {
            if (preg_match('/Disallow:\s*\/\s*$/im', $content)) {
                set_transient($transientKey, 'no', DAY_IN_SECONDS);
                return false;
            }
        }
    }
    if (preg_match('/User-agent:\s*\*\s*\n\s*Disallow:\s*\/\s*$/im', $content)) {
        set_transient($transientKey, 'no', DAY_IN_SECONDS);
        return false;
    }
    set_transient($transientKey, 'yes', DAY_IN_SECONDS);
    return true;
}

function shop_to_llm_robots_txt_fix_notice(): void
{
    // Only show on Shop2LLM settings page
    $screen = get_current_screen();
    if (!$screen || $screen->id !== 'toplevel_page_shop2llm') {
        return;
    }

    // Handle fix action
    if (isset($_GET['shop2llm_fix_robots']) && check_admin_referer('shop2llm_fix_robots')) {
        flush_rewrite_rules();
        delete_transient('shop2llm_robots_allows_ai');
        sleep(1);
        if (shop_to_llm_check_robots_txt_allows_ai()) {
            echo '<div class="notice notice-success"><p>'
                . esc_html__('✅ AI crawler rules added to robots.txt. Your content is now discoverable by AI.', 'shop-to-llm')
                . '</p></div>';
            return;
        }
    }

    if (shop_to_llm_check_robots_txt_allows_ai()) {
        // Show green status badge — always visible
        ?>
        <div class="notice notice-success" style="border-left-color:#16a34a">
            <p style="font-size:13px">
                <?php echo esc_html__('✅ robots.txt allows AI crawlers — 12 AI bots whitelisted. Your content is discoverable by ChatGPT, GPTBot, ClaudeBot and more.', 'shop-to-llm'); ?>
            </p>
        </div>
        <?php
        return;
    }

    // Blocked — show fix button
    ?>
    <div class="notice notice-warning" style="border-left-color:#f59e0b">
        <p style="font-size:13px">
            <strong><?php echo esc_html__('⚠ robots.txt may block AI crawlers', 'shop-to-llm'); ?></strong><br>
            <?php echo esc_html__('AI search engines cannot index your site. This reduces your AI Score.', 'shop-to-llm'); ?>
            <a href="<?php echo esc_url(wp_nonce_url(admin_url('admin.php?page=shop2llm&shop2llm_fix_robots=1'), 'shop2llm_fix_robots')); ?>" 
               style="display:inline-block;margin-top:6px;padding:4px 12px;background:#f59e0b;color:#fff;border-radius:4px;text-decoration:none;font-weight:600;font-size:12px">
                <?php echo esc_html__('🛠 Fix robots.txt', 'shop-to-llm'); ?>
            </a>
            <span style="font-size:11px;color:#94a3b8;margin-left:8px">
                <?php echo esc_html__('Adds AI crawler rules to allow GPTBot, ClaudeBot & 10 more', 'shop-to-llm'); ?>
            </span>
        </p>
    </div>
    <?php
}

function shop_to_llm_check_prerendered_html(int $postId): bool
{
    $transientKey = 'shop2llm_prerendered_' . $postId;
    $cached = get_transient($transientKey);
    if ($cached !== false) return $cached === 'yes';

    $url = get_permalink($postId);
    if (!$url) {
        set_transient($transientKey, 'no', DAY_IN_SECONDS);
        return false;
    }
    $response = wp_remote_get($url, ['timeout' => 5]);
    if (is_wp_error($response)) {
        set_transient($transientKey, 'yes', DAY_IN_SECONDS);
        return true;
    }
    $html = wp_remote_retrieve_body($response);
    $text = preg_replace('/<script[^>]*>.*?<\/script>/si', '', $html);
    $text = preg_replace('/<style[^>]*>.*?<\/style>/si', '', $text);
    $text = wp_strip_all_tags($text);
    $text = trim($text);
    $result = strlen($text) > 200;
    set_transient($transientKey, $result ? 'yes' : 'no', DAY_IN_SECONDS);
    return $result;
}

function shop_to_llm_check_wikidata_entity(string $query): bool
{
    $transientKey = 'shop2llm_wikidata_' . md5($query);
    $cached = get_transient($transientKey);
    if ($cached !== false) return $cached === 'yes';

    $url = 'https://www.wikidata.org/w/api.php?' . http_build_query([
        'action' => 'wbsearchentities',
        'search' => $query,
        'language' => 'en',
        'format' => 'json',
        'limit' => 1,
    ]);
    $response = wp_remote_get($url, ['timeout' => 5]);
    if (is_wp_error($response)) {
        set_transient($transientKey, 'no', DAY_IN_SECONDS);
        return false;
    }
    $data = json_decode(wp_remote_retrieve_body($response), true);
    $found = !empty($data['search']) && count($data['search']) > 0;
    set_transient($transientKey, $found ? 'yes' : 'no', 7 * DAY_IN_SECONDS);
    return $found;
}

function shop_to_llm_check_wikipedia_mention(string $query): bool
{
    $transientKey = 'shop2llm_wikipedia_' . md5($query);
    $cached = get_transient($transientKey);
    if ($cached !== false) return $cached === 'yes';

    $url = 'https://en.wikipedia.org/w/api.php?' . http_build_query([
        'action' => 'query',
        'list' => 'search',
        'srsearch' => $query,
        'format' => 'json',
    ]);
    $response = wp_remote_get($url, ['timeout' => 5]);
    if (is_wp_error($response)) {
        set_transient($transientKey, 'no', DAY_IN_SECONDS);
        return false;
    }
    $data = json_decode(wp_remote_retrieve_body($response), true);
    $found = !empty($data['query']['search']) && count($data['query']['search']) > 0;
    set_transient($transientKey, $found ? 'yes' : 'no', 7 * DAY_IN_SECONDS);
    return $found;
}

function shop_to_llm_has_statistics(string $content): bool
{
    $patterns = [
        '/\d+\.?\d*\s*%/',
        '/\$\d+[\d,]*\.?\d*/',
        '/\d+[\d,]*\s*(people|users|customers|clients|companies|sites|stores|products|items|orders|sales|revenue|growth|increase|decrease)/i',
        '/\d{4}\s*(study|report|survey|research|data)/i',
    ];
    foreach ($patterns as $pattern) {
        if (preg_match($pattern, $content)) return true;
    }
    return false;
}

function shop_to_llm_has_citations(string $content): bool
{
    if (preg_match('/<blockquote|<cite|<q/i', $content)) return true;
    if (preg_match('/"[^"]{10,}"\s*[-—]\s*\w+/i', $content)) return true;
    if (preg_match('/"[^"]{10,}"\s*(said|according|noted|stated)/i', $content)) return true;
    return false;
}

function shop_to_llm_count_internal_links(string $content): int
{
    $homeUrl = home_url();
    $count = preg_match_all('/<a[^>]+href=["\']' . preg_quote($homeUrl, '/') . '/i', $content);
    $count += preg_match_all('/<a[^>]+href=["\']\/[^"\']*["\']/i', $content);
    return (int) $count;
}

function shop_to_llm_keyword_density(string $content, string $keyword): float
{
    $wordCount = str_word_count(wp_strip_all_tags($content));
    if ($wordCount === 0) return 0.0;
    $keywordCount = substr_count(strtolower($content), strtolower($keyword));
    return ($keywordCount / $wordCount) * 100;
}

function shop_to_llm_get_post_schema_data(\WP_Post $post): array
{
    $data = [];

    if (shop_to_llm_has_woocommerce() && $post->post_type === 'product') {
        $product = wc_get_product($post->ID);
        if ($product) {
            $data[] = $product->get_name();
            $data[] = wp_strip_all_tags($product->get_short_description() ?: $product->get_description());
            $cats = wp_get_post_terms($post->ID, 'product_cat', ['fields' => 'names']);
            if (!is_wp_error($cats)) {
                $data = array_merge($data, $cats);
            }
        }
    } else {
        $data[] = $post->post_title;
        $data[] = wp_strip_all_tags(get_the_excerpt($post) ?: wp_trim_words(wp_strip_all_tags($post->post_content), 30));
        $cats = wp_get_post_terms($post->ID, 'category', ['fields' => 'names']);
        if (!is_wp_error($cats)) {
            $data = array_merge($data, $cats);
        }
    }

    return $data;
}

function shop_to_llm_get_meta_description(\WP_Post $post): string
{
    // Try Yoast SEO
    $yoastMeta = get_post_meta($post->ID, '_yoast_wpseo_metadesc', true);
    if (!empty(trim((string) $yoastMeta))) {
        return $yoastMeta;
    }

    // Try Rank Math
    $rankMathMeta = get_post_meta($post->ID, 'rank_math_description', true);
    if (!empty(trim((string) $rankMathMeta))) {
        return $rankMathMeta;
    }

    // Fallback to excerpt
    if (!empty(trim($post->post_excerpt))) {
        return wp_strip_all_tags($post->post_excerpt);
    }

    return '';
}

function shop_to_llm_save_focus_query(int $postId): void
{
    if (!isset($_POST['shop2llm_focus_query_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['shop2llm_focus_query_nonce'])), 'shop2llm_save_focus_query')) {
        return;
    }
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }
    if (!current_user_can('edit_post', $postId)) {
        return;
    }

    $focusQuery = isset($_POST['shop2llm_focus_query']) ? sanitize_text_field(wp_unslash($_POST['shop2llm_focus_query'])) : '';
    update_post_meta($postId, '_shop2llm_focus_query', $focusQuery);

    // Save authoritative list checkbox
    $onList = isset($_POST['shop2llm_on_authoritative_list']) ? 1 : 0;
    update_post_meta($postId, '_shop2llm_on_authoritative_list', $onList);

    // Clear score cache so it recalculates
    delete_transient('shop2llm_score_' . $postId);

    // Also save visibility snapshot
    $score = shop_to_llm_calculate_visibility_score($postId);
    $history = get_post_meta($postId, '_shop2llm_visibility_history', true);
    if (!is_array($history)) {
        $history = [];
    }
    $today = current_time('Y-m-d');
    // Remove any entry for today (update it)
    $history = array_filter($history, function ($entry) use ($today) {
        return $entry['date'] !== $today;
    });
    $history[] = ['date' => $today, 'score' => $score];
    // Keep only last 8 weeks (56 days)
    $cutoff = gmdate('Y-m-d', time() - 56 * DAY_IN_SECONDS);
    $history = array_filter($history, function ($entry) use ($cutoff) {
        return $entry['date'] >= $cutoff;
    });
    $history = array_values($history);
    update_post_meta($postId, '_shop2llm_visibility_history', $history);
}

// ─── AI Visibility Trend — Weekly Snapshot Cron ────────────────────────

function shop_to_llm_save_visibility_snapshot(int $postId): void
{
    $score = shop_to_llm_calculate_visibility_score($postId);
    $history = get_post_meta($postId, '_shop2llm_visibility_history', true);
    if (!is_array($history)) {
        $history = [];
    }
    $today = current_time('Y-m-d');
    $history = array_filter($history, function ($entry) use ($today) {
        return $entry['date'] !== $today;
    });
    $history[] = ['date' => $today, 'score' => $score];
    $cutoff = gmdate('Y-m-d', time() - 56 * DAY_IN_SECONDS);
    $history = array_filter($history, function ($entry) use ($cutoff) {
        return $entry['date'] >= $cutoff;
    });
    $history = array_values($history);
    update_post_meta($postId, '_shop2llm_visibility_history', $history);
}

function shop_to_llm_register_snapshot_cron(): void
{
    if (!wp_next_scheduled('shop2llm_weekly_snapshot')) {
        wp_schedule_event(time(), 'weekly', 'shop2llm_weekly_snapshot');
    }
}

function shop_to_llm_weekly_snapshot_callback(): void
{
    $posts = get_posts([
        'numberposts' => 100,
        'post_status' => 'publish',
        'post_type' => ['post', 'product'],
    ]);
    foreach ($posts as $p) {
        shop_to_llm_save_visibility_snapshot($p->ID);
    }
}

/**
 * Score a single post using the 25-item 5-dimension model.
 * Used by the Dashboard Content AI Score card.
 *
 * @return array{total: int, has_focus: bool, items: array, groups: array}
 */
/**
 * Convert a site URL to an internally-accessible URL.
 * Fixes Docker port-mapping where site_url uses host port (e.g. 8500)
 * but the container listens on 80 internally.
 */
function shop_to_llm_self_url(string $path = ''): string {
    $site_url = get_site_url();
    $parsed = wp_parse_url($site_url);
    $host = $parsed['host'] ?? 'localhost';
    $port = $parsed['port'] ?? '';
    $scheme = $parsed['scheme'] ?? 'http';
    // If localhost with a mapped port, use port 80 for internal access
    if ($host === 'localhost' && $port && $port !== '80' && $port !== '443') {
        $port = '';
    }
    $base = $scheme . '://' . $host;
    if ($port) {
        $base .= ':' . $port;
    }
    return untrailingslashit($base) . $path;
}

/**
 * Get the 7-factor AI Visibility Score for the entire site.
 * Returns total, label, color, bg, and per-factor breakdown.
 * @since 1.0.8
 */
function shop_to_llm_get_visibility_score(): array
{
    $scoreRobots = shop_to_llm_check_robots_txt_allows_ai() ? 20 : 0;

    $scoreLlms = 0;
    $llmsAccessible = get_transient('shop2llm_llms_accessible');
    if ($llmsAccessible === false) {
        // Try HTTP check via self URL (handles Docker port-mapping)
        $llmsUrl = shop_to_llm_self_url('/llms.txt');
        $llmsResp = wp_remote_get($llmsUrl, ['timeout' => 5, 'redirection' => 0]);
        $httpOk = !is_wp_error($llmsResp) && wp_remote_retrieve_response_code($llmsResp) === 200;
        if ($httpOk && strlen(wp_remote_retrieve_body($llmsResp)) > 50) {
            $llmsAccessible = 'yes';
        } else {
            // Fallback: check if rewrite rule is registered (works even if HTTP unreachable)
            $rules = get_option('rewrite_rules');
            $hasRule = $rules && (isset($rules['^llms\.txt$']) || (is_array($rules) && in_array('index.php?shop_to_llm_llms_txt=1', $rules, true)));
            $llmsAccessible = $hasRule ? 'yes' : 'no';
        }
        set_transient('shop2llm_llms_accessible', $llmsAccessible, HOUR_IN_SECONDS);
    }
    if ($llmsAccessible === 'yes') $scoreLlms = 20;

    $pubCount = shop_to_llm_has_woocommerce()
        ? (int)(wp_count_posts('product')->publish ?? 0)
        : (int)(wp_count_posts('post')->publish ?? 0);

    $scoreSchema = 0;
    // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- 'active_plugins' is a WordPress core hook
    $activePlugins = apply_filters('active_plugins', get_option('active_plugins', []));
    $seoSlugs = ['wordpress-seo/wp-seo.php', 'seo-by-rank-math/rank-math.php', 'all-in-one-seo-pack/all_in_one_seo_pack.php', 'wp-seopress/seopress.php'];
    $schemaHasConflict = (bool) array_intersect($seoSlugs, $activePlugins);
    if ($schemaHasConflict) { $scoreSchema = 12; }
    elseif ($pubCount > 0) { $scoreSchema = 15; }
    else { $scoreSchema = 10; }

    $todayCount = shop_to_llm_get_today_api_count();
    $scoreMcp = 0;
    $mcpEnabled = get_option('shop_to_llm_enabled', 'yes') === 'yes';
    if ($mcpEnabled && $todayCount > 0) { $scoreMcp = 15; }
    elseif ($mcpEnabled) { $scoreMcp = 5; }

    $scoreFeed = 0;
    if (shop_to_llm_has_woocommerce()) {
        $feedAccessible = get_transient('shop2llm_feed_accessible');
        if ($feedAccessible === false) {
            // Try HTTP check via self URL (handles Docker port-mapping)
            $feedUrl = shop_to_llm_self_url('/product-feed.xml');
            $feedResp = wp_remote_get($feedUrl, ['timeout' => 5, 'redirection' => 0]);
            $httpOk = !is_wp_error($feedResp) && wp_remote_retrieve_response_code($feedResp) === 200;
            if ($httpOk && strlen(wp_remote_retrieve_body($feedResp)) > 100) {
                $feedAccessible = 'yes';
            } else {
                // Fallback: check if rewrite rule is registered
                $rules = get_option('rewrite_rules');
                $hasRule = $rules && (isset($rules['^product-feed\.xml$']) || (is_array($rules) && in_array('index.php?shop_to_llm_feed=1', $rules, true)));
                $feedAccessible = $hasRule ? 'yes' : 'no';
            }
            set_transient('shop2llm_feed_accessible', $feedAccessible, HOUR_IN_SECONDS);
        }
        if ($feedAccessible === 'yes') $scoreFeed = 10;
    } else { $scoreFeed = 10; }

    // Queries score: based on how many posts/products have Focus Query set
    global $wpdb;
    $scoreQueries = 0;
    $fqCount = (int) $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
        "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value != ''", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        '_shop2llm_focus_query'
    ));
    if ($pubCount > 0) {
        $fqRatio = $fqCount / $pubCount;
        if ($fqRatio >= 0.5) $scoreQueries = 10;
        elseif ($fqRatio >= 0.25) $scoreQueries = 7;
        elseif ($fqRatio >= 0.1) $scoreQueries = 5;
        elseif ($fqCount > 0) $scoreQueries = 3;
    }

    // Crawlers score: based on AI crawler visits in last 30 days
    $scoreCrawlers = 0;
    $cTable = $wpdb->prefix . 'shop2llm_crawler_log';
    $cTableExists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $cTable)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    if ($cTableExists) {
        $thirtyDaysAgo = gmdate('Y-m-d H:i:s', time() - 30 * DAY_IN_SECONDS);
        $crawlerTotal30d = (int) $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
            "SELECT COUNT(*) FROM {$cTable} WHERE visited_at >= %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
            $thirtyDaysAgo
        ));
        $uniqueBots30d = (int) $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
            "SELECT COUNT(DISTINCT bot_name) FROM {$cTable} WHERE visited_at >= %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
            $thirtyDaysAgo
        ));
        if ($uniqueBots30d >= 4 && $crawlerTotal30d >= 20) $scoreCrawlers = 10;
        elseif ($uniqueBots30d >= 2 && $crawlerTotal30d >= 5) $scoreCrawlers = 7;
        elseif ($crawlerTotal30d >= 1) $scoreCrawlers = 5;
    }

    // Total based on all 7 factors (max 100)
    $total = $scoreRobots + $scoreLlms + $scoreSchema + $scoreMcp + $scoreFeed + $scoreQueries + $scoreCrawlers;
    $freeMax = 100;

    $label = $total >= 90 ? __('Excellent', 'shop-to-llm') : ($total >= 70 ? __('Good', 'shop-to-llm') : ($total >= 50 ? __('Fair', 'shop-to-llm') : ($total >= 30 ? __('Poor', 'shop-to-llm') : __('Critical', 'shop-to-llm'))));
    $color = $total >= 70 ? '#22c55e' : ($total >= 40 ? '#eab308' : '#ef4444');
    $bg = $total >= 70 ? '#f0fdf4' : ($total >= 40 ? '#fefce8' : '#fef2f2');

    return [
        'total' => $total, 'free_max' => $freeMax,
        'label' => $label, 'color' => $color, 'bg' => $bg,
        'schema_has_conflict' => $schemaHasConflict,
        'factors' => [
            'robots' => $scoreRobots, 'llms' => $scoreLlms, 'schema' => $scoreSchema,
            'mcp' => $scoreMcp, 'feed' => $scoreFeed,
            'queries' => $scoreQueries, 'crawlers' => $scoreCrawlers,
        ],
    ];
}

function shop_to_llm_score_single_post(int $postId): array
{
    $post = get_post($postId);
    if (!$post || $post->post_status !== 'publish') {
        return ['total' => 0, 'has_focus' => false, 'items' => [], 'groups' => []];
    }

    $focusQuery = get_post_meta($postId, '_shop2llm_focus_query', true) ?: '';
    $hasFocusQuery = !empty(trim((string)$focusQuery));
    $content = $post->post_content;
    $entityChecksEnabled = (bool) get_option('shop_to_llm_enable_entity_checks', 0);
    $items = [];
    $total = 0;

    // ─── L1: Discoverability (30 pts, items 1-6) ───
    $llmsTxt = get_transient('shop_to_llm_llms_txt_output');
    if (!$llmsTxt) { ob_start(); shop_to_llm_render_llms_txt(); $llmsTxt = ob_get_clean(); }
    $i1 = $hasFocusQuery ? ($llmsTxt && stripos($llmsTxt, $focusQuery) !== false) : !empty($llmsTxt);
    if ($i1) $total += 8;
    $items[] = ['name' => __('llms.txt contains query', 'shop-to-llm'), 'pass' => $i1, 'pts' => 8];

    $i2 = shop_to_llm_check_robots_txt_allows_ai();
    if ($i2) $total += 6;
    $items[] = ['name' => __('robots.txt allows AI', 'shop-to-llm'), 'pass' => $i2, 'pts' => 6];

    $sd = shop_to_llm_get_post_schema_data($post);
    $i3 = $hasFocusQuery ? (!empty($sd) && stripos(implode(' ', $sd), $focusQuery) !== false) : !empty($sd);
    if ($i3) $total += 6;
    $items[] = ['name' => __('Schema contains query', 'shop-to-llm'), 'pass' => $i3, 'pts' => 6];

    $i4 = (bool) get_option('shop_to_llm_mcp_connected', false);
    if ($i4) $total += 4;
    $items[] = ['name' => __('MCP tools connected', 'shop-to-llm'), 'pass' => $i4, 'pts' => 4];

    $i5 = shop_to_llm_check_prerendered_html($post->ID);
    if ($i5) $total += 4;
    $items[] = ['name' => __('Pre-rendered HTML readable', 'shop-to-llm'), 'pass' => $i5, 'pts' => 4];

    $i6 = (stripos($content, 'FAQPage') !== false || stripos($content, '<!-- wp:faq') !== false || preg_match('/<h[2-4][^>]*>.*\?.*<\/h/i', $content));
    if ($i6) $total += 2;
    $items[] = ['name' => __('FAQPage schema present', 'shop-to-llm'), 'pass' => $i6, 'pts' => 2];

    // ─── L2: Relevance (25 pts, items 7-12) ───
    if ($hasFocusQuery) {
        $tLen = strlen($post->post_title);
        $i7pts = 0;
        if ($tLen > 0 && stripos($post->post_title, $focusQuery) !== false) {
            $r = stripos($post->post_title, $focusQuery) / $tLen;
            $i7pts = $r < 0.3 ? 6 : ($r < 0.7 ? 4 : 2);
        }
        $total += $i7pts;
        $items[] = ['name' => __('Query in title', 'shop-to-llm'), 'pass' => $i7pts > 0, 'pts' => $i7pts];

        $i8pts = 0;
        if (preg_match('/<h1[^>]*>.*' . preg_quote($focusQuery, '/') . '.*<\/h1>/i', $content)) $i8pts = 5;
        elseif (preg_match('/<h2[^>]*>.*' . preg_quote($focusQuery, '/') . '.*<\/h2>/i', $content)) $i8pts = 3;
        $total += $i8pts;
        $items[] = ['name' => __('Query in heading', 'shop-to-llm'), 'pass' => $i8pts > 0, 'pts' => $i8pts];

        $fw = implode(' ', array_slice(explode(' ', wp_strip_all_tags($content)), 0, 100));
        $i9 = (stripos($fw, $focusQuery) !== false);
        if ($i9) $total += 4;
        $items[] = ['name' => __('Query in first 100 words', 'shop-to-llm'), 'pass' => $i9, 'pts' => 4];

        $i10 = (stripos($post->post_name, $focusQuery) !== false);
        if ($i10) $total += 4;
        $items[] = ['name' => __('Query in URL/slug', 'shop-to-llm'), 'pass' => $i10, 'pts' => 4];

        $md = shop_to_llm_get_meta_description($post);
        $i11 = (!empty($md) && stripos($md, $focusQuery) !== false);
        if ($i11) $total += 3;
        $items[] = ['name' => __('Query in meta description', 'shop-to-llm'), 'pass' => $i11, 'pts' => 3];

        $altF = false;
        foreach (get_posts(['post_type' => 'attachment', 'posts_per_page' => -1, 'post_parent' => $post->ID]) as $a) {
            $alt = get_post_meta($a->ID, '_wp_attachment_image_alt', true);
            if (!empty($alt) && stripos($alt, $focusQuery) !== false) { $altF = true; break; }
        }
        if ($altF) $total += 3;
        $items[] = ['name' => __('Query in image alt text', 'shop-to-llm'), 'pass' => $altF, 'pts' => 3];
    } else {
        $l2Names = [__('Query in title', 'shop-to-llm'), __('Query in heading', 'shop-to-llm'), __('Query in first 100 words', 'shop-to-llm'), __('Query in URL/slug', 'shop-to-llm'), __('Query in meta description', 'shop-to-llm'), __('Query in image alt text', 'shop-to-llm')];
        foreach ($l2Names as $n) { $items[] = ['name' => $n, 'pass' => false, 'pts' => 0, 'locked' => true]; }
    }

    // ─── L3: Content Quality (25 pts, items 13-18) ───
    $wc = str_word_count(wp_strip_all_tags($content));
    $i13pts = $wc > 300 ? 4 : ($wc > 150 ? 2 : 0);
    $total += $i13pts;
    $items[] = ['name' => __('Content length > 300 words', 'shop-to-llm'), 'pass' => $i13pts > 0, 'pts' => $i13pts];

    $i14 = shop_to_llm_has_statistics($content);
    if ($i14) $total += 5;
    $items[] = ['name' => __('Contains statistics/claims', 'shop-to-llm'), 'pass' => $i14, 'pts' => 5];

    $i15 = shop_to_llm_has_citations($content);
    if ($i15) $total += 5;
    $items[] = ['name' => __('Contains citations/quotes', 'shop-to-llm'), 'pass' => $i15, 'pts' => 5];

    $daysMod = (time() - strtotime($post->post_modified)) / DAY_IN_SECONDS;
    $i16pts = $daysMod <= 30 ? 4 : ($daysMod <= 90 ? 2 : 0);
    $total += $i16pts;
    $items[] = ['name' => __('Content freshness', 'shop-to-llm'), 'pass' => $i16pts > 0, 'pts' => $i16pts];

    $sf = 0;
    if (!empty($post->post_title)) $sf++;
    if (!empty(get_the_excerpt($post))) $sf++;
    if (has_post_thumbnail($post->ID)) $sf++;
    if (get_the_author_meta('display_name', $post->post_author)) $sf++;
    $i17pts = $sf >= 4 ? 4 : ($sf >= 3 ? 3 : ($sf >= 2 ? 2 : ($sf >= 1 ? 1 : 0)));
    $total += $i17pts;
    $items[] = ['name' => __('Schema completeness', 'shop-to-llm'), 'pass' => $i17pts > 0, 'pts' => $i17pts];

    $il = shop_to_llm_count_internal_links($content);
    $i18pts = $il >= 3 ? 3 : ($il >= 1 ? 1 : 0);
    $total += $i18pts;
    $items[] = ['name' => __('Internal links >= 3', 'shop-to-llm'), 'pass' => $i18pts > 0, 'pts' => $i18pts];

    // ─── L4: Entity Authority (15 pts, items 19-21) ───
    if ($hasFocusQuery) {
        $i19 = ($entityChecksEnabled && shop_to_llm_check_wikidata_entity($focusQuery));
        if ($i19) $total += 5;
        $items[] = ['name' => __('Brand in knowledge graph', 'shop-to-llm'), 'pass' => $i19, 'pts' => 5];

        $i20 = ($entityChecksEnabled && shop_to_llm_check_wikipedia_mention($focusQuery));
        if ($i20) $total += 5;
        $items[] = ['name' => __('Wikipedia mention', 'shop-to-llm'), 'pass' => $i20, 'pts' => 5];

        $onAuth = get_post_meta($postId, '_shop2llm_on_authoritative_list', true);
        $i21 = !empty($onAuth);
        if ($i21) $total += 5;
        $items[] = ['name' => __('On authoritative lists', 'shop-to-llm'), 'pass' => $i21, 'pts' => 5];
    } else {
        $l4Names = [__('Brand in knowledge graph', 'shop-to-llm'), __('Wikipedia mention', 'shop-to-llm'), __('On authoritative lists', 'shop-to-llm')];
        foreach ($l4Names as $n) { $items[] = ['name' => $n, 'pass' => false, 'pts' => 0, 'locked' => true]; }
    }

    // ─── L5: Activity (5 pts, items 22-25) ───
    $cs = shop_to_llm_get_post_crawler_stats($post->ID);
    $i22pts = 0;
    if (!empty($cs['last_visit']) && $cs['last_visit'] !== '—') {
        $daysVisit = (time() - strtotime($cs['last_visit'])) / DAY_IN_SECONDS;
        $i22pts = $daysVisit <= 7 ? 2 : ($daysVisit <= 30 ? 1 : 0);
    }
    $total += $i22pts;
    $items[] = ['name' => __('AI crawler visit < 7 days', 'shop-to-llm'), 'pass' => $i22pts > 0, 'pts' => $i22pts];

    $i23 = ($post->comment_count > 0);
    if ($i23) $total += 1;
    $items[] = ['name' => __('Reviews/ratings exist', 'shop-to-llm'), 'pass' => $i23, 'pts' => 1];

    global $wpdb;
    $ct = $wpdb->prefix . 'shop2llm_crawler_log';
    $te = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $ct)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $i24 = false;
    if ($te) {
        $db = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(DISTINCT bot_name) FROM {$ct} WHERE post_id = %d", $post->ID)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $i24 = ($db >= 4);
    }
    if ($i24) $total += 1;
    $items[] = ['name' => __('Multi-platform presence 4+', 'shop-to-llm'), 'pass' => $i24, 'pts' => 1];

    $i25 = false;
    if ($hasFocusQuery) {
        $density = shop_to_llm_keyword_density($content, $focusQuery);
        $i25 = ($density >= 0.5 && $density <= 2.5);
    }
    if ($i25) $total += 1;
    $items[] = ['name' => __('Keyword density 0.5-2.5%', 'shop-to-llm'), 'pass' => $i25, 'pts' => $i25 ? 1 : 0, 'locked' => !$hasFocusQuery];

    return ['total' => $total, 'has_focus' => $hasFocusQuery, 'items' => $items, 'groups' => []];
}

// Load Citation Widget
require_once __DIR__ . '/includes/class-citation-widget.php';
add_action('init', ['Shop2LLM_Citation_Widget', 'init']);
