<?php

// ===============================================================
// VMO CLIENT VERSION - Vultr client instances route through Kinsta
// ===============================================================
if (!defined('VMO_CLIENT_MODE')) {
    define('VMO_CLIENT_MODE', true);
}
if (!defined('VMO_MASTER_VERSION')) {
    define('VMO_MASTER_VERSION', false);
}
if (!defined('VMO_MASTER_PROXY_URL')) {
    define('VMO_MASTER_PROXY_URL', 'https://getvmo.com/wp-json/vmo-builder/v1/proxy');
}

if (!function_exists('vmo_client_allowed_admin_pages')) {
    function vmo_client_allowed_admin_pages() {
        return ['vmo-ai-builder-main', 'vmo-ai-dashboard', 'vmo-my-plan', 'vmo-html-to-pdf'];
    }
}

if (!function_exists('vmo_client_is_allowed_admin_page')) {
    function vmo_client_is_allowed_admin_page($page) {
        return in_array($page, vmo_client_allowed_admin_pages(), true);
    }
}

add_action('admin_menu', function() {
    if (!defined('VMO_CLIENT_MODE') || VMO_CLIENT_MODE !== true) {
        return;
    }

    global $submenu;

    if (!empty($submenu['vmo-ai-builder-main']) && is_array($submenu['vmo-ai-builder-main'])) {
        foreach ($submenu['vmo-ai-builder-main'] as $index => $item) {
            $slug = $item[2] ?? '';
            if ($slug !== '' && !vmo_client_is_allowed_admin_page($slug)) {
                unset($submenu['vmo-ai-builder-main'][$index]);
            }
        }
        $submenu['vmo-ai-builder-main'] = array_values($submenu['vmo-ai-builder-main']);
    }

    foreach (['vmo-ai-settings', 'vmo-master-admin', 'vmo-twilio-settings'] as $restrictedSlug) {
        remove_submenu_page('vmo-ai-builder-main', $restrictedSlug);
        remove_submenu_page('vmo-dashboard', $restrictedSlug);
    }
}, 999);

add_action('admin_init', function() {
    if (!defined('VMO_CLIENT_MODE') || VMO_CLIENT_MODE !== true || !is_admin()) {
        return;
    }

    $page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
    if ($page === '' || strpos($page, 'vmo') !== 0) {
        return;
    }

    if (vmo_client_is_allowed_admin_page($page)) {
        return;
    }

    wp_safe_redirect(admin_url('admin.php?page=vmo-ai-dashboard'));
    exit;
}, 1);

/*
Plugin Name: VMO
Plugin URI:  https://getvmo.com
Description: The first WordPress plugin that replaces your web designer, marketing team, and business manager with one AI-powered consultation. Tell it about your business — it researches your market, builds a custom website from scratch, sets up payments, email, phone, SEO, and a full operations dashboard. No templates. No guesswork. Just a real business, running.
Version: 1.0.0
Author: Jerreme Josephson LLC
Author URI: https://virtualmediaoffice.com
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: vmo-ai-builder
Domain Path: /languages
Requires at least: 6.0
Requires PHP: 7.4
*/

if (!defined('ABSPATH')) exit;
// ── VMO Table Existence Cache ──
// Caches SHOW TABLES results in a static array for the request lifecycle.
// Eliminates ~200+ redundant SHOW TABLES queries per page load.

// ── VMO AI Rate Limiter ──
// Prevents excessive OpenAI API calls (cost protection + rate limit avoidance)
if (!function_exists('vmo_check_ai_rate_limit')) {
    function vmo_check_ai_rate_limit($user_id, $action = 'ai_call', $max_per_minute = 5, $max_per_hour = 30) {
        $minute_key = "vmo_rate_{$action}_{$user_id}_min";
        $hour_key   = "vmo_rate_{$action}_{$user_id}_hr";
        
        $minute_count = (int) get_transient($minute_key);
        $hour_count   = (int) get_transient($hour_key);
        
        if ($minute_count >= $max_per_minute) {
            return new WP_Error('rate_limited', 'Too many AI requests. Please wait a moment before trying again.', ['retry_after' => 60]);
        }
        if ($hour_count >= $max_per_hour) {
            return new WP_Error('rate_limited', 'Hourly AI request limit reached. Please try again later.', ['retry_after' => 3600]);
        }
        
        // Increment counters
        set_transient($minute_key, $minute_count + 1, 60);
        set_transient($hour_key, $hour_count + 1, 3600);
        
        return true; // Allowed
    }
}

if (!function_exists('vmo_table_exists')) {
    function vmo_table_exists($table_name) {
        static $cache = [];
        if (isset($cache[$table_name])) {
            return $cache[$table_name];
        }
        global $wpdb;
        $result = (bool) $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name));
        $cache[$table_name] = $result;
        return $result;
    }
}

// ── WP_Filesystem helper ──
// Returns the global $wp_filesystem object, initializing it if needed.
// Used instead of file_put_contents / file_get_contents for WP.org compliance.
if (!function_exists('vmo_get_filesystem')) {
    function vmo_get_filesystem() {
        global $wp_filesystem;
        if (empty($wp_filesystem)) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
            WP_Filesystem();
        }
        return $wp_filesystem;
    }
}



// ═══════════════════════════════════════════════════════════════════
// Global wp_mail From Name & Address
// Ensures ALL emails from the plugin display the site name instead
// of the default "WordPress".  Uses the site title + admin email.
// ═══════════════════════════════════════════════════════════════════
add_filter('wp_mail_from_name', function ($name) {
    // Only override the default "WordPress" (or blank) — respect
    // explicit overrides from other plugins / SMTP plugins.
    if ($name === 'WordPress' || $name === '' || $name === 'wordpress') {
        $site = get_bloginfo('name');
        return $site ? $site : 'VMO AI';
    }
    return $name;
});
add_filter('wp_mail_from', function ($email) {
    // Replace the default wordpress@domain with the site admin email
    if (strpos($email, 'wordpress@') === 0) {
        $admin = get_option('admin_email');
        return $admin ? $admin : $email;
    }
    return $email;
});

/**
 * Public Twilio endpoints fallback.
 *
 * On getvmo.com, REST (`/wp-json` and `rest_route`) and sometimes admin-ajax can be
 * rewritten/blocked by hosting or security rules, which causes Twilio to fetch HTML
 * (homepage) and announce "application error".
 *
 * These endpoints are query-param based and run before theme output.
 * Example: https://getvmo.com/?vmo_twilio=power_dialer_twiml&twiml_action=conference&conference=test
 */
add_action('init', function () {
    if (empty($_GET['vmo_twilio'])) {
        return;
    }

    $kind = sanitize_key((string) $_GET['vmo_twilio']);

    if (!class_exists('VMO_Twilio_Manager')) {
        // If the manager isn't available, we can't serve TwiML.
        status_header(500);
        header('Content-Type: text/plain; charset=UTF-8');
        echo 'Twilio manager unavailable';
        exit;
    }

    $manager = VMO_Twilio_Manager::get_instance();

    // Minimal REST-like request wrapper.
    $params = wp_unslash($_REQUEST);
    $request = new class($params) {
        private array $params;
        public function __construct(array $params) { $this->params = $params; }
        public function get_params(): array { return $this->params; }
        public function get_json_params(): array { return array(); }
    };

    // Never cache webhook responses.
    if (function_exists('nocache_headers')) {
        nocache_headers();
    }

    try {
        switch ($kind) {
            case 'ping':
                header('Content-Type: text/plain; charset=UTF-8');
                echo 'pong';
                exit;

            case 'voice':
                $manager->handle_voice_webhook($request);
                exit;

            case 'browser_call':
                $manager->handle_browser_call($request);
                exit;

            case 'pd_twiml':
            case 'power_dialer_twiml':
                $manager->power_dialer_twiml($request);
                exit;

            case 'status':
            case 'status_callback':
                $resp = $manager->handle_status_callback($request);
                header('Content-Type: application/json; charset=UTF-8');
                if ($resp instanceof WP_REST_Response) {
                    status_header($resp->get_status());
                    echo wp_json_encode($resp->get_data());
                } elseif (is_wp_error($resp)) {
                    status_header(500);
                    echo wp_json_encode(array('success' => false, 'message' => $resp->get_error_message()));
                } else {
                    status_header(200);
                    echo wp_json_encode(is_array($resp) ? $resp : array('success' => true));
                }
                exit;

            case 'sms':
            case 'sms_webhook':
                $resp = $manager->handle_sms_webhook($request);
                header('Content-Type: application/json; charset=UTF-8');
                if ($resp instanceof WP_REST_Response) {
                    status_header($resp->get_status());
                    echo wp_json_encode($resp->get_data());
                } else {
                    status_header(200);
                    echo wp_json_encode(array('success' => true));
                }
                exit;

            case 'dial_status':
                $resp = $manager->handle_dial_status($request);
                header('Content-Type: application/json; charset=UTF-8');
                if ($resp instanceof WP_REST_Response) {
                    status_header($resp->get_status());
                    echo wp_json_encode($resp->get_data());
                } else {
                    status_header(200);
                    echo wp_json_encode(array('success' => true));
                }
                exit;

            case 'transcription':
                $resp = $manager->handle_transcription_callback($request);
                header('Content-Type: application/json; charset=UTF-8');
                if ($resp instanceof WP_REST_Response) {
                    status_header($resp->get_status());
                    echo wp_json_encode($resp->get_data());
                } else {
                    status_header(200);
                    echo wp_json_encode(array('success' => true));
                }
                exit;

            case 'recording':
                $resp = $manager->handle_recording_callback($request);
                header('Content-Type: application/json; charset=UTF-8');
                status_header(200);
                echo wp_json_encode(is_array($resp) ? $resp : array('ok' => true));
                exit;

            case 'pd_outbound_callback':
                $resp = $manager->power_dialer_outbound_callback($request);
                header('Content-Type: application/json; charset=UTF-8');
                if ($resp instanceof WP_REST_Response) {
                    status_header($resp->get_status());
                    echo wp_json_encode($resp->get_data());
                } else {
                    status_header(200);
                    echo wp_json_encode(is_array($resp) ? $resp : array('success' => true));
                }
                exit;

            case 'ext_ring':
                $manager->handle_ext_ring($request);
                exit;

            case 'ext_listen_action':
                $manager->handle_ext_listen_action($request);
                exit;

            case 'ext_monitor':
                $manager->handle_ext_monitor($request);
                exit;

            default:
                status_header(404);
                header('Content-Type: text/plain; charset=UTF-8');
                echo 'Unknown endpoint';
                exit;
        }
    } catch (Throwable $e) {
        status_header(500);
        header('Content-Type: text/plain; charset=UTF-8');
        echo 'Endpoint error';
        exit;
    }
}, 0);

/**
 * Handle Stripe Connect return / refresh URLs.
 *
 * When Stripe onboarding redirects back to:
 *   - home_url('/?vmo_stripe_return=1&account_id=...')  (from provision flow)
 *   - home_url('/?vmo_stripe_refresh=1&account_id=...')  (onboarding expired / needs retry)
 *
 * This handler syncs the account status and sends the user to the WP admin dashboard
 * with a clear success or retry message.
 */
add_action('template_redirect', function () {
    $is_return  = isset($_GET['vmo_stripe_return'])  && $_GET['vmo_stripe_return'] == '1';
    $is_refresh = isset($_GET['vmo_stripe_refresh']) && $_GET['vmo_stripe_refresh'] == '1';

    if (!$is_return && !$is_refresh) {
        return;
    }

    $account_id = isset($_GET['account_id']) ? sanitize_text_field(wp_unslash($_GET['account_id'])) : '';
    $dashboard  = admin_url('admin.php?page=vmo-ai-dashboard');

    // ----- REFRESH: onboarding link expired, generate a new one -----
    if ($is_refresh && $account_id) {
        try {
            if (class_exists('VMO_Stripe_Connect')) {
                require_once VMO_AI_BUILDER_DIR . 'stripe-sdk/init.php';
                $sk = get_option('vmo_stripe_secret_key');
                if ($sk) {
                    \Stripe\Stripe::setApiKey($sk);
                    $link = \Stripe\AccountLink::create([
                        'account'     => $account_id,
                        'refresh_url' => home_url('/?vmo_stripe_refresh=1&account_id=' . $account_id),
                        'return_url'  => home_url('/?vmo_stripe_return=1&account_id=' . $account_id),
                        'type'        => 'account_onboarding',
                    ]);
                    wp_redirect($link->url);
                    exit;
                }
            }
        } catch (Throwable $e) {
            // Fall through to dashboard with error notice
        }
        wp_redirect(add_query_arg('stripe_onboard', 'refresh_error', $dashboard));
        exit;
    }

    // ----- RETURN: user finished Stripe onboarding, sync status -----
    if ($is_return) {
        // Sync account status to DB if possible
        if ($account_id) {
            try {
                require_once VMO_AI_BUILDER_DIR . 'stripe-sdk/init.php';
                $sk = get_option('vmo_stripe_secret_key');
                if ($sk) {
                    \Stripe\Stripe::setApiKey($sk);
                    $acct = \Stripe\Account::retrieve($account_id);

                    global $wpdb;
                    $table = $wpdb->prefix . 'vmo_connected_accounts';
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                    $wpdb->update($table, [
                        'details_submitted' => intval($acct->details_submitted),
                        'charges_enabled'   => intval($acct->charges_enabled),
                        'payouts_enabled'   => intval($acct->payouts_enabled),
                        'capabilities'      => wp_json_encode($acct->capabilities),
                    ], ['stripe_account_id' => $account_id]);
                }
            } catch (Throwable $e) {
                // Non-blocking: webhook will eventually sync anyway
            }
        }

        wp_redirect(add_query_arg('stripe_onboard', 'return', $dashboard));
        exit;
    }
});

// Ensure Twilio can reach our public REST webhooks even if another plugin
// globally blocks REST requests (common cause of Twilio "application error").
add_filter('rest_authentication_errors', function ($result) {
    // If nothing has blocked REST, do nothing.
    if (empty($result)) {
        return $result;
    }

    $uri = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : '';
    if ($uri === '') {
        return $result;
    }

    // Only bypass for specific public Twilio endpoints.
    $public_paths = array(
        '/wp-json/vmo/v1/twilio-voice-webhook',
        '/wp-json/vmo/v1/twilio-status-callback',
        '/wp-json/vmo/v1/twilio-sms-webhook',
        '/wp-json/vmo/v1/dial-status-callback',
        '/wp-json/vmo/v1/twilio-transcription-callback',
        '/wp-json/vmo/v1/recording-callback',
        '/wp-json/vmo/v1/power-dialer/twiml',
        '/wp-json/vmo/v1/power-dialer/outbound-callback',
    );

    foreach ($public_paths as $path) {
        if (strpos($uri, $path) !== false) {
            return null;
        }
    }

    return $result;
}, 99);

// MASTER VERSION FLAG
// This version has full access to all features without subscription checks
// Set to false in client/distribution version via create_client_zip.php
if (!defined('VMO_MASTER_VERSION')) {
    define('VMO_MASTER_VERSION', true);
}

// Define plugin file constant for activation hooks
if (!defined('VMO_PLUGIN_FILE')) {
    define('VMO_PLUGIN_FILE', __FILE__);
}

// Define plugin directory constants with fallback for early loading
if (!defined('VMO_AI_BUILDER_DIR')) {
    if (function_exists('plugin_dir_path')) {
        define('VMO_AI_BUILDER_DIR', plugin_dir_path(__FILE__));
    } else {
        define('VMO_AI_BUILDER_DIR', dirname(__FILE__) . '/');
    }
}

if (!defined('VMO_AI_BUILDER_URL')) {
    if (function_exists('plugin_dir_url')) {
        define('VMO_AI_BUILDER_URL', plugin_dir_url(__FILE__));
    } else {
        // Fallback URL - will be corrected when WordPress loads
        define('VMO_AI_BUILDER_URL', '');
    }
}
// Back-compat constants for auxiliary classes
if (!defined('VMO_PLUGIN_DIR')) {
    define('VMO_PLUGIN_DIR', VMO_AI_BUILDER_DIR);
}
if (!defined('VMO_PLUGIN_URL')) {
    define('VMO_PLUGIN_URL', VMO_AI_BUILDER_URL);
}

// Register every text domain this plugin uses so WP's JIT loader never
// has to resolve them from a null path.
if (!function_exists('vmo_register_textdomains')) {
    function vmo_register_textdomains() {
        $domain_path = dirname(plugin_basename(__FILE__)) . '/languages';
        foreach (['vmo-ai-builder', 'vmo', 'vmo-consultant'] as $textdomain) {
            load_plugin_textdomain($textdomain, false, $domain_path);
        }
    }
}
vmo_register_textdomains();
add_action('init', 'vmo_register_textdomains', 1);

// === VMO Modular Architecture (Dec 2025) ===
// Load Core Services and Modules
$vmo_includes_dir = plugin_dir_path(__FILE__) . 'includes/';

// === MASTER MODE: Load API Proxy and Client Manager ===
// These classes handle incoming requests from client installations
if (VMO_MASTER_VERSION === true) {
    // API Proxy Server - routes client API requests to providers
    if (file_exists($vmo_includes_dir . 'class-vmo-api-proxy.php')) {
        require_once $vmo_includes_dir . 'class-vmo-api-proxy.php';
    }
    
    // Master Client Manager - handles license validation, subscription sync
    if (file_exists($vmo_includes_dir . 'class-vmo-master-client-manager.php')) {
        require_once $vmo_includes_dir . 'class-vmo-master-client-manager.php';
    }

    // Master CRM Sync - Auto-sync VMO Users to CRM Contacts
    if (file_exists($vmo_includes_dir . 'class-vmo-master-sync.php')) {
        require_once $vmo_includes_dir . 'class-vmo-master-sync.php';
    }
    
    // Admin Dashboards - User Management, Affiliate Management, Support
    // Removed per user request
    
    // Customer Service & Support System - Jarvis AI, Live Chat, Feedback
    // Removed per user request
}

if (file_exists($vmo_includes_dir . 'modules/core/class-vmo-service.php')) {
    require_once $vmo_includes_dir . 'modules/core/class-vmo-service.php';
}

if (file_exists($vmo_includes_dir . 'orchestration/class-vmo-plan-policy.php')) {
    require_once $vmo_includes_dir . 'orchestration/class-vmo-plan-policy.php';
}

if (file_exists($vmo_includes_dir . 'orchestration/class-vmo-model-router.php')) {
    require_once $vmo_includes_dir . 'orchestration/class-vmo-model-router.php';
}

if (file_exists($vmo_includes_dir . 'state/class-vmo-jurisdiction-engine.php')) {
    require_once $vmo_includes_dir . 'state/class-vmo-jurisdiction-engine.php';
}

if (file_exists($vmo_includes_dir . 'state/class-vmo-master-business-brief.php')) {
    require_once $vmo_includes_dir . 'state/class-vmo-master-business-brief.php';
}

if (file_exists($vmo_includes_dir . 'qa/class-vmo-website-qa.php')) {
    require_once $vmo_includes_dir . 'qa/class-vmo-website-qa.php';
}

if (file_exists($vmo_includes_dir . 'orchestration/class-vmo-approval-guard.php')) {
    require_once $vmo_includes_dir . 'orchestration/class-vmo-approval-guard.php';
}

if (file_exists($vmo_includes_dir . 'orchestration/class-vmo-agent-orchestrator.php')) {
    require_once $vmo_includes_dir . 'orchestration/class-vmo-agent-orchestrator.php';
}

if (file_exists($vmo_includes_dir . 'modules/services/class-vmo-openai-service.php')) {
    require_once $vmo_includes_dir . 'modules/services/class-vmo-openai-service.php';
}

if (file_exists($vmo_includes_dir . 'modules/services/class-vmo-twilio-service.php')) {
    require_once $vmo_includes_dir . 'modules/services/class-vmo-twilio-service.php';
}

// === LOAD TWILIO MANAGER (The class that handles phone provisioning) ===
$twilio_manager_file = $vmo_includes_dir . 'class-vmo-twilio-manager.php';
if (file_exists($twilio_manager_file)) {
    require_once $twilio_manager_file;
    // Instantiate it immediately so its hooks are registered
    if (class_exists('VMO_Twilio_Manager')) {
        VMO_Twilio_Manager::get_instance();
    }
} else {
    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log('VMO: Missing file - class-vmo-twilio-manager.php');
    }
}

if (file_exists($vmo_includes_dir . 'modules/subscription/class-vmo-subscription-manager.php')) {
    require_once $vmo_includes_dir . 'modules/subscription/class-vmo-subscription-manager.php';
}

if (file_exists($vmo_includes_dir . 'modules/generator/class-vmo-generator-engine.php')) {
    require_once $vmo_includes_dir . 'modules/generator/class-vmo-generator-engine.php';
}

// === STANDALONE MODULE: HTML TO PDF PUBLISHER ===
// For VMO Master/Bonded Distributions, load the converter natively.
$vmo_pdf_module = VMO_AI_BUILDER_DIR . 'vmo-html-to-pdf-converter/vmo-html-to-pdf.php';
if (file_exists($vmo_pdf_module)) {
    require_once $vmo_pdf_module;
}

// Initialize Modules
global $vmo_subscription_manager, $vmo_generator_engine, $vmo_affiliate_manager;

if (class_exists('VMO_Subscription_Manager')) {
    $vmo_subscription_manager = new VMO_Subscription_Manager();
}

if (class_exists('VMO_Generator_Engine')) {
    $vmo_generator_engine = new VMO_Generator_Engine();
}

// === Affiliate System (Dec 2025) ===
// ✅ Safe loading - prevent crashes if file doesn't exist on server
$affiliate_file = VMO_AI_BUILDER_DIR . 'includes/class-vmo-affiliate-manager.php';
global $vmo_affiliate_manager;
$vmo_affiliate_manager = null;

if (file_exists($affiliate_file)) {
    require_once $affiliate_file;
    if (class_exists('VMO_Affiliate_Manager')) {
        $vmo_affiliate_manager = new VMO_Affiliate_Manager();
    }
} else {
    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log('VMO: Missing file - class-vmo-affiliate-manager.php (expected at: ' . $affiliate_file . ')');
    }
}

// === Elite 8-Phase Consultation System (Jan 2025) ===
// ✅ Safe loading for phase/consultation files
$phase_output_file = VMO_AI_BUILDER_DIR . 'includes/class-vmo-phase-output-manager.php';
$elite_prompts_file = VMO_AI_BUILDER_DIR . 'includes/class-vmo-elite-consultation-prompts.php';

if (file_exists($phase_output_file)) {
    require_once $phase_output_file;
} else {
    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log('VMO: Missing file - class-vmo-phase-output-manager.php');
    }
}

if (file_exists($elite_prompts_file)) {
    require_once $elite_prompts_file;
} else {
    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log('VMO: Missing file - class-vmo-elite-consultation-prompts.php');
    }
}

// === Vision Roadmap Engine (AI-driven milestones, daily activities, daily reports, progress) ===
$roadmap_engine_file = VMO_AI_BUILDER_DIR . 'includes/class-vmo-vision-roadmap-engine.php';
if (file_exists($roadmap_engine_file)) {
    require_once $roadmap_engine_file;
} else {
    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log('VMO: Missing file - class-vmo-vision-roadmap-engine.php');
    }
}

// === Membership & Ticketing Systems (Dec 2025) ===
// ✅ Safe loading - prevent crashes if files don't exist on server
$membership_file = VMO_AI_BUILDER_DIR . 'includes/class-vmo-membership-system.php';
$ticketing_file = VMO_AI_BUILDER_DIR . 'includes/class-vmo-ticketing-system.php';

global $vmo_membership_system, $vmo_ticketing_system;
$vmo_membership_system = null;
$vmo_ticketing_system = null;

if (file_exists($membership_file)) {
    require_once $membership_file;
    $vmo_membership_system = new VMO_Membership_System();
} else {
    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log('VMO: Missing file - class-vmo-membership-system.php');
    }
}

if (file_exists($ticketing_file)) {
    require_once $ticketing_file;
    $vmo_ticketing_system = new VMO_Ticketing_System();
} else {
    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log('VMO: Missing file - class-vmo-ticketing-system.php');
    }
}

// ✅ CRITICAL FIX: Force cache busting for Nov 1 2025 fixes
// This ensures browsers and WordPress load the NEW fixed files
if (!defined('VMO_CACHE_BUST_VERSION')) {
    define('VMO_CACHE_BUST_VERSION', '9.0.5-TTS-PLAY-DEBUG-NOV01-2025-V2');
}

// Safe filemtime helper to prevent warnings on missing files
if (!function_exists('vmo_safe_filemtime')) {
    function vmo_safe_filemtime($file_path, $fallback = null) {
        if (file_exists($file_path)) {
            return filemtime($file_path);
        }
        return $fallback ?: (defined('VMO_CACHE_BUST_VERSION') ? VMO_CACHE_BUST_VERSION : time());
    }
}

// === Unified nonce verification helper (accepts both admin and builder nonces) ===
if (!function_exists('vmo_verify_admin_security')) {
    function vmo_verify_admin_security(): bool {
        $nonce = sanitize_text_field($_POST['security'] ?? $_POST['nonce'] ?? '');
        if (!$nonce) return false;
        // Accept either builder or admin nonces for backward compatibility
        if (wp_verify_nonce($nonce, 'vmo_admin_nonce')) return true;
        if (wp_verify_nonce($nonce, 'vmo_builder_nonce')) return true;
        // Legacy nonce used by some older dashboard endpoints
        if (wp_verify_nonce($nonce, 'vmo_nonce')) return true;
        return false;
    }
}

if (!defined('VMO_SECRET_PLACEHOLDER')) {
    define('VMO_SECRET_PLACEHOLDER', '__VMO_SECRET_PLACEHOLDER__');
}

if (!function_exists('vmo_encrypt_secret_value')) {
    if (!function_exists('vmo_wp_salt_compat')) {
        function vmo_wp_salt_compat($scheme = 'auth') {
            if (!function_exists('wp_salt') && defined('ABSPATH') && defined('WPINC')) {
                $pluggable = rtrim(ABSPATH, '/\\') . '/' . trim(WPINC, '/\\') . '/pluggable.php';
                if (file_exists($pluggable)) {
                    require_once $pluggable;
                }
            }
            if (function_exists('wp_salt')) {
                return wp_salt($scheme);
            }
            // Fallback: stable material from wp-config constants (available before plugins load).
            $parts = array(
                defined('AUTH_KEY') ? AUTH_KEY : '',
                defined('SECURE_AUTH_KEY') ? SECURE_AUTH_KEY : '',
                defined('LOGGED_IN_KEY') ? LOGGED_IN_KEY : '',
                defined('NONCE_KEY') ? NONCE_KEY : '',
                defined('AUTH_SALT') ? AUTH_SALT : '',
                defined('SECURE_AUTH_SALT') ? SECURE_AUTH_SALT : '',
                defined('LOGGED_IN_SALT') ? LOGGED_IN_SALT : '',
                defined('NONCE_SALT') ? NONCE_SALT : '',
                (string) $scheme,
            );
            $material = implode('|', $parts);
            return $material !== '' ? $material : 'vmo-fallback-salt|' . (string) $scheme;
        }
    }

    function vmo_encrypt_secret_value($plaintext) {
        $plaintext = is_string($plaintext) ? trim($plaintext) : '';
        if ($plaintext === '' || strpos($plaintext, 'vmo_encrypted:') === 0) {
            return $plaintext;
        }
        if (!function_exists('openssl_encrypt')) {
            return $plaintext;
        }
        $method = 'AES-256-CBC';
        $key = hash('sha256', vmo_wp_salt_compat('auth'), true);
        $iv_length = openssl_cipher_iv_length($method);
        $iv = ($iv_length > 0) ? (function_exists('random_bytes') ? random_bytes($iv_length) : openssl_random_pseudo_bytes($iv_length)) : '';
        $cipher = openssl_encrypt($plaintext, $method, $key, OPENSSL_RAW_DATA, $iv);
        if ($cipher === false) {
            return $plaintext;
        }
        return 'vmo_encrypted:' . base64_encode($iv . $cipher); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- encryption/decryption
    }
}

if (!function_exists('vmo_decrypt_secret_value')) {
    function vmo_decrypt_secret_value($value) {
        $value = is_string($value) ? $value : '';
        if ($value === '' || strpos($value, 'vmo_encrypted:') !== 0 || !function_exists('openssl_decrypt')) {
            return $value;
        }
        $payload = base64_decode(substr($value, strlen('vmo_encrypted:'))); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- encryption/decryption
        if ($payload === false) {
            return '';
        }
        $method = 'AES-256-CBC';
        $iv_length = openssl_cipher_iv_length($method);
        $iv = substr($payload, 0, $iv_length);
        $cipher = substr($payload, $iv_length);
        if ($iv === false || $cipher === false) {
            return '';
        }
        $key = hash('sha256', vmo_wp_salt_compat('auth'), true);
        $plaintext = openssl_decrypt($cipher, $method, $key, OPENSSL_RAW_DATA, $iv);
        return $plaintext !== false ? $plaintext : '';
    }
}

if (!function_exists('vmo_register_secret_option_filters')) {
    function vmo_register_secret_option_filters() {
        // Encryption disabled — decrypt filters caused timing issues with OPcache
        // on production servers (encrypted value used as raw API credential).
        // Keep array empty; use wp-config.php constants for secrets if needed.
        $secret_options = array();
        foreach ($secret_options as $option) {
            add_filter("pre_update_option_{$option}", function ($value, $old_value) use ($option) {
                $value = is_string($value) ? trim($value) : '';
                if ($value === '') {
                    return '';
                }
                if ($value === VMO_SECRET_PLACEHOLDER) {
                    return $old_value;
                }
                return vmo_encrypt_secret_value($value);
            }, 10, 2);

            add_filter("option_{$option}", function ($value) use ($option) {
                return vmo_decrypt_secret_value($value);
            });
        }

        // ── One-time migration: decrypt any values that were previously encrypted ──
        $migrate_options = array(
            'vmo_twilio_account_sid',
            'vmo_twilio_auth_token',
            'vmo_twilio_api_key_sid',
            'vmo_twilio_api_key_secret',
            'vmo_openai_api_key',
        );
        foreach ($migrate_options as $opt) {
            $raw = get_option($opt, '');
            if (is_string($raw) && strpos($raw, 'vmo_encrypted:') === 0) {
                $plain = vmo_decrypt_secret_value($raw);
                // If decrypt succeeded (non-empty), save plaintext back.
                // If it failed (empty string), keep encrypted value and log.
                if ($plain !== '' && strpos($plain, 'vmo_encrypted:') !== 0) {
                    update_option($opt, $plain);
                    if (defined('WP_DEBUG') && WP_DEBUG) error_log("[VMO] Migrated {$opt} from encrypted back to plaintext");
                } else {
                    if (defined('WP_DEBUG') && WP_DEBUG) error_log("[VMO] WARNING: Could not decrypt {$opt} — user must re-enter on settings page");
                    // Wipe the broken encrypted value so the settings page shows empty
                    // and the user is clearly prompted to re-enter.
                    delete_option($opt);
                }
            }
        }

        // OpenAI key fallback: if vmo_openai_api_key is empty, try vmo_manager_openai_api_key
        add_filter('option_vmo_openai_api_key', function ($value) {
            if (empty($value)) {
                $manager_key = get_option('vmo_manager_openai_api_key', '');
                if (!empty($manager_key)) {
                    return $manager_key;
                }
            }
            return $value;
        }, 20);
    }
    vmo_register_secret_option_filters();
}

if (!function_exists('vmo_get_greeting_display_name')) {
    function vmo_get_greeting_display_name(): string {
        if (function_exists('wp_get_current_user')) {
            $user = wp_get_current_user();
            if ($user && $user->exists()) {
                $name = trim((string) $user->display_name);
                if ($name !== '') {
                    return $name;
                }
            }
        }
        return __('there', 'vmo');
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// VMO CENTRALIZED API REQUEST HANDLER
// All external API calls (OpenAI, Twilio, Stripe) should use this function.
// In CLIENT MODE, requests are proxied through the master server.
// In MASTER MODE, requests go directly to the API providers.
// ═══════════════════════════════════════════════════════════════════════════════
if (!function_exists('vmo_api_request')) {
    /**
     * Make an API request - routes through master proxy in client mode
     * 
     * @param string $service   'openai', 'twilio', or 'stripe'
     * @param string $endpoint  API endpoint path (e.g., 'v1/chat/completions')
     * @param array  $args      wp_remote_post arguments (headers, body, timeout, etc.)
     * @param string $method    HTTP method (POST, GET, etc.) - default POST
     * @return array|WP_Error   Response array or WP_Error
     */
    function vmo_api_request($service, $endpoint, $args = [], $method = 'POST') {
        // Check if user has an active subscription (required for client mode)
        if (defined('VMO_CLIENT_MODE') && VMO_CLIENT_MODE === true) {
            // Verify subscription status before making any API calls
            $user_id = get_current_user_id();
            $plan = get_user_meta($user_id, 'vmo_subscription_status', true);
            $valid_plans = ['tester', 'starter', 'pro', 'elite', 'enterprise', 'premium', 'business'];
            
            if (empty($plan) || !in_array(strtolower($plan), $valid_plans)) {
                return new WP_Error('vmo_subscription_required', 
                    'Active subscription required. Please select a plan to use VMO features.',
                    ['status' => 403]
                );
            }
            
            // Route through master proxy (configurable via constant or wp-admin setting)
            $proxy_url = defined('VMO_MASTER_PROXY_URL') 
                ? VMO_MASTER_PROXY_URL 
                : get_option('vmo_master_proxy_url', 'https://getvmo.com/wp-json/vmo-builder/v1/proxy');
            
            $target_url = trailingslashit($proxy_url) . $service . '/' . ltrim($endpoint, '/');
            
            // Add installation ID and user license info for tracking/validation
            $installation_id = get_option('vmo_installation_id', '');
            $license_key = get_option('vmo_license_key', '');
            
            // Merge custom headers
            $args['headers'] = array_merge(
                $args['headers'] ?? [],
                [
                    'X-VMO-Installation' => $installation_id,
                    'X-VMO-License' => $license_key,
                    'X-VMO-User' => $user_id,
                    'X-VMO-Plan' => $plan,
                ]
            );
            
            $args['method'] = $method;
            
            if (defined('WP_DEBUG') && WP_DEBUG) error_log("VMO Client Mode: Routing $service/$endpoint through master proxy");
            
            return wp_remote_request($target_url, $args);
        }
        
        // MASTER MODE: Direct API calls
        $api_urls = [
            'openai' => 'https://api.openai.com/',
            'twilio' => 'https://api.twilio.com/',
            'stripe' => 'https://api.stripe.com/',
        ];
        
        if (!isset($api_urls[$service])) {
            return new WP_Error('vmo_invalid_service', 'Unknown service: ' . $service);
        }
        
        $target_url = $api_urls[$service] . ltrim($endpoint, '/');
        $args['method'] = $method;
        
        return wp_remote_request($target_url, $args);
    }
}

/**
 * Helper to make OpenAI chat completion requests
 */
if (!function_exists('vmo_openai_chat')) {
    function vmo_openai_chat($messages, $model = 'gpt-5.2-2025-12-11', $options = []) {
        // In client mode, don't add API key (master will add it)
        $headers = ['Content-Type' => 'application/json'];
        
        if (!defined('VMO_CLIENT_MODE') || VMO_CLIENT_MODE !== true) {
            $api_key = get_option('vmo_openai_api_key');
            if (empty($api_key)) {
                return new WP_Error('vmo_no_api_key', 'OpenAI API key not configured');
            }
            $headers['Authorization'] = 'Bearer ' . $api_key;
        }
        
        $body = array_merge([
            'model' => $model,
            'messages' => $messages,
            'temperature' => $options['temperature'] ?? 0.7,
        ], $options);
        
        // Handle token parameters based on model
        if (function_exists('vmo_get_token_param')) {
            $max_tokens = $options['max_tokens'] ?? 4096;
            $body = array_merge($body, vmo_get_token_param($model, $max_tokens));
        }
        
        $args = [
            'headers' => $headers,
            'body' => json_encode($body),
            'timeout' => $options['timeout'] ?? 120,
        ];
        
        return vmo_api_request('openai', 'v1/chat/completions', $args, 'POST');
    }
}

/**
 * Helper to make OpenAI image generation requests
 */
if (!function_exists('vmo_openai_image')) {
    function vmo_openai_image($prompt, $size = '1024x1024', $options = []) {
        $headers = ['Content-Type' => 'application/json'];
        
        if (!defined('VMO_CLIENT_MODE') || VMO_CLIENT_MODE !== true) {
            $api_key = get_option('vmo_openai_api_key');
            if (empty($api_key)) {
                return new WP_Error('vmo_no_api_key', 'OpenAI API key not configured');
            }
            $headers['Authorization'] = 'Bearer ' . $api_key;
        }
        
        $body = [
            'model' => $options['model'] ?? 'gpt-image-1',
            'prompt' => $prompt,
            'n' => $options['n'] ?? 1,
            'size' => $size,
            'response_format' => $options['response_format'] ?? 'url',
        ];
        
        $args = [
            'headers' => $headers,
            'body' => json_encode($body),
            'timeout' => $options['timeout'] ?? 90,
        ];
        
        return vmo_api_request('openai', 'v1/images/generations', $args, 'POST');
    }
}

/**
 * Helper to make OpenAI TTS (text-to-speech) requests
 */
if (!function_exists('vmo_openai_tts')) {
    function vmo_openai_tts($text, $voice = 'alloy', $options = []) {
        $headers = ['Content-Type' => 'application/json'];
        
        if (!defined('VMO_CLIENT_MODE') || VMO_CLIENT_MODE !== true) {
            $api_key = get_option('vmo_openai_api_key');
            if (empty($api_key)) {
                return new WP_Error('vmo_no_api_key', 'OpenAI API key not configured');
            }
            $headers['Authorization'] = 'Bearer ' . $api_key;
        }
        
        $body = [
            'model' => $options['model'] ?? 'tts-1-hd',
            'input' => $text,
            'voice' => $voice,
            'response_format' => $options['format'] ?? 'mp3',
        ];
        
        $args = [
            'headers' => $headers,
            'body' => json_encode($body),
            'timeout' => $options['timeout'] ?? 60,
        ];
        
        return vmo_api_request('openai', 'v1/audio/speech', $args, 'POST');
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// VMO SMART API WRAPPER
// Intercepts wp_remote_post calls to external APIs and routes through proxy in client mode
// This is a compatibility layer for legacy code that makes direct API calls
// ═══════════════════════════════════════════════════════════════════════════════
if (!function_exists('vmo_smart_remote_post')) {
    /**
     * Smart wrapper for wp_remote_post that handles client mode proxying
     * Use this instead of wp_remote_post for API calls to OpenAI, Twilio, Stripe
     * 
     * @param string $url  The target URL
     * @param array  $args wp_remote_post arguments
     * @return array|WP_Error
     */
    function vmo_smart_remote_post($url, $args = []) {
        $url = (string) ($url ?? '');
        // Check if this is a call to a supported external API
        $api_patterns = [
            'openai' => 'api.openai.com',
            'twilio' => 'api.twilio.com',
            'stripe' => 'api.stripe.com',
        ];
        
        $detected_service = null;
        $endpoint = '';
        
        foreach ($api_patterns as $service => $domain) {
            if (strpos($url, $domain) !== false) {
                $detected_service = $service;
                // Extract endpoint from URL
                $endpoint = preg_replace('#^https?://' . preg_quote($domain, '#') . '/?#', '', $url);
                break;
            }
        }
        
        // If not an API call we handle, use regular wp_remote_post
        if (!$detected_service) {
            return wp_remote_post($url, $args);
        }
        
        // In CLIENT MODE, route through proxy
        if (defined('VMO_CLIENT_MODE') && VMO_CLIENT_MODE === true) {
            // Check subscription before making API call
            $user_id = get_current_user_id();
            $plan = get_user_meta($user_id, 'vmo_subscription_status', true);
            $valid_plans = ['tester', 'starter', 'pro', 'elite', 'enterprise', 'premium', 'business'];
            
            if (empty($plan) || !in_array(strtolower($plan), $valid_plans)) {
                return new WP_Error('vmo_subscription_required', 
                    'Active subscription required. Please select a plan to use VMO features.',
                    ['status' => 403]
                );
            }
            
            // Build proxy URL (configurable via constant or wp-admin setting)
            $proxy_url = defined('VMO_MASTER_PROXY_URL') 
                ? VMO_MASTER_PROXY_URL 
                : get_option('vmo_master_proxy_url', 'https://getvmo.com/wp-json/vmo-builder/v1/proxy');
            
            $target_url = trailingslashit($proxy_url) . $detected_service . '/' . ltrim($endpoint, '/');
            
            // Add VMO headers for authentication/tracking
            $installation_id = get_option('vmo_installation_id', '');
            $license_key = get_option('vmo_license_key', '');
            
            $args['headers'] = array_merge(
                $args['headers'] ?? [],
                [
                    'X-VMO-Installation' => $installation_id,
                    'X-VMO-License' => $license_key,
                    'X-VMO-User' => $user_id,
                    'X-VMO-Plan' => $plan,
                ]
            );
            
            // Remove any existing Authorization header (master will add it)
            if (isset($args['headers']['Authorization'])) {
                unset($args['headers']['Authorization']);
            }
            
            if (defined('WP_DEBUG') && WP_DEBUG) error_log("VMO Smart Proxy: Routing $detected_service/$endpoint through master proxy");
            
            return wp_remote_post($target_url, $args);
        }
        
        // MASTER MODE: Make direct call
        return wp_remote_post($url, $args);
    }
}

if (!function_exists('vmo_get_consultation_greeting_text')) {
    function vmo_get_consultation_greeting_text(): string {
        $default = <<<EOT
Welcome, {{user_name}}. I'm Jerreme, and I'll lead your VMO Business Wizard consultation.

Jarvis and the rest of the VMO AI team are already standing by across design, content, SEO, media, documents, automation, and launch operations. My job is to turn your goals into a clear build plan we can execute inside your website builder without overwhelm.

We'll keep this focused and easy. First, tell me this: are we building a brand new business from scratch, or are we improving an existing business?

You can type, use speech-to-text, tap the mic, or upload documents if that is faster. I'll guide you one calm question at a time.
EOT;
        $display_name = vmo_get_greeting_display_name();
        $replacements = ['{{user_name}}', '{{ user_name }}', '[USER_NAME]', '[username]', '[UserName]'];
        $personalized = str_replace($replacements, $display_name, $default);
        return apply_filters('vmo_consultation_greeting_text', $personalized, $display_name);
    }
}

if (!function_exists('vmo_filter_greeting_audio_text')) {
    function vmo_filter_greeting_audio_text($audio_text, $voice) {
        $text = is_string($audio_text) ? $audio_text : '';
        if ($text === '') {
            return $audio_text;
        }
        $text = str_replace(
            ['{{user_name}}', '{{ user_name }}', '[USER_NAME]', '[username]', '[UserName]'],
            vmo_get_greeting_display_name(),
            $text
        );
        return $text;
    }
    add_filter('vmo_consultation_greeting_audio_text', 'vmo_filter_greeting_audio_text', 10, 2);
}

if (!function_exists('vmo_force_greeting_voice')) {
    function vmo_force_greeting_voice($voice) {
        return 'onyx';
    }
    add_filter('vmo_consultation_greeting_voice', 'vmo_force_greeting_voice', 1000, 1);
}

// Tiny echo endpoint for debugging admin-ajax connectivity and nonce issues
add_action('wp_ajax_vmo_debug_echo', function () {
    $payload = [
        'method' => $_SERVER['REQUEST_METHOD'] ?? 'UNKNOWN',
        'received' => [
            'action' => sanitize_key($_POST['action'] ?? ''),
            'security' => isset($_POST['security']) ? 'present' : 'missing',
            'nonce' => isset($_POST['nonce']) ? 'present' : 'missing',
        ],
        'nonce_valid' => vmo_verify_admin_security(),
        'user' => get_current_user_id(),
        'ts' => current_time('mysql'),
    ];
    if (!$payload['nonce_valid']) {
        $payload['status'] = 'invalid_nonce';
        wp_send_json_error($payload);
    }
    wp_send_json_success($payload);
});

// ✅ QuickLaunch API Connection Test — admin-only AJAX endpoint
// Tests Vultr, Cloudflare, MXroute, and Stripe API keys from the Settings page
add_action('wp_ajax_vmo_test_quicklaunch_apis', function() {
    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => 'Unauthorized']);
    }
    check_ajax_referer('vmo_test_ql_apis', 'security');

    $results = [
        'vultr'      => ['valid' => false, 'message' => 'Not configured'],
        'cloudflare' => ['valid' => false, 'message' => 'Not configured'],
        'mxroute'    => ['valid' => false, 'message' => 'Not configured'],
        'stripe'     => ['valid' => false, 'message' => 'Not configured'],
        'namecheap'  => ['valid' => false, 'message' => 'Not configured'],
    ];

    // ── Vultr ──
    $vultr_key = get_option('vmo_vultr_api_key', '');
    if (!empty($vultr_key)) {
        $resp = wp_remote_get('https://api.vultr.com/v2/account', [
            'timeout' => 15,
            'headers' => ['Authorization' => 'Bearer ' . $vultr_key],
        ]);
        if (!is_wp_error($resp)) {
            $body = json_decode(wp_remote_retrieve_body($resp), true);
            $code = wp_remote_retrieve_response_code($resp);
            if ($code < 400 && isset($body['account'])) {
                $balance = abs($body['account']['balance'] ?? 0);
                $pending = $body['account']['pending_charges'] ?? 0;
                $results['vultr'] = ['valid' => true, 'message' => 'Connected. Balance: $' . number_format($balance, 2) . ' | Pending: $' . number_format($pending, 2)];
            } else {
                $results['vultr'] = ['valid' => false, 'message' => 'Invalid API key (HTTP ' . $code . ')'];
            }
        } else {
            $results['vultr'] = ['valid' => false, 'message' => 'Connection failed: ' . $resp->get_error_message()];
        }
    }

    // ── Cloudflare ──
    $cf_token = get_option('vmo_cloudflare_api_token', '');
    if (!empty($cf_token)) {
        $resp = wp_remote_get('https://api.cloudflare.com/client/v4/user/tokens/verify', [
            'timeout' => 15,
            'headers' => ['Authorization' => 'Bearer ' . $cf_token, 'Content-Type' => 'application/json'],
        ]);
        if (!is_wp_error($resp)) {
            $body = json_decode(wp_remote_retrieve_body($resp), true);
            $status = $body['result']['status'] ?? '';
            $results['cloudflare'] = ($status === 'active')
                ? ['valid' => true, 'message' => 'Token active']
                : ['valid' => false, 'message' => 'Token invalid (status: ' . ($status ?: 'unknown') . ')'];
        } else {
            $results['cloudflare'] = ['valid' => false, 'message' => 'Connection failed: ' . $resp->get_error_message()];
        }
    }

    // ── MXroute (REST API at api.mxroute.com) ──
    $mx_server  = get_option('vmo_mxroute_server', '');
    $mx_user    = get_option('vmo_mxroute_username', '');
    $mx_api_key = get_option('vmo_mxroute_api_key', '');
    if (!empty($mx_server) && !empty($mx_user) && !empty($mx_api_key)) {
        $resp = wp_remote_get('https://api.mxroute.com/domains', [
            'timeout' => 15,
            'headers' => [
                'X-Server'   => $mx_server,
                'X-Username' => $mx_user,
                'X-API-Key'  => $mx_api_key,
                'Accept'     => 'application/json',
            ],
        ]);
        if (!is_wp_error($resp) && wp_remote_retrieve_response_code($resp) < 400) {
            $body = json_decode(wp_remote_retrieve_body($resp), true);
            $domain_count = is_array($body['data'] ?? null) ? count($body['data']) : 0;
            $results['mxroute'] = ['valid' => true, 'message' => 'Connected to ' . $mx_server . ' (' . $domain_count . ' domains)'];
        } else {
            $code = is_wp_error($resp) ? $resp->get_error_message() : 'HTTP ' . wp_remote_retrieve_response_code($resp);
            $body = !is_wp_error($resp) ? json_decode(wp_remote_retrieve_body($resp), true) : null;
            $detail = $body['error']['message'] ?? $code;
            $results['mxroute'] = ['valid' => false, 'message' => 'Connection failed: ' . $detail];
        }
    }

    // ── Stripe ──
    $stripe_key = get_option('vmo_stripe_secret_key', '');
    if (!empty($stripe_key)) {
        $resp = wp_remote_get('https://api.stripe.com/v1/balance', [
            'timeout' => 15,
            'headers' => ['Authorization' => 'Bearer ' . $stripe_key],
        ]);
        if (!is_wp_error($resp)) {
            $body = json_decode(wp_remote_retrieve_body($resp), true);
            if (!empty($body['available'])) {
                $results['stripe'] = ['valid' => true, 'message' => 'Connected'];
            } else {
                $results['stripe'] = ['valid' => false, 'message' => 'Invalid key'];
            }
        } else {
            $results['stripe'] = ['valid' => false, 'message' => 'Connection failed: ' . $resp->get_error_message()];
        }
    }

    // ── Namecheap ──
    $namecheap_api_user = get_option('vmo_namecheap_api_user', '');
    $namecheap_username = get_option('vmo_namecheap_username', '');
    $namecheap_api_key = get_option('vmo_namecheap_api_key', '');
    $namecheap_client_ip = get_option('vmo_namecheap_client_ip', '');
    $contact_complete = !empty(get_option('vmo_namecheap_contact_first_name', ''))
        && !empty(get_option('vmo_namecheap_contact_last_name', ''))
        && !empty(get_option('vmo_namecheap_contact_address1', ''))
        && !empty(get_option('vmo_namecheap_contact_city', ''))
        && !empty(get_option('vmo_namecheap_contact_state', ''))
        && !empty(get_option('vmo_namecheap_contact_postal_code', ''))
        && !empty(get_option('vmo_namecheap_contact_country', ''))
        && !empty(get_option('vmo_namecheap_contact_phone', ''))
        && !empty(get_option('vmo_namecheap_contact_email', ''));

    if (!empty($namecheap_api_user) && !empty($namecheap_username) && !empty($namecheap_api_key) && !empty($namecheap_client_ip)) {
        $endpoint = get_option('vmo_namecheap_use_sandbox', '0') === '1'
            ? 'https://api.sandbox.namecheap.com/xml.response'
            : 'https://api.namecheap.com/xml.response';

        $resp = wp_remote_get(add_query_arg([
            'ApiUser' => $namecheap_api_user,
            'ApiKey' => $namecheap_api_key,
            'UserName' => $namecheap_username,
            'ClientIp' => $namecheap_client_ip,
            'Command' => 'namecheap.domains.getTldList',
        ], $endpoint), [
            'timeout' => 20,
        ]);

        if (!is_wp_error($resp)) {
            $body = wp_remote_retrieve_body($resp);
            $xml = @simplexml_load_string($body);
            $status = $xml ? strtoupper((string) ($xml['Status'] ?? '')) : '';
            $tlds = $xml ? $xml->xpath('//*[local-name()="Tld"]') : [];

            if ($status === 'OK') {
                $results['namecheap'] = [
                    'valid' => $contact_complete,
                    'message' => $contact_complete
                        ? 'Connected. ' . count($tlds) . ' TLDs available'
                        : 'API connected, but contact profile is incomplete',
                ];
            } else {
                $errors = [];
                if ($xml) {
                    foreach ($xml->xpath('//*[local-name()="Error"]') as $error) {
                        $errors[] = trim((string) $error);
                    }
                }
                $results['namecheap'] = ['valid' => false, 'message' => !empty($errors) ? implode(' | ', $errors) : 'Invalid Namecheap credentials'];
            }
        } else {
            $results['namecheap'] = ['valid' => false, 'message' => 'Connection failed: ' . $resp->get_error_message()];
        }
    }

    $all_valid = $results['vultr']['valid'] && $results['cloudflare']['valid'] && $results['mxroute']['valid'] && $results['stripe']['valid'] && $results['namecheap']['valid'];
    wp_send_json_success(['all_valid' => $all_valid, 'services' => $results]);
});

// ✅ GPT-5.2 Test Endpoint - Test OpenAI model availability
// Call from browser console: jQuery.post(vmoAjax.url, {action:'vmo_test_gpt52', security:vmoAjax.nonce}, console.log)
add_action('wp_ajax_vmo_test_gpt52', function() {
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid security token']);
    }
    
    $api_key = get_option('vmo_openai_api_key');
    if (empty($api_key)) {
        wp_send_json_error(['message' => 'OpenAI API key not configured']);
    }
    
    $models_to_test = ['gpt-5.2-2025-12-11', 'gpt-4o', 'gpt-4o-mini'];
    $results = [];
    
    foreach ($models_to_test as $model) {
        // Use correct token parameter based on model
        $token_param = vmo_get_token_param($model, 50);
        
        $data = array_merge([
            'model' => $model,
            'messages' => [
                ['role' => 'user', 'content' => 'Say exactly: "Model ' . $model . ' is working!"']
            ],
            'temperature' => 0.3
        ], $token_param);
        
        $start = microtime(true);
        
        $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
            'headers' => [
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type' => 'application/json',
            ],
            'body' => json_encode($data),
            'timeout' => 60
        ]);
        
        $elapsed = round(microtime(true) - $start, 2);
        
        if (is_wp_error($response)) {
            $results[$model] = [
                'status' => 'error',
                'message' => $response->get_error_message(),
                'time' => $elapsed
            ];
            continue;
        }
        
        $http_code = wp_remote_retrieve_response_code($response);
        $body = json_decode(wp_remote_retrieve_body($response), true);
        
        if ($http_code === 200 && isset($body['choices'][0]['message']['content'])) {
            $results[$model] = [
                'status' => 'success',
                'http_code' => $http_code,
                'response' => trim($body['choices'][0]['message']['content']),
                'tokens' => $body['usage']['total_tokens'] ?? 'N/A',
                'time' => $elapsed
            ];
        } else {
            $results[$model] = [
                'status' => 'failed',
                'http_code' => $http_code,
                'error_type' => $body['error']['type'] ?? 'unknown',
                'error_message' => $body['error']['message'] ?? 'Unknown error',
                'time' => $elapsed
            ];
        }
    }
    
    wp_send_json_success([
        'message' => 'GPT Model Test Complete',
        'vmo_openai_model' => defined('VMO_OPENAI_MODEL') ? VMO_OPENAI_MODEL : 'not defined',
        'results' => $results
    ]);
});

// ✅ SIMPLE GPT-5.2 Test (no nonce required - admin only) - for quick browser testing
// Just visit: /wp-admin/admin-ajax.php?action=vmo_simple_gpt_test
add_action('wp_ajax_vmo_simple_gpt_test', function() {
    if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Admin only'], 403); }
    // Only allow for logged-in admins
    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => 'Admin access required']);
    }
    
    $api_key = get_option('vmo_openai_api_key');
    if (empty($api_key)) {
        wp_send_json_error(['message' => 'OpenAI API key not configured', 'key_exists' => false]);
    }
    
    $results = [];
    $models = ['gpt-5.2', 'gpt-4o'];
    
    foreach ($models as $model) {
        $token_param = vmo_get_token_param($model, 30);
        
        $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
            'headers' => [
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type' => 'application/json',
            ],
            'body' => json_encode(array_merge([
                'model' => $model,
                'messages' => [['role' => 'user', 'content' => 'Say "OK"']],
                'temperature' => 0
            ], $token_param)),
            'timeout' => 30
        ]);
        
        if (is_wp_error($response)) {
            $results[$model] = '❌ WP Error: ' . $response->get_error_message();
        } else {
            $code = wp_remote_retrieve_response_code($response);
            $body = json_decode(wp_remote_retrieve_body($response), true);
            
            if ($code === 200) {
                $reply = $body['choices'][0]['message']['content'] ?? 'no content';
                $results[$model] = "✅ Works! Response: {$reply}";
            } else {
                $err = $body['error']['message'] ?? 'Unknown error';
                $results[$model] = "❌ HTTP {$code}: {$err}";
            }
        }
    }
    
    wp_send_json_success([
        'VMO_OPENAI_MODEL' => defined('VMO_OPENAI_MODEL') ? VMO_OPENAI_MODEL : 'undefined',
        'api_key_ending' => '...' . substr($api_key, -6),
        'test_results' => $results
    ]);
});

// ✅ DEEP RESEARCH DIAGNOSTIC - Test step-by-step what fails
// Visit: /wp-admin/admin-ajax.php?action=vmo_test_deep_research
add_action('wp_ajax_vmo_test_deep_research', function() {
    if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Admin only'], 403); }
    // Only allow for logged-in admins
    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => 'Admin access required']);
    }
    
    $diagnostics = ['started' => true];
    
    try {
        // Step 1: Check if class exists
        $diagnostics['step1_class_check'] = class_exists('VMO_Deep_Research') ? '✅ Class exists' : '❌ Class NOT found';
        
        if (!class_exists('VMO_Deep_Research')) {
            // Try loading it
            $file_path = VMO_AI_BUILDER_DIR . 'includes/class-vmo-deep-research.php';
            $diagnostics['class_file_exists'] = file_exists($file_path) ? '✅ File exists' : '❌ File missing';
            if (file_exists($file_path)) {
                require_once $file_path;
                $diagnostics['class_loaded_manually'] = class_exists('VMO_Deep_Research') ? '✅ Now exists' : '❌ Still missing';
            }
        }
        
        if (!class_exists('VMO_Deep_Research')) {
            wp_send_json_error($diagnostics);
        }
        
        // Step 2: Try instantiating
        $diagnostics['step2_instantiate'] = 'Attempting...';
        $research = new VMO_Deep_Research();
        $diagnostics['step2_instantiate'] = '✅ Instance created';
        
        // Step 3: Check API key
        $api_key = get_option('vmo_openai_api_key');
        $diagnostics['step3_api_key'] = !empty($api_key) ? '✅ API key exists (ends: ...' . substr($api_key, -6) . ')' : '❌ API key missing';
        
        // Step 4: Try a minimal test call
        $diagnostics['step4_test_call'] = 'Attempting minimal research call...';
        
        $test_data = [
            'business_name' => 'Test Business',
            'business_type' => 'service',
            'industry' => 'consulting',
            'location' => 'New York',
            'target_market' => 'small businesses'
        ];
        
        // Call chunk 1 only (4 API calls)
        $result = $research->conduct_deep_research($test_data, 1);
        
        $diagnostics['step4_test_call'] = '✅ Research chunk 1 completed';
        $diagnostics['step4_result_keys'] = is_array($result) ? array_keys($result) : 'not array';
        $diagnostics['step4_is_complete'] = $result['is_complete'] ?? 'unknown';
        $diagnostics['step4_current_chunk'] = $result['current_chunk'] ?? 'unknown';
        
        wp_send_json_success($diagnostics);
        
    } catch (Throwable $e) {
        $diagnostics['fatal_error'] = $e->getMessage();
        $diagnostics['error_file'] = $e->getFile() . ':' . $e->getLine();
        $diagnostics['error_trace'] = substr($e->getTraceAsString(), 0, 1000);
        wp_send_json_error($diagnostics);
    }
});

// Get user status for auto-greeting system
add_action('wp_ajax_vmo_get_user_status_for_greeting', function() {
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'));
        return;
    }
    
    $user_id = get_current_user_id();
    $user = get_userdata($user_id);
    $username = $user ? $user->display_name : 'there';
    
    // Check if user has completed consultation
    global $wpdb;
    $consultation_table = $wpdb->prefix . 'vmo_consultations';
    
    $consultation = $wpdb->get_row(
        $wpdb->prepare(
            "SELECT * FROM {$consultation_table} WHERE user_id = %d AND status = 'completed' ORDER BY updated_at DESC LIMIT 1",
            $user_id
        )
    );
    
    // Check if user has a published website
    $has_published_site = false;
    $website_content = get_user_meta($user_id, 'vmo_published_website', true);
    if (!empty($website_content)) {
        $has_published_site = true;
    }
    
    // Check for new KPIs/notifications
    $has_new_data = false;
    $new_data_summary = '';
    
    // Check for new leads/contacts
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    $new_contacts = 0;
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$contacts_table}'") == $contacts_table) {
        $new_contacts = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$contacts_table} WHERE user_id = %d AND created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)",
                $user_id
            )
        );
    }
    
    // VOICE SYSTEM REMOVED: Check for new phone calls disabled
    // $phone_calls_table = $wpdb->prefix . 'vmo_phone_calls';
    $new_calls = 0;
    // if ($wpdb->get_var("SHOW TABLES LIKE '{$phone_calls_table}'") == $phone_calls_table) {
    //     $new_calls = $wpdb->get_var(
    //         $wpdb->prepare(
    //             "SELECT COUNT(*) FROM {$phone_calls_table} WHERE user_id = %d AND created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)",
    //             $user_id
    //         )
    //     );
    // }
    
    if ($new_contacts > 0 || $new_calls > 0) {
        $has_new_data = true;
        $parts = array();
        if ($new_contacts > 0) {
            $parts[] = "{$new_contacts} new contact" . ($new_contacts > 1 ? 's' : '');
        }
        if ($new_calls > 0) {
            $parts[] = "{$new_calls} new phone call" . ($new_calls > 1 ? 's' : '');
        }
        $new_data_summary = "You have " . implode(' and ', $parts) . " since yesterday.";
    }
    
    // Determine scenario
    $scenario = 'first_time';
    
    if ($consultation && $has_published_site) {
        $scenario = 'active_business';
    } elseif ($consultation || get_user_meta($user_id, 'vmo_consultation_started', true)) {
        $scenario = 'incomplete_consultation';
    }
    
    // Check if this is truly first time
    if (get_user_meta($user_id, 'vmo_first_greeting_done', true)) {
        if ($scenario === 'first_time') {
            $scenario = 'incomplete_consultation';
        }
    } else {
        // Mark first greeting as done
        update_user_meta($user_id, 'vmo_first_greeting_done', true);
    }
    
    // Generate personalized greeting based on scenario
    $greeting = '';
    
    if ($scenario === 'active_business') {
        // User has completed consultation and has active business
        $greeting = "Welcome back, {$username}. Jarvis here. Your VMO team is ready to keep building, refining, and managing your business. If you want strategy, content, design, or new execution work, open the red Business Wizard button and Jerreme will take it from there.";
    } elseif ($scenario === 'incomplete_consultation') {
        // User has started but not completed consultation
        $greeting = "Welcome back, {$username}. I'm Jarvis, your VMO launch guide. Your builder is ready, and behind me is the full VMO AI team covering design, content, SEO, documents, media, and automation. Click the red Business Wizard button to speak with Jerreme and continue your consultation.";
    } else {
        // First time user
        $greeting = "Welcome, {$username}. I'm Jarvis, your VMO AI launch guide. Your website builder is ready, and the wider VMO team is standing by across strategy, content, design, SEO, media, documents, and automation. Click the red Business Wizard button to meet Jerreme and begin building your business the right way.";
    }
    
    wp_send_json_success(array(
        'scenario' => $scenario,
        'username' => $username,
        'display_name' => $username,
        'greeting' => $greeting,
        'has_new_data' => $has_new_data,
        'new_data_summary' => $new_data_summary,
        'has_published_site' => $has_published_site,
        'has_consultation' => !empty($consultation),
        'status' => $scenario
    ));
});

// Save greeting to chat history so AI has full context
add_action('wp_ajax_vmo_save_greeting_to_history', function() {
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'));
        return;
    }
    
    $greeting = isset($_POST['greeting']) ? sanitize_textarea_field(wp_unslash($_POST['greeting'])) : '';
    
    if (empty($greeting)) {
        wp_send_json_error(array('message' => 'No greeting provided'));
        return;
    }
    
    $user_id = get_current_user_id();
    global $wpdb;
    $chat_history_table = $wpdb->prefix . 'vmo_chat_history';
    
    // Save greeting as assistant message to database
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $result = $wpdb->insert(
        $chat_history_table,
        [
            'user_id' => $user_id,
            'message_role' => 'assistant',
            'message_content' => $greeting,
            'conversation_context' => 'auto_greeting',
            'chat_type' => isset($_POST['chat_type']) ? sanitize_text_field(wp_unslash($_POST['chat_type'])) : 'jarvis',
            'created_at' => current_time('mysql')
        ],
        ['%d', '%s', '%s', '%s', '%s', '%s']
    );
    
    if ($result === false) {
        if (defined('WP_DEBUG') && WP_DEBUG) error_log('❌ VMO: Failed to save greeting to database: ' . $wpdb->last_error);
        wp_send_json_error(array('message' => 'Failed to save greeting', 'error' => $wpdb->last_error));
        return;
    }
    
    if (defined('WP_DEBUG') && WP_DEBUG) error_log('✅ VMO: Greeting saved to chat history for AI context');
    wp_send_json_success(array('message' => 'Greeting saved successfully'));
});

// === Daily Progress Snapshot Cron ===
add_action('vmo_daily_progress_snapshot', function() {
    if (!function_exists('wp_get_current_user')) return;
    global $wpdb;
    $chat_history_table = $wpdb->prefix . 'vmo_chat_history';
    $summaries_table = $wpdb->prefix . 'vmo_progress_summaries';

    // Summarize per user based on last 24h activity
    $since = gmdate('Y-m-d H:i:s', time() - DAY_IN_SECONDS);
    $user_ids = $wpdb->get_col($wpdb->prepare(
        "SELECT DISTINCT user_id FROM $chat_history_table WHERE created_at >= %s LIMIT 500",
        $since
    ));
    if (empty($user_ids)) return;

    foreach ($user_ids as $uid) {
        $count_msgs = (int)$wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM $chat_history_table WHERE user_id = %d AND created_at >= %s",
            $uid, $since
        ));
        $count_ai = (int)$wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM $chat_history_table WHERE user_id = %d AND message_role='assistant' AND created_at >= %s",
            $uid, $since
        ));
        $count_user = $count_msgs - $count_ai;
        $date = gmdate('Y-m-d');

        $summary = "Daily activity summary for $date:\n".
                   "• Total messages: $count_msgs\n".
                   "• User messages: $count_user\n".
                   "• AI responses: $count_ai\n".
                   "• Status: On track. Review milestones and next actions.";

        // Upsert summary for the day
        $existing_id = $wpdb->get_var($wpdb->prepare(
            "SELECT id FROM $summaries_table WHERE user_id = %d AND period = 'daily' AND period_date = %s",
            $uid, $date
        ));
        if ($existing_id) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->update($summaries_table, [
                'summary' => $summary,
                'updated_at' => current_time('mysql')
            ], [ 'id' => $existing_id ], [ '%s', '%s' ], [ '%d' ]);
        } else {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->insert($summaries_table, [
                'user_id' => $uid,
                'period' => 'daily',
                'period_date' => $date,
                'summary' => $summary,
                'created_at' => current_time('mysql')
            ], [ '%d', '%s', '%s', '%s', '%s' ]);
        }

        // Also compute a weekly rollup every Monday for the previous 7 days
        $dow = (int) gmdate('N'); // 1..7
        if ($dow === 1) {
            $week_start = gmdate('Y-m-d', strtotime('monday this week'));
            $week_end = gmdate('Y-m-d', strtotime('sunday this week'));

            $exists_week = $wpdb->get_var($wpdb->prepare(
                "SELECT id FROM $summaries_table WHERE user_id = %d AND period = 'weekly' AND period_date = %s",
                $uid, $week_start
            ));
            if (!$exists_week) {
                $since_week = gmdate('Y-m-d H:i:s', time() - 7 * DAY_IN_SECONDS);
                $count_msgs_w = (int)$wpdb->get_var($wpdb->prepare(
                    "SELECT COUNT(*) FROM $chat_history_table WHERE user_id = %d AND created_at >= %s",
                    $uid, $since_week
                ));
                $count_ai_w = (int)$wpdb->get_var($wpdb->prepare(
                    "SELECT COUNT(*) FROM $chat_history_table WHERE user_id = %d AND message_role='assistant' AND created_at >= %s",
                    $uid, $since_week
                ));
                $count_user_w = $count_msgs_w - $count_ai_w;

                $recent_dailies = $wpdb->get_results($wpdb->prepare(
                    "SELECT period_date, summary FROM $summaries_table WHERE user_id = %d AND period='daily' AND period_date >= %s ORDER BY period_date DESC LIMIT 3",
                    $uid, $since_week
                ), ARRAY_A);
                $daily_snippets = '';
                if (!empty($recent_dailies)) {
                    foreach ($recent_dailies as $row) {
                        $daily_snippets .= "- " . $row['period_date'] . ": " . wp_strip_all_tags((string)$row['summary']) . "\n";
                    }
                }

                $weekly_text = "Weekly activity summary (".$week_start." → ".$week_end."):\n".
                               "• Total messages: $count_msgs_w\n".
                               "• User messages: $count_user_w\n".
                               "• AI responses: $count_ai_w\n".
                               ($daily_snippets ? ("\nRecent daily notes:\n".$daily_snippets) : '') .
                               "\nStatus: Keep momentum. Review milestones and set next week's top 3 priorities.";

                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $wpdb->insert($summaries_table, [
                    'user_id' => $uid,
                    'period' => 'weekly',
                    'period_date' => $week_start,
                    'summary' => $weekly_text,
                    'created_at' => current_time('mysql')
                ], [ '%d', '%s', '%s', '%s', '%s' ]);
            }
        }
    }
});

// Power Dialer Batch Processing Hook
// Handles scheduled batches of outbound calls for Power Dialer campaigns
add_action('vmo_power_dialer_batch', function($campaign_id) {
    // Get the Twilio Manager instance and process the batch
    $twilio_manager = VMO_Twilio_Manager::get_instance();
    if ($twilio_manager && method_exists($twilio_manager, 'schedule_power_dialer_batch')) {
        // Trigger through reflection since method is private
        $reflection = new ReflectionMethod($twilio_manager, 'schedule_power_dialer_batch');
        $reflection->setAccessible(true);
        $reflection->invoke($twilio_manager, $campaign_id);
    }
}, 10, 1);

// Quarterly Insights Auto-Generation Cron Hook
// Automatically generates quarterly business insights for all active users
add_action('vmo_quarterly_insights_cron', function() {
    global $wpdb;
    
    // Get current quarter info
    $current_month = (int) date('n');
    $current_year = (int) date('Y');
    $quarter = ceil($current_month / 3);
    
    // Check if this is the first day of a new quarter
    $is_new_quarter = in_array($current_month, [1, 4, 7, 10]) && (int) date('j') <= 7;
    
    if (!$is_new_quarter) {
        return; // Only run in first week of each quarter
    }
    
    // Get all users with active subscriptions or token balance
    $users = $wpdb->get_col(
        $wpdb->prepare(
            "SELECT DISTINCT user_id FROM {$wpdb->usermeta} 
            WHERE meta_key = %s AND CAST(meta_value AS UNSIGNED) > 0
            LIMIT 1000",
            'vmo_token_balance'
        )
    );
    
    if (!$users) {
        return;
    }
    
    foreach ($users as $user_id) {
        // Check if we already generated a report for this quarter
        $reports_table = $wpdb->prefix . 'vmo_quarterly_reports';
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        if ($wpdb->get_var("SHOW TABLES LIKE '{$reports_table}'") === $reports_table) {
            $existing = $wpdb->get_var($wpdb->prepare(
                "SELECT id FROM {$reports_table} WHERE user_id = %d AND quarter = %d AND year = %d",
                $user_id, $quarter, $current_year
            ));
            if ($existing) {
                continue; // Already generated
            }
        }
        
        // Generate quarterly insights for this user
        // We need to simulate the AJAX environment for the user
        wp_set_current_user($user_id);
        
        // Call the generation function directly (it will use current_user_id)
        if (function_exists('vmo_generate_quarterly_insights_for_user')) {
            vmo_generate_quarterly_insights_for_user($user_id);
        }
    }
});

// Register the quarterly cron schedule
add_action('init', function() {
    if (!wp_next_scheduled('vmo_quarterly_insights_cron')) {
        // Schedule to run daily at 9 AM - the function itself checks if it's the right time
        wp_schedule_event(strtotime('09:00:00'), 'daily', 'vmo_quarterly_insights_cron');
    }
});

// Feature flag: Payments (Stripe/Connect). ENABLED for VMO Payments functionality.
if (!defined('VMO_PAYMENTS_ENABLED')) {
    // Payments ENABLED - VMO white-labeled Stripe payment processing
    define('VMO_PAYMENTS_ENABLED', true);
}

// VMO AI Builder v8.0.0: Unified Business Development Platform with Multi-Agent AI System
if (!defined('VMO_OPENAI_FAST_MODEL')) {
    define('VMO_OPENAI_FAST_MODEL', 'gpt-5.3-chat-latest');
}
if (!defined('VMO_OPENAI_STRATEGIC_MODEL')) {
    define('VMO_OPENAI_STRATEGIC_MODEL', 'gpt-5.4');
}
if (!defined('VMO_OPENAI_REALTIME_MODEL')) {
    define('VMO_OPENAI_REALTIME_MODEL', 'gpt-realtime');
}
if (!defined('VMO_OPENAI_REALTIME_FALLBACK_MODEL')) {
    define('VMO_OPENAI_REALTIME_FALLBACK_MODEL', 'gpt-realtime-mini');
}
if (!defined('VMO_OPENAI_IMAGE_MODEL')) {
    define('VMO_OPENAI_IMAGE_MODEL', 'gpt-image-1');
}
if (!defined('VMO_OPENAI_SORA_MODEL')) {
    define('VMO_OPENAI_SORA_MODEL', 'sora-2-pro');
}
if (!defined('VMO_OPENAI_SORA_FALLBACK_MODEL')) {
    define('VMO_OPENAI_SORA_FALLBACK_MODEL', 'sora-2');
}
if (!defined('VMO_PRIMARY_MODEL')) {
    define('VMO_PRIMARY_MODEL', VMO_OPENAI_FAST_MODEL);
}
if (!defined('VMO_OPENAI_MODEL')) {
    define('VMO_OPENAI_MODEL', VMO_OPENAI_STRATEGIC_MODEL);
}
if (!defined('VMO_TOKEN_RATE')) {
    define('VMO_TOKEN_RATE', 0.000667); // $0.000667 per VMO token
}
if (!defined('VMO_DEFAULT_AFFILIATE_COMMISSION')) {
    define('VMO_DEFAULT_AFFILIATE_COMMISSION', 5.00);
}
if (!defined('VMO_OPERATING_OVERHEAD_RATE')) {
    define('VMO_OPERATING_OVERHEAD_RATE', 0.40);
}
if (!defined('VMO_MIN_NET_PROFIT_RATE')) {
    define('VMO_MIN_NET_PROFIT_RATE', 0.25);
}
if (!defined('VMO_PROVIDER_COST_BUDGET_RATE')) {
    define('VMO_PROVIDER_COST_BUDGET_RATE', 0.30);
}

// Updated token costs for v8.0.0 - includes business wizard features
if (!defined('VMO_TOKENS_FULL_PAGE')) {
    define('VMO_TOKENS_FULL_PAGE', 2500); // Complete webpage
}
if (!defined('VMO_TOKENS_SECTION')) {
    define('VMO_TOKENS_SECTION', 500); // Single section (400-600 range)
}
if (!defined('VMO_TOKENS_REFINEMENT')) {
    define('VMO_TOKENS_REFINEMENT', 225); // AI refinement (150-300 range)
}
if (!defined('VMO_TOKENS_IMAGE_GENERATION')) {
    define('VMO_TOKENS_IMAGE_GENERATION', 1080); // Image generation (9x markup on ~$0.08 avg GPT Image 1 cost)
}
if (!defined('VMO_TOKENS_ADVANCED_EFFECTS')) {
    define('VMO_TOKENS_ADVANCED_EFFECTS', 175); // Animations/effects (100-250 range)
}
// VOICE SYSTEM REMOVED: Voice transcription tokens disabled
// if (!defined('VMO_TOKENS_VOICE_TRANSCRIPTION')) {
//     define('VMO_TOKENS_VOICE_TRANSCRIPTION', 10); // Voice transcription per minute
// }
if (!defined('VMO_TOKENS_MEDIA_INTEGRATION')) {
    // Media Integration: ~8K input + 4K output tokens on GPT-5.2
    // Actual cost: $0.02 (input) + $0.04 (output) = $0.06
    // 9x markup: $0.54 / $0.000667 = 810 tokens (rounded to 900 for safety margin)
    define('VMO_TOKENS_MEDIA_INTEGRATION', 900); // AI-powered creative media integration (9x markup)
}

// Business Wizard specific token costs
if (!defined('VMO_TOKENS_BUSINESS_WIZARD')) {
    define('VMO_TOKENS_BUSINESS_WIZARD', 2500); // Business wizard conversation
}

if (!defined('VMO_TOKENS_EMAIL')) {
    define('VMO_TOKENS_EMAIL', 50); // Cost per email sent (MXroute flat-rate, rationing only)
}

if (!defined('VMO_TOKENS_GENERATE_WEBSITE')) {
    define('VMO_TOKENS_GENERATE_WEBSITE', 9450); // Complete business website generation (9x markup on ~$0.70 API cost)
}
if (!defined('VMO_TOKENS_AI_CONVERSATION')) {
    define('VMO_TOKENS_AI_CONVERSATION', 50); // AI conversation per message
}
// VOICE SYSTEM REMOVED: Voice processing tokens disabled
// if (!defined('VMO_TOKENS_VOICE_PROCESSING')) {
//     define('VMO_TOKENS_VOICE_PROCESSING', 100); // Voice processing
// }
if (!defined('VMO_TOKENS_PREMIUM_FEATURES')) {
    define('VMO_TOKENS_PREMIUM_FEATURES', 1000); // Premium features
}

// ✅ NEW v8.1.0: Token costs for previously unbilled operations (fixing revenue leaks)
// All costs maintain 7-9x markup on actual API costs for sustainable 23% margin

// TTS (Text-to-Speech) - OpenAI tts-1-hd costs ~$0.030/1K chars
// At $0.000667/token, 1K chars = $0.030 / $0.000667 = ~45 tokens (actual cost)
// 9x markup: 45 * 9 = 405 tokens per 1K chars → ~200 char response = 9 actual tokens * 9 = 81
if (!defined('VMO_TOKENS_TTS_RESPONSE')) {
    define('VMO_TOKENS_TTS_RESPONSE', 81); // TTS voice response (~200 chars avg, 9x markup)
}
if (!defined('VMO_TOKENS_TTS_PER_1K_CHARS')) {
    define('VMO_TOKENS_TTS_PER_1K_CHARS', 405); // TTS per 1,000 characters (9x markup on $0.030)
}

// Consultation Greeting Audio - TTS-1-HD for consultation greeting (~600-800 chars)
// At $0.030/1K chars: ~700 chars = $0.021 actual → 31.5 tokens actual → 9x = 285
if (!defined('VMO_TOKENS_GREETING_AUDIO')) {
    define('VMO_TOKENS_GREETING_AUDIO', 285); // Consultation greeting TTS generation (9x markup)
}

// Whisper Transcription - OpenAI costs ~$0.006/min
// At $0.000667/token, 1 min = $0.006 / $0.000667 = ~9 tokens (actual cost)
// 9x markup: 9 * 9 = 81 tokens per minute
if (!defined('VMO_TOKENS_TRANSCRIPTION_PER_MIN')) {
    define('VMO_TOKENS_TRANSCRIPTION_PER_MIN', 81); // Whisper transcription per minute (9x markup)
}

// Vision Analysis (GPT-4o Vision) - ~$0.01-0.03 per image analysis
// At $0.000667/token, avg $0.02 = ~30 tokens (actual cost)
// 9x markup: 30 * 9 = 270 tokens
if (!defined('VMO_TOKENS_VISION_ANALYSIS')) {
    define('VMO_TOKENS_VISION_ANALYSIS', 270); // Image/vision analysis (9x markup)
}

// Deep Research - GPT-5.2 extensive research (25+ API calls, ~$0.50-2.00 per research)
// Average $1.00 actual cost = ~1500 tokens; 9x markup = 13,500 tokens
if (!defined('VMO_TOKENS_DEEP_RESEARCH')) {
    define('VMO_TOKENS_DEEP_RESEARCH', 13500); // Comprehensive deep research (9x markup)
}

// Consultation Message - Each AI response in business wizard (~$0.02-0.05)
// Average $0.03 actual cost = ~45 tokens; 9x markup = 405 tokens
if (!defined('VMO_TOKENS_CONSULTATION_MESSAGE')) {
    define('VMO_TOKENS_CONSULTATION_MESSAGE', 405); // Per consultation AI response (9x markup)
}

// Document Analysis - GPT-4o analyzing uploaded documents (~$0.02-0.05)
// Average $0.03 = ~45 tokens; 9x markup = 405 tokens
if (!defined('VMO_TOKENS_DOCUMENT_ANALYSIS')) {
    define('VMO_TOKENS_DOCUMENT_ANALYSIS', 405); // Document/blueprint analysis (9x markup)
}

// Email Campaign Generation - GPT generating campaign content (~$0.05-0.10)
// Average $0.07 = ~105 tokens; 9x markup = 945 tokens
if (!defined('VMO_TOKENS_EMAIL_CAMPAIGN_GENERATION')) {
    define('VMO_TOKENS_EMAIL_CAMPAIGN_GENERATION', 945); // AI email campaign content generation (9x markup)
}

// Contact Insights - AI analyzing contact data (~$0.02-0.04)
// Average $0.03 = ~45 tokens; 9x markup = 405 tokens
if (!defined('VMO_TOKENS_CONTACT_INSIGHTS')) {
    define('VMO_TOKENS_CONTACT_INSIGHTS', 405); // AI contact insights generation (9x markup)
}

// Business Roadmap Generation - Comprehensive AI planning (~$0.10-0.20)
// Average $0.15 = ~225 tokens; 9x markup = 2025 tokens
if (!defined('VMO_TOKENS_ROADMAP_GENERATION')) {
    define('VMO_TOKENS_ROADMAP_GENERATION', 2025); // Business roadmap/daily activities generation (9x markup)
}

// GPT Image 1 Generation - $0.04-0.12 per image (1024x1024 standard)
// At $0.08 average = ~120 tokens actual; 9x markup = 1080 tokens
if (!defined('VMO_TOKENS_DALLE_IMAGE')) {
    define('VMO_TOKENS_DALLE_IMAGE', 1080); // GPT Image 1 generation (9x markup on $0.08 avg)
}

// Sora 2 Pro Video Generation - Premium AI video creation
// Pricing: ~$0.60 per second of video (sora-2-pro)
// 4 sec = ~$2.40 actual → 7x markup = $16.80 → 25,200 tokens
// 8 sec = ~$4.80 actual → 7x markup = $33.60 → 50,400 tokens
// 12 sec = ~$7.20 actual → 7x markup = $50.40 → 75,600 tokens
if (!defined('VMO_TOKENS_SORA_VIDEO_4S')) {
    define('VMO_TOKENS_SORA_VIDEO_4S', 25200); // Sora 2 Pro 4-second video (7x markup)
}
if (!defined('VMO_TOKENS_SORA_VIDEO_8S')) {
    define('VMO_TOKENS_SORA_VIDEO_8S', 50400); // Sora 2 Pro 8-second video (7x markup)
}
if (!defined('VMO_TOKENS_SORA_VIDEO_12S')) {
    define('VMO_TOKENS_SORA_VIDEO_12S', 75600); // Sora 2 Pro 12-second video (7x markup)
}
// Sora 2 Standard (lower quality, ~$0.30/sec)
// 4 sec = ~$1.20 actual → 7x = $8.40 → 12,600 tokens
// 8 sec = ~$2.40 actual → 7x = $16.80 → 25,200 tokens
// 12 sec = ~$3.60 actual → 7x = $25.20 → 37,800 tokens
if (!defined('VMO_TOKENS_SORA_STD_4S')) {
    define('VMO_TOKENS_SORA_STD_4S', 12600); // Sora 2 Standard 4-second video (7x markup)
}
if (!defined('VMO_TOKENS_SORA_STD_8S')) {
    define('VMO_TOKENS_SORA_STD_8S', 25200); // Sora 2 Standard 8-second video (7x markup)
}
if (!defined('VMO_TOKENS_SORA_STD_12S')) {
    define('VMO_TOKENS_SORA_STD_12S', 37800); // Sora 2 Standard 12-second video (7x markup)
}

// ═══════════════════════════════════════════════════════════════════════════════
// ✅ RECURRING OPERATION TOKEN COSTS v8.1.0
// These are for scheduled/automated AI operations that run weekly/monthly/daily
// These justify the ongoing subscription and provide continuous value
// ═══════════════════════════════════════════════════════════════════════════════

// Weekly Deep Research - Comprehensive business analysis (~GPT-5.2 extended reasoning)
// Actual API cost ~$0.05 = 75 tokens; 20x markup = 1,500 tokens
// This scans competitors, market trends, KPIs, and generates strategic recommendations
if (!defined('VMO_TOKENS_WEEKLY_DEEP_RESEARCH')) {
    define('VMO_TOKENS_WEEKLY_DEEP_RESEARCH', 1500); // Weekly deep business research scan (20x markup)
}

// Weekly Email Research & Automation - Full marketing intelligence
// Actual API cost ~$0.10 = 150 tokens; 20x markup = 3,000 tokens
// Conducts competitive email analysis, generates leads, creates campaigns
if (!defined('VMO_TOKENS_WEEKLY_EMAIL_RESEARCH')) {
    define('VMO_TOKENS_WEEKLY_EMAIL_RESEARCH', 3000); // Weekly email marketing automation (20x markup)
}

// Weekly SEO Audit - Comprehensive SEO health check
// Actual API cost ~$0.08 = 120 tokens; 20x markup = 2,400 tokens
// Analyzes competition, generates strategy updates, identifies content gaps
if (!defined('VMO_TOKENS_WEEKLY_SEO_AUDIT')) {
    define('VMO_TOKENS_WEEKLY_SEO_AUDIT', 2400); // Weekly SEO audit and strategy (20x markup)
}

// Monthly Blog Generation - Full SEO-optimized blog post
// Actual API cost ~$0.05 = 75 tokens; 20x markup = 1,500 tokens
// Creates 1500-2000 word article with topic research
if (!defined('VMO_TOKENS_MONTHLY_BLOG')) {
    define('VMO_TOKENS_MONTHLY_BLOG', 1500); // Monthly automated blog post (20x markup)
}

// Competitor Monitoring - Bi-weekly competitive analysis
// Actual API cost ~$0.05 = 75 tokens; 20x markup = 1,500 tokens
// Deep dive into competitor strategies and market positioning
if (!defined('VMO_TOKENS_COMPETITOR_MONITORING')) {
    define('VMO_TOKENS_COMPETITOR_MONITORING', 1500); // Competitor monitoring scan (20x markup)
}

// Email Strategy Review - Monthly email performance analysis
// Actual API cost ~$0.04 = 60 tokens; 20x markup = 1,200 tokens
// Reviews campaigns, generates optimization recommendations
if (!defined('VMO_TOKENS_EMAIL_STRATEGY_REVIEW')) {
    define('VMO_TOKENS_EMAIL_STRATEGY_REVIEW', 1200); // Monthly email strategy review (20x markup)
}

// Content Embeddings - Daily knowledge base update
// Actual API cost ~$0.01 = 15 tokens; 20x markup = 300 tokens
// Creates embeddings from website content for AI phone assistant
if (!defined('VMO_TOKENS_CONTENT_EMBEDDINGS')) {
    define('VMO_TOKENS_CONTENT_EMBEDDINGS', 300); // Daily content embedding generation (20x markup)
}

// Call Summary - Post-call transcription and analysis
// Whisper transcription + GPT summary generation
// Real cost: ~$0.03 (Whisper + GPT), 10x markup = $0.30 = 450 tokens
if (!defined('VMO_TOKENS_CALL_SUMMARY')) {
    define('VMO_TOKENS_CALL_SUMMARY', 450); // Call transcription + summary (10x markup)
}

// ✅ PHONE SYSTEM TOKEN COSTS
// Based on actual Twilio costs: ~$0.10/min for AI calls
// At $0.000667/token = ~150 tokens/min actual cost
// 2x markup for sustainable business: 300 tokens/min = $0.20/min (profit: $0.10/min)
// NEVER operate at a loss - heavy users could bankrupt VMO at subsidized rates
if (!defined('VMO_TOKENS_PHONE_PER_MINUTE')) {
    define('VMO_TOKENS_PHONE_PER_MINUTE', 300); // 300 tokens per minute of phone time (2x markup)
}

// Publishing costs based on user type
// Note: No free users can publish — they must purchase a plan first.
if (!defined('VMO_TOKENS_PUBLISH_FREE')) {
    define('VMO_TOKENS_PUBLISH_FREE', 625); // Legacy constant — free users cannot publish (0 tokens)
}
if (!defined('VMO_TOKENS_PUBLISH_SUBSCRIBER')) {
    define('VMO_TOKENS_PUBLISH_SUBSCRIBER', 500); // Subscribers
}
if (!defined('VMO_TOKENS_PUBLISH_TOPUP')) {
    define('VMO_TOKENS_PUBLISH_TOPUP', 550); // Top-up users
}

// Updated plan allocations

/**
 * ✅ HELPER: Build OpenAI API payload with correct token parameter
 * GPT-5.2 and o-series models require 'max_completion_tokens', older models use 'max_tokens'
 * 
 * @param string $model The model name
 * @param int $tokens The maximum tokens for completion
 * @return array The token parameter array to merge with payload
 */
function vmo_get_token_param($model, $tokens) {
    // GPT-5.x and o-series models (o1, o3) require max_completion_tokens
    if (preg_match('/^(gpt-5|o1|o3)/i', $model)) {
        return array('max_completion_tokens' => $tokens);
    }
    // Older models (gpt-4, gpt-4o, gpt-4o-mini) use max_tokens
    return array('max_tokens' => $tokens);
}

// --- Load Documents handler (registers AJAX endpoints for the Documents tab) with file validation
$documents_handler_path = VMO_AI_BUILDER_DIR . 'includes/documents-handler.php';
if (file_exists($documents_handler_path) && is_readable($documents_handler_path)) {
    try {
        require_once $documents_handler_path;
    } catch (Exception $e) {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('VMO: Failed to load documents-handler.php: ' . $e->getMessage());
        }
    }
} else {
    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log('VMO: documents-handler.php missing or unreadable: ' . $documents_handler_path);
    }
}
// ✅ NO FREE TOKENS — All users must purchase a plan before using any features.
// This constant is kept at 0 for backward compatibility with code that references it.
if (!defined('VMO_TOKENS_FREE_TRIAL')) {
    define('VMO_TOKENS_FREE_TRIAL', 0); // No free tokens — users must purchase a plan
}

// ✅ FINAL PRICING: Sustainable Business Model (23% Net Margin)
// 25% Direct Costs (API/Tokens) + 40% Operating Overhead + 26.75% Tax on Profit = 23% Net Profit
// Includes: Servers, Salaries, Marketing, R&D (Custom AI), Legal, Insurance
// See: BUSINESS-FINANCIAL-MODEL.md for complete cash flow breakdown
if (!defined('VMO_TOKENS_TESTER_PACKAGE')) {
    define('VMO_TOKENS_TESTER_PACKAGE', 36034); // $49.99 - Covers full onboarding + testing (108 min phone OR 127 pages)
}
if (!defined('VMO_TOKENS_STARTER_PLAN')) {
    define('VMO_TOKENS_STARTER_PLAN', 72326); // $99.99/month - 217 min phone max OR 255 pages max
}
if (!defined('VMO_TOKENS_PRO_PLAN')) {
    define('VMO_TOKENS_PRO_PLAN', 144909); // $199.99/month - 435 min phone max OR 512 pages max
}
if (!defined('VMO_TOKENS_ELITE_PLAN')) {
    define('VMO_TOKENS_ELITE_PLAN', 362659); // $499.99/month - 1,089 min phone max OR 1,281 pages max
}
// Legacy compatibility
if (!defined('VMO_TOKENS_BUSINESS_PLAN')) {
    define('VMO_TOKENS_BUSINESS_PLAN', 362659); // Same as Elite
}
if (!defined('VMO_TOKENS_ENTERPRISE_PLAN')) {
    define('VMO_TOKENS_ENTERPRISE_PLAN', 500000); // Custom enterprise tier
}

// Phone activation is now FREE for all paid plans (no 30k token charge)
if (!defined('VMO_PHONE_ACTIVATION_FREE')) {
    define('VMO_PHONE_ACTIVATION_FREE', true);
}

// === Table Name Constants – Set after plugins_loaded (to ensure $wpdb is ready) ===
add_action('plugins_loaded', function () {
    global $wpdb;
    if (!defined('VMO_PROJECTS_TABLE')) {
        define('VMO_PROJECTS_TABLE', $wpdb->prefix . 'vmo_projects');
    }
    if (!defined('VMO_HISTORY_TABLE')) {
        define('VMO_HISTORY_TABLE', $wpdb->prefix . 'vmo_project_history');
    }
});

/**
 * Determine if the current user has a published live page generated by VMO.
 * We consider a site "published" when at least one vmo_project has a non-empty
 * vmo_live_page_id meta that points to a WordPress page with status 'publish'.
 */
function vmo_user_has_published_site($user_id) {
    $user_id = (int) $user_id;
    if ($user_id <= 0) return false;

    $projects = get_posts([
        'post_type'      => 'vmo_project',
        'author'         => $user_id,
        'posts_per_page' => 5,
        'fields'         => 'ids',
        'meta_query'     => [
            [
                'key'     => 'vmo_live_page_id',
                'compare' => 'EXISTS',
            ],
        ],
    ]);

    if (empty($projects)) return false;

    foreach ($projects as $project_id) {
        $live_id = get_post_meta($project_id, 'vmo_live_page_id', true);
        if ($live_id) {
            $status = get_post_status((int) $live_id);
            if ($status === 'publish') return true;
        }
    }
    return false;
}

/**
 * ✅ Get or create an activation record for the user
 * This ensures every user has an install_id for email/token operations
 */
function vmo_get_or_create_user_activation($user_id = null) {
    if (!$user_id) {
        $user_id = get_current_user_id();
    }
    if (!$user_id) {
        return null;
    }
    
    global $wpdb;
    $table_name = $wpdb->prefix . 'vmo_manager_activations';
    
    // Check if table exists, create it if not
    $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name));
    if (!$table_exists) {
        // Table doesn't exist - trigger table creation
        vmo_create_plugin_tables();
        $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name));
        if (!$table_exists) {
            if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: Failed to create manager_activations table');
            return null;
        }
    }
    
    // Check if user has an activation
    $activation = $wpdb->get_row($wpdb->prepare(
        "SELECT * FROM {$table_name} WHERE user_id = %d LIMIT 1",
        $user_id
    ));
    
    if ($activation) {
        return $activation;
    }
    
    // Create a new activation for the user
    $install_id = 'vmo_' . wp_generate_uuid4();
    $site_url = get_site_url();
    
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $inserted = $wpdb->insert($table_name, [
        'install_id' => $install_id,
        'user_id' => $user_id,
        'site_url' => $site_url,
        'plan_type' => 'free',
        'token_balance' => 0, // No free tokens — user must purchase a plan
        'tokens_used' => 0,
        'status' => 'active',
        'activated_at' => current_time('mysql'),
        'created_at' => current_time('mysql'),
        'updated_at' => current_time('mysql')
    ]);
    
    if (!$inserted) {
        if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: Failed to create activation for user ' . $user_id);
        return null;
    }
    
    if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: Created new activation for user ' . $user_id . ' with install_id ' . $install_id);
    
    return $wpdb->get_row($wpdb->prepare(
        "SELECT * FROM {$table_name} WHERE user_id = %d LIMIT 1",
        $user_id
    ));
}

/**
 * ✅ Get install_id for current user (creates activation if needed)
 */
function vmo_get_user_install_id($user_id = null) {
    $activation = vmo_get_or_create_user_activation($user_id);
    return $activation ? $activation->install_id : null;
}

// === Activation Hook to Create Tables ===
register_activation_hook(__FILE__, 'vmo_activate_plugin');
register_deactivation_hook(__FILE__, 'vmo_deactivate_plugin');

// ✅ Ensure critical tables exist on every admin page load (not just activation)
add_action('admin_init', 'vmo_ensure_critical_tables_exist');

// ✅ CLIENT MODE: Auto-register with the master server on first admin load
// When the plugin runs in VMO_CLIENT_MODE and has no installation_id yet,
// it calls the master's /register endpoint to get an installation_id and license_key.
add_action('admin_init', 'vmo_client_auto_register_with_master');

function vmo_client_auto_register_with_master() {
    // Only run in client mode
    if (!defined('VMO_CLIENT_MODE') || VMO_CLIENT_MODE !== true) {
        return;
    }

    // Already registered? Skip.
    $installation_id = get_option('vmo_installation_id', '');
    if (!empty($installation_id)) {
        return;
    }

    // Rate limit: don't try more than once per hour
    if (get_transient('vmo_client_register_attempted')) {
        return;
    }
    set_transient('vmo_client_register_attempted', true, HOUR_IN_SECONDS);

    // Determine master URL
    $master_url = defined('VMO_MASTER_PROXY_URL')
        ? VMO_MASTER_PROXY_URL
        : get_option('vmo_master_proxy_url', 'https://getvmo.com/wp-json/vmo-builder/v1/proxy');

    // Build the register URL (vmo-manager/v1/register on the master)
    $register_url = str_replace('/vmo-builder/v1/proxy', '/vmo-manager/v1/register', $master_url);

    $site_url    = home_url();
    $site_domain = wp_parse_url($site_url, PHP_URL_HOST);
    $admin_email = get_option('admin_email', '');

    $response = wp_remote_post($register_url, [
        'timeout' => 15,
        'headers' => ['Content-Type' => 'application/json'],
        'body'    => wp_json_encode([
            'site_url'    => $site_url,
            'site_email'  => $admin_email,
            'site_domain' => $site_domain,
            'user_email'  => $admin_email,
        ]),
    ]);

    if (is_wp_error($response)) {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('VMO Client: Auto-registration failed — ' . $response->get_error_message());
        }
        return;
    }

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

    if ($code === 200 && !empty($body['success']) && !empty($body['installation_id'])) {
        update_option('vmo_installation_id', sanitize_text_field($body['installation_id']));

        if (!empty($body['license_key'])) {
            update_option('vmo_license_key', sanitize_text_field($body['license_key']));
        }

        // Clear the rate-limit transient so it doesn't block future valid checks
        delete_transient('vmo_client_register_attempted');

        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('✅ VMO Client: Registered with master — install_id=' . $body['installation_id']);
        }
    } else {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('VMO Client: Registration response code=' . $code . ' body=' . wp_json_encode($body));
        }
    }
}

// ✅ CLIENT MODE: Periodic subscription sync from master server
// Runs once every 6 hours to keep local plan/tokens in sync with the master.
// Without this, plan upgrades/downgrades/renewals wouldn't reflect locally.
add_action('admin_init', 'vmo_client_sync_subscription_from_master');

function vmo_client_sync_subscription_from_master() {
    if (!defined('VMO_CLIENT_MODE') || VMO_CLIENT_MODE !== true) {
        return;
    }

    $license_key     = get_option('vmo_license_key', '');
    $installation_id = get_option('vmo_installation_id', '');

    if (empty($license_key) || empty($installation_id)) {
        return;
    }

    // Only sync every 6 hours
    $last_sync = get_transient('vmo_client_last_subscription_sync');
    if ($last_sync) {
        return;
    }

    // Set transient BEFORE the request to prevent concurrent syncs
    set_transient('vmo_client_last_subscription_sync', time(), 6 * HOUR_IN_SECONDS);

    $master_url = defined('VMO_MASTER_PROXY_URL')
        ? VMO_MASTER_PROXY_URL
        : get_option('vmo_master_proxy_url', 'https://getvmo.com/wp-json/vmo-builder/v1/proxy');

    // Build the subscription status URL on the master
    $status_url = str_replace('/vmo-builder/v1/proxy', '/vmo-builder/v1/subscription/status', $master_url);

    $response = wp_remote_get($status_url, [
        'timeout' => 10,
        'headers' => [
            'X-VMO-License'      => $license_key,
            'X-VMO-Installation' => $installation_id,
        ],
    ]);

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

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

    if ($code === 200 && !empty($body['success']) && !empty($body['subscription'])) {
        $user_id = get_current_user_id();
        if ($user_id) {
            $sub = $body['subscription'];
            if (!empty($sub['plan'])) {
                update_user_meta($user_id, 'vmo_subscription_status', sanitize_text_field($sub['plan']));
            }
            if (isset($sub['tokens'])) {
                update_user_meta($user_id, 'vmo_token_balance', (int) $sub['tokens']);
            }
        }
    }
}

// ✅ One-time migration: auto-enable call recording for all existing users
// Recording previously defaulted to OFF, which meant no calls were ever recorded.
// This flips it ON for every user who has it at 0 or unset.
add_action('admin_init', function() {
    if (get_option('vmo_recording_default_on_v1')) return;
    $users = get_users(array('fields' => 'ID'));
    foreach ($users as $uid) {
        update_user_meta($uid, 'call_recording_enabled', 1);
    }
    update_option('vmo_recording_default_on_v1', time(), true);
}, 4);

// ✅ One-time migration: ensure vmo_contacts has full_name, first_name, last_name columns
add_action('admin_init', function() {
    if (get_option('vmo_contacts_name_cols_migrated')) return;
    global $wpdb;
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    $table_exists = vmo_table_exists($contacts_table);
    if (!$table_exists) return; // table doesn't exist yet, activation hook will create it properly
    $suppress = $wpdb->suppress_errors(true);
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $cols = $wpdb->get_col("SHOW COLUMNS FROM {$contacts_table}", 0);
    if (is_array($cols)) {
        if (!in_array('full_name', $cols, true)) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE {$contacts_table} ADD COLUMN full_name VARCHAR(200) DEFAULT NULL AFTER last_name");
        }
        if (!in_array('first_name', $cols, true)) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE {$contacts_table} ADD COLUMN first_name VARCHAR(100) DEFAULT NULL AFTER lifecycle_stage");
        }
        if (!in_array('last_name', $cols, true)) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE {$contacts_table} ADD COLUMN last_name VARCHAR(100) DEFAULT NULL AFTER first_name");
        }
    }
    $wpdb->suppress_errors($suppress);
    update_option('vmo_contacts_name_cols_migrated', 1, true);
}, 5); // priority 5 = runs before most other admin_init hooks

// ===========================================
// VMO SIGN UP BONUS HANDLER
// Checks on login for deferred bonuses from webhook.
// These are post-purchase bonuses ONLY — given after a user
// buys a plan, to help cover the heavy Month 1 setup costs
// (consultation, generations, background ops, calls, emails).
// ===========================================
add_action('admin_init', 'vmo_process_pending_signup_bonus');
function vmo_process_pending_signup_bonus() {
    $user_id = get_current_user_id();
    if (!$user_id) return;
    
    // Check for deferred bonus set by webhook
    $bonus_tokens = (int) get_user_meta($user_id, 'vmo_unclaimed_signup_bonus', true);
    
    if ($bonus_tokens > 0) {
        $bonus_value = get_user_meta($user_id, 'vmo_unclaimed_signup_bonus_value', true) ?: '$0';
        $plan_name = get_user_meta($user_id, 'vmo_unclaimed_signup_bonus_plan', true) ?: 'Premium';
        
        // 1. Award Tokens
        if (function_exists('vmo_update_token_balance')) {
            vmo_update_token_balance($user_id, $bonus_tokens, 'add');
        } else {
            $current = (int) get_user_meta($user_id, 'vmo_token_balance', true);
            update_user_meta($user_id, 'vmo_token_balance', $current + $bonus_tokens);
        }
        
        // 2. Queue the Notification
        update_user_meta($user_id, 'vmo_pending_notification', wp_json_encode([
            'type' => 'welcome_bonus',
            'title' => '\ud83c\udf89 Sign Up Bonus!',
            'message' => "Thank you for choosing the {$plan_name} Plan! We've added {$bonus_value} in bonus credits to your account to help you get through your initial setup — consultation, website builds, phone calls, and more. Let's start building!",
            'amount' => $bonus_value,
            'created' => current_time('mysql')
        ]));
        
        // 3. Clean up
        delete_user_meta($user_id, 'vmo_unclaimed_signup_bonus');
        delete_user_meta($user_id, 'vmo_unclaimed_signup_bonus_value');
        delete_user_meta($user_id, 'vmo_unclaimed_signup_bonus_plan');
        
        // 4. Log
        if (defined('WP_DEBUG') && WP_DEBUG) error_log("VMO Sign Up Bonus Claimed: User {$user_id} logged in and received {$bonus_tokens} tokens.");
    }
}
function vmo_ensure_critical_tables_exist() {
    // Only run once per day to avoid performance impact
    $last_check = get_option('vmo_tables_last_check', 0);
    if (time() - $last_check < DAY_IN_SECONDS) {
        // But always check if the critical table is missing
        global $wpdb;
        $activations_table = $wpdb->prefix . 'vmo_manager_activations';
        $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $activations_table));
        if ($table_exists) {
            return; // Table exists, no need to run
        }
    }
    
    // Run table creation (buffer output to prevent HTML leaking into AJAX responses)
    ob_start();
    try {
        vmo_create_plugin_tables();
    } catch (Exception $e) {
        if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: Table creation error: ' . $e->getMessage());
    }
    ob_end_clean();
    update_option('vmo_tables_last_check', time());
    if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: Critical tables check completed');
}

function vmo_activate_plugin() {
    // Buffer ALL output during activation to prevent "unexpected output" errors.
    // dbDelta() and ALTER TABLE can emit notices/warnings that WordPress treats as errors.
    ob_start();

    try {
        vmo_create_plugin_tables();
    } catch (Exception $e) {
        if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO Activation Error: ' . $e->getMessage());
    }

    // Discard any output that leaked (dbDelta notices, SQL warnings, etc.)
    ob_end_clean();
    
    // Create abandoned cart table
    if (class_exists('VMO_Abandoned_Cart')) {
        VMO_Abandoned_Cart::create_table();
    }

    // Schedule monthly reports if not already scheduled
    if (!wp_next_scheduled('vmo_send_monthly_reports')) {
        wp_schedule_event(time(), 'monthly', 'vmo_send_monthly_reports');
    }

    // Schedule daily progress snapshots
    if (!wp_next_scheduled('vmo_daily_progress_snapshot')) {
        // Schedule for next 02:30 AM site time
        $timestamp = strtotime('tomorrow 02:30');
        if ($timestamp === false) { $timestamp = time() + DAY_IN_SECONDS; }
        wp_schedule_event($timestamp, 'daily', 'vmo_daily_progress_snapshot');
    }

    // Schedule session cleanup (removes stale consultation sessions daily)
    if (!wp_next_scheduled('vmo_cleanup_expired_sessions')) {
        wp_schedule_event(time() + HOUR_IN_SECONDS, 'daily', 'vmo_cleanup_expired_sessions');
    }

    // Schedule email queue processing (hourly)
    if (!wp_next_scheduled('vmo_process_email_queue')) {
        wp_schedule_event(time() + 300, 'hourly', 'vmo_process_email_queue');
    }

    // Schedule Stripe subscription sync (hourly fallback for missed webhooks)
    if (!wp_next_scheduled('vmo_sync_stripe_subscriptions')) {
        wp_schedule_event(time() + 600, 'hourly', 'vmo_sync_stripe_subscriptions');
    }

    // Schedule hourly A2P status polling so SMS users still get updates if a webhook is delayed.
    if (!wp_next_scheduled('vmo_a2p_status_poll')) {
        wp_schedule_event(time() + 900, 'hourly', 'vmo_a2p_status_poll');
    }

    // Schedule weekly analytics report
    if (!wp_next_scheduled('vmo_weekly_analytics_report')) {
        wp_schedule_event(time() + DAY_IN_SECONDS, 'weekly', 'vmo_weekly_analytics_report');
    }

    // Schedule weekly SEO report
    if (!wp_next_scheduled('vmo_weekly_seo_report')) {
        wp_schedule_event(time() + DAY_IN_SECONDS, 'weekly', 'vmo_weekly_seo_report');
    }

    // Schedule daily SEO check
    if (!wp_next_scheduled('vmo_daily_seo_check')) {
        wp_schedule_event(strtotime('tomorrow 04:00') ?: time() + DAY_IN_SECONDS, 'daily', 'vmo_daily_seo_check');
    }

    // Schedule daily competitor check
    if (!wp_next_scheduled('vmo_daily_competitor_check')) {
        wp_schedule_event(strtotime('tomorrow 05:00') ?: time() + DAY_IN_SECONDS, 'daily', 'vmo_daily_competitor_check');
    }

    // ──────────────────────────────────────────────────────────────
    // Auto-create the Stripe promo coupon for testers/reviewers
    // (runs only once; requires Stripe secret key to be configured)
    // NOTE: The /get-started/ page with [vmo_quicklaunch] lives on
    // the VMO marketing website (getvmo.com), NOT auto-created here.
    // ──────────────────────────────────────────────────────────────
    vmo_ensure_stripe_promo_coupon();
}

/**
 * Ensure the VMO reviewer-access Stripe promo coupon exists.
 * Creates a first-invoice access coupon + human-readable promotion code.
 * Idempotent — skips if already created or Stripe key not configured.
 */
function vmo_ensure_stripe_promo_coupon() {
    // Already created?
    if (get_option('vmo_stripe_promo_coupon_created')) {
        return;
    }

    $stripe_secret = get_option('vmo_stripe_secret_key', '');
    if (empty($stripe_secret)) {
        return; // No Stripe key yet — will retry on admin_init
    }

    $coupon_id   = 'vmo-elite-review-access';
    $promo_code  = 'VMO-ELITE-REVIEW';
    $headers     = [
        'Authorization' => 'Bearer ' . $stripe_secret,
        'Content-Type'  => 'application/x-www-form-urlencoded',
    ];

    // Step 1: Create the reviewer-access coupon
    $coupon_response = wp_remote_post('https://api.stripe.com/v1/coupons', [
        'timeout' => 15,
        'headers' => $headers,
        'body'    => [
            'id'          => $coupon_id,
            'percent_off' => 100,
            'duration'    => 'once',
            'name'        => 'VMO Elite Reviewer Access',
            'max_redemptions' => 100,      // Limit total uses
        ],
    ]);

    $coupon_body = json_decode(wp_remote_retrieve_body($coupon_response), true);

    // If coupon already exists in Stripe, that's fine — proceed to promo code
    if (isset($coupon_body['error']) && ($coupon_body['error']['code'] ?? '') !== 'resource_already_exists') {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('VMO: Stripe coupon creation failed — ' . ($coupon_body['error']['message'] ?? 'unknown'));
        }
        // Don't mark as created so it retries
        return;
    }

    // Step 2: Create the human-readable promotion code
    $promo_response = wp_remote_post('https://api.stripe.com/v1/promotion_codes', [
        'timeout' => 15,
        'headers' => $headers,
        'body'    => [
            'coupon' => $coupon_id,
            'code'   => $promo_code,
            'active' => 'true',
            'metadata[purpose]'   => 'Tester/reviewer promotional access',
            'metadata[created_by]' => 'vmo-plugin-auto',
        ],
    ]);

    $promo_body = json_decode(wp_remote_retrieve_body($promo_response), true);

    // Mark as created (even if promo code already exists)
    if (!empty($promo_body['id']) || (($promo_body['error']['code'] ?? '') === 'resource_already_exists')) {
        update_option('vmo_stripe_promo_coupon_created', true);
        update_option('vmo_stripe_promo_code', $promo_code);
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('VMO: Stripe promo coupon ' . $promo_code . ' ready');
        }
    } else {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('VMO: Stripe promo code creation failed — ' . ($promo_body['error']['message'] ?? 'unknown'));
        }
    }
}

function vmo_deactivate_plugin() {
    // Clear all scheduled events
    wp_clear_scheduled_hook('vmo_send_monthly_reports');
    wp_clear_scheduled_hook('vmo_daily_progress_snapshot');
    wp_clear_scheduled_hook('vmo_cleanup_expired_sessions');
    wp_clear_scheduled_hook('vmo_process_email_queue');
    wp_clear_scheduled_hook('vmo_sync_stripe_subscriptions');
    wp_clear_scheduled_hook('vmo_a2p_status_poll');
    wp_clear_scheduled_hook('vmo_weekly_analytics_report');
    wp_clear_scheduled_hook('vmo_weekly_seo_report');
    wp_clear_scheduled_hook('vmo_daily_seo_check');
    wp_clear_scheduled_hook('vmo_daily_competitor_check');
    wp_clear_scheduled_hook('vmo_quarterly_insights_cron');
    if (class_exists('VMO_Abandoned_Cart')) {
        VMO_Abandoned_Cart::deactivate();
    }
}

// ══════════════════════════════════════════════════════════════════
// admin_init fallback: Ensure Stripe promo coupon exists
// For existing installations that update without re-activating
// ══════════════════════════════════════════════════════════════════
add_action('admin_init', function() {
    if (!current_user_can('manage_options')) return;

    // Only run once per day to avoid performance overhead
    $last_check = get_option('vmo_quicklaunch_setup_checked', 0);
    if (time() - $last_check < DAY_IN_SECONDS) return;

    vmo_ensure_stripe_promo_coupon();

    update_option('vmo_quicklaunch_setup_checked', time());
});

// === Admin Notices for Stripe Configuration ===
add_action('admin_notices', function() {
    // Only show on VMO plugin pages
    $screen = get_current_screen();
    if (!$screen || strpos($screen->id, 'vmo') === false) {
        return;
    }
    
    // Check if user has dismissed this notice
    if (get_user_meta(get_current_user_id(), 'vmo_stripe_notice_dismissed', true)) {
        return;
    }
    
    // Only show to admins
    if (!current_user_can('manage_options')) {
        return;
    }
    
    // Check Stripe configuration status
    $stripe_secret = get_option('vmo_stripe_secret_key', '');
    $stripe_publishable = get_option('vmo_stripe_publishable_key', '');
    $payments_enabled = defined('VMO_PAYMENTS_ENABLED') && VMO_PAYMENTS_ENABLED;
    
    // Show warning if payments enabled but keys not configured
    if ($payments_enabled && (empty($stripe_secret) || empty($stripe_publishable))) {
        ?>
        <div class="notice notice-warning is-dismissible" data-vmo-notice="stripe-config">
            <p>
                <strong>⚠️ VMO Payments Setup Required</strong><br>
                Stripe payments are enabled, but API keys are not configured. 
                <a href="<?php echo admin_url('admin.php?page=vmo-ai-builder'); ?>">Configure Stripe Keys →</a>
            </p>
        </div>
        <?php
    }
    
    // Show success if everything is configured
    if ($payments_enabled && !empty($stripe_secret) && !empty($stripe_publishable)) {
        // Only check API connection if SDK is loaded
        if (class_exists('Stripe\\Stripe')) {
            try {
                \Stripe\Stripe::setApiKey($stripe_secret);
                $account = \Stripe\Account::retrieve();
                
                // Show success notice with account details
                ?>
                <div class="notice notice-success is-dismissible" data-vmo-notice="stripe-success">
                    <p>
                        <strong>✅ Stripe Connected Successfully</strong><br>
                        Account: <?php echo esc_html($account->email); ?> (<?php echo esc_html($account->country); ?>)<br>
                        Mode: <?php echo (strpos($stripe_secret, 'sk_test_') === 0) ? 'Test Mode' : 'Live Mode'; ?>
                    </p>
                </div>
                <?php
                
            } catch (Exception $e) {
                // Show error if API key is invalid
                ?>
                <div class="notice notice-error is-dismissible" data-vmo-notice="stripe-error">
                    <p>
                        <strong>❌ Stripe Connection Error</strong><br>
                        <?php echo esc_html($e->getMessage()); ?><br>
                        <a href="<?php echo admin_url('admin.php?page=vmo-ai-builder'); ?>">Check API Keys →</a>
                    </p>
                </div>
                <?php
            }
        }
    }
});

// Handle notice dismissal
add_action('wp_ajax_vmo_dismiss_notice', function() {
    check_ajax_referer('vmo-admin', 'nonce');
    update_user_meta(get_current_user_id(), 'vmo_stripe_notice_dismissed', true);
    wp_send_json_success();
});

// ========================================
// DISMISS BACKGROUND OPS UPGRADE PROMPT
// ========================================
add_action('wp_ajax_vmo_dismiss_upgrade_prompt', function() {
    check_ajax_referer('vmo_builder_nonce', 'security');
    $user_id = get_current_user_id();
    if (!$user_id) { wp_send_json_error(['message' => 'Not logged in']); return; }

    $index = isset($_POST['index']) ? intval($_POST['index']) : -1;
    $dismiss_all = !empty($_POST['dismiss_all']);

    if ($dismiss_all) {
        delete_user_meta($user_id, 'vmo_upgrade_prompts');
        wp_send_json_success(['cleared' => true]);
        return;
    }

    $prompts = get_user_meta($user_id, 'vmo_upgrade_prompts', true) ?: [];
    if ($index >= 0 && $index < count($prompts)) {
        array_splice($prompts, $index, 1);
        if (empty($prompts)) {
            delete_user_meta($user_id, 'vmo_upgrade_prompts');
        } else {
            update_user_meta($user_id, 'vmo_upgrade_prompts', $prompts);
        }
    }
    wp_send_json_success(['remaining' => count($prompts)]);
});

// ========================================
// NEXT.JS PROJECT EXPORT
// ========================================
// Generates a downloadable Next.js App Router project ZIP from the user's generated website
add_action('wp_ajax_vmo_export_nextjs', function() {
    check_ajax_referer('vmo_builder_nonce', 'security');
    $user_id = get_current_user_id();
    if (!$user_id) { wp_send_json_error(['message' => 'Not logged in']); return; }

    // Get the current website HTML from user meta or last content
    $website_html = get_user_meta($user_id, 'vmo_website_code', true);
    if (empty($website_html)) {
        $website_html = get_user_meta($user_id, 'vmo_generated_website', true);
    }
    if (empty($website_html)) {
        wp_send_json_error(['message' => 'No generated website found. Please generate a website first.']);
        return;
    }

    $business_data = get_user_meta($user_id, 'vmo_business_data', true) ?: [];
    $biz_name = sanitize_title($business_data['business_name'] ?? 'my-website');

    // Create temp directory for the project
    $tmp_dir = get_temp_dir() . 'vmo-nextjs-' . $user_id . '-' . time();
    $project_dir = $tmp_dir . '/' . $biz_name;
    wp_mkdir_p($project_dir . '/app');
    wp_mkdir_p($project_dir . '/components');
    wp_mkdir_p($project_dir . '/public');

    // package.json
    $package_json = wp_json_encode([
        'name' => $biz_name,
        'version' => '1.0.0',
        'private' => true,
        'scripts' => [
            'dev' => 'next dev',
            'build' => 'next build',
            'start' => 'next start',
            'lint' => 'next lint',
        ],
        'dependencies' => [
            'next' => '^14.0.0',
            'react' => '^18.2.0',
            'react-dom' => '^18.2.0',
            'gsap' => '^3.12.5',
            'framer-motion' => '^10.16.4',
            'three' => '^0.160.0',
            '@studio-freight/lenis' => '^1.0.29',
            'swiper' => '^11.0.0',
        ],
        'devDependencies' => [
            'tailwindcss' => '^3.4.0',
            'postcss' => '^8.4.0',
            'autoprefixer' => '^10.4.0',
            '@types/react' => '^18.2.0',
            '@types/node' => '^20.0.0',
            'typescript' => '^5.0.0',
        ],
    ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    vmo_get_filesystem()->put_contents($project_dir . '/package.json', $package_json, FS_CHMOD_FILE);

    // next.config.js
    vmo_get_filesystem()->put_contents($project_dir . '/next.config.js', "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  output: 'export',\n  images: { unoptimized: true },\n};\n\nmodule.exports = nextConfig;\n", FS_CHMOD_FILE);

    // tailwind.config.js
    vmo_get_filesystem()->put_contents($project_dir . '/tailwind.config.js', "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  content: ['./app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],\n  theme: {\n    extend: {\n      colors: { primary: '#3b82f6', secondary: '#10b981' },\n    },\n  },\n  plugins: [],\n};\n", FS_CHMOD_FILE);

    // postcss.config.js
    vmo_get_filesystem()->put_contents($project_dir . '/postcss.config.js', "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n", FS_CHMOD_FILE);

    // tsconfig.json
    $tsconfig = wp_json_encode([
        'compilerOptions' => [
            'target' => 'es5',
            'lib' => ['dom', 'dom.iterable', 'esnext'],
            'allowJs' => true,
            'skipLibCheck' => true,
            'strict' => true,
            'noEmit' => true,
            'esModuleInterop' => true,
            'module' => 'esnext',
            'moduleResolution' => 'bundler',
            'resolveJsonModule' => true,
            'isolatedModules' => true,
            'jsx' => 'preserve',
            'incremental' => true,
            'paths' => ['@/*' => ['./*']],
        ],
        'include' => ['next-env.d.ts', '**/*.ts', '**/*.tsx'],
        'exclude' => ['node_modules'],
    ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    vmo_get_filesystem()->put_contents($project_dir . '/tsconfig.json', $tsconfig, FS_CHMOD_FILE);

    // app/globals.css
    vmo_get_filesystem()->put_contents($project_dir . '/app/globals.css', "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n/* Smooth scrolling */\nhtml { scroll-behavior: smooth; }\nbody { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }\n", FS_CHMOD_FILE);

    // app/layout.tsx
    $layout = "'use client';\nimport './globals.css';\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <body>{children}</body>\n    </html>\n  );\n}\n";
    vmo_get_filesystem()->put_contents($project_dir . '/app/layout.tsx', $layout, FS_CHMOD_FILE);

    // app/page.tsx — the main generated website content
    // Extract React component code from the HTML, stripping CDN script tags
    $component_code = $website_html;
    // Remove script CDN tags (React, Babel, Tailwind CDN, etc.) — Next.js bundles these
    $component_code = preg_replace('/<script[^>]*src="https?:\/\/[^"]*"[^>]*><\/script>\s*/i', '', $component_code);
    $component_code = preg_replace('/<script[^>]*src="https?:\/\/[^"]*"[^>]*\/>\s*/i', '', $component_code);
    // Remove <link> CDN tags
    $component_code = preg_replace('/<link[^>]*href="https?:\/\/[^"]*"[^>]*\/?>\s*/i', '', $component_code);
    // Remove Babel script blocks and extract JSX
    if (preg_match('/<script[^>]*type=["\']text\/babel["\'][^>]*>(.*?)<\/script>/is', $component_code, $m)) {
        $jsx_code = $m[1];
        // Remove ReactDOM.createRoot render call — Next.js handles this
        $jsx_code = preg_replace('/ReactDOM\.createRoot\([^)]+\)\.render\([^)]+\);?\s*/i', '', $jsx_code);
        $page_content = "'use client';\nimport { useEffect, useRef, useState } from 'react';\nimport gsap from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\n" . trim($jsx_code) . "\n\nexport default function Page() {\n  return <App />;\n}\n";
    } else {
        // Fallback: wrap entire HTML as a dangerouslySetInnerHTML page
        $escaped = addslashes($component_code);
        $escaped = str_replace("\n", "\\n", $escaped);
        $page_content = "'use client';\n\nexport default function Page() {\n  return <div dangerouslySetInnerHTML={{ __html: \"" . $escaped . "\" }} />;\n}\n";
    }
    vmo_get_filesystem()->put_contents($project_dir . '/app/page.tsx', $page_content, FS_CHMOD_FILE);

    // README.md
    $readme = "# " . ($business_data['business_name'] ?? 'My Website') . "\n\nGenerated by VMO AI Website Builder — Next.js App Router Edition.\n\n## Getting Started\n\n```bash\nnpm install\nnpm run dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000) in your browser.\n\n## Deploy\n\nDeploy instantly to Vercel:\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new)\n\n## Tech Stack\n\n- **Next.js 14** (App Router)\n- **React 18**\n- **Tailwind CSS**\n- **GSAP** (ScrollTrigger animations)\n- **Framer Motion**\n- **Three.js** (3D elements)\n- **Lenis** (Smooth scroll)\n";
    vmo_get_filesystem()->put_contents($project_dir . '/README.md', $readme, FS_CHMOD_FILE);

    // .gitignore
    vmo_get_filesystem()->put_contents($project_dir . '/.gitignore', "node_modules/\n.next/\nout/\n.env*.local\n", FS_CHMOD_FILE);

    // Create ZIP
    $zip_path = $tmp_dir . '/' . $biz_name . '-nextjs.zip';
    $zip = new ZipArchive();
    if ($zip->open($zip_path, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
        wp_send_json_error(['message' => 'Failed to create ZIP archive']);
        return;
    }

    $dir_iterator = new RecursiveDirectoryIterator($project_dir, RecursiveDirectoryIterator::SKIP_DOTS);
    $iterator = new RecursiveIteratorIterator($dir_iterator, RecursiveIteratorIterator::SELF_FIRST);
    foreach ($iterator as $file) {
        $relative = substr($file->getPathname(), strlen($project_dir) + 1);
        if ($file->isDir()) {
            $zip->addEmptyDir($relative);
        } else {
            $zip->addFile($file->getPathname(), $relative);
        }
    }
    $zip->close();

    // Read ZIP and send as base64 (for AJAX download)
    $zip_data = vmo_get_filesystem()->get_contents($zip_path);
    $zip_base64 = base64_encode($zip_data); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- data encoding
    $zip_size = strlen($zip_data);

    // Cleanup temp files
    $cleanup_iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($tmp_dir, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::CHILD_FIRST
    );
    foreach ($cleanup_iterator as $file) {
        $file->isDir() ? @rmdir($file->getPathname()) : @unlink($file->getPathname());
    }
    @rmdir($tmp_dir);

    wp_send_json_success([
        'filename' => $biz_name . '-nextjs.zip',
        'data' => $zip_base64,
        'size' => $zip_size,
        'message' => 'Next.js project exported! Run npm install && npm run dev to start.',
    ]);
});

// ========================================
// CLEAR PENDING NOTIFICATION (Welcome Bonus, etc.)
// ========================================
// Clears pending notifications after they've been displayed to the user
add_action('wp_ajax_vmo_clear_pending_notification', function() {
    check_ajax_referer('vmo_builder_nonce', 'security');
    $user_id = get_current_user_id();
    if ($user_id) {
        delete_user_meta($user_id, 'vmo_pending_notification');
        wp_send_json_success(['cleared' => true]);
    } else {
        wp_send_json_error(['message' => 'Not logged in']);
    }
});

function vmo_create_plugin_tables() {
    global $wpdb;

    $charset_collate = $wpdb->get_charset_collate();

    // Only require upgrade.php if it exists (WordPress environment)
    if (defined('ABSPATH') && file_exists(ABSPATH . 'wp-admin/includes/upgrade.php')) {
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    } else {
        // Fallback: define dbDelta if not available
        if (!function_exists('dbDelta')) {
            function dbDelta($queries) {
                // Mock function for testing - in real WordPress this handles table creation
                if (defined('WP_DEBUG') && WP_DEBUG) error_log("Mock dbDelta called with: " . print_r($queries, true));
                return array();
            }
        }
    }

    $projects_table = $wpdb->prefix . 'vmo_projects';
    $history_table = $wpdb->prefix . 'vmo_project_history';
    $consultations_table = $wpdb->prefix . 'vmo_consultations';
    $approval_table = $wpdb->prefix . 'vmo_approval_queue';
    // VOICE SYSTEM ACTIVE: Phone system tables enabled
    $phone_numbers_table = $wpdb->prefix . 'vmo_phone_numbers';
    $phone_calls_table = $wpdb->prefix . 'vmo_phone_calls';
    $phone_warnings_table = $wpdb->prefix . 'vmo_phone_warnings';
    $knowledge_table = $wpdb->prefix . 'vmo_knowledge_base';
    $business_profiles_table = $wpdb->prefix . 'vmo_business_profiles';

    $sql1 = "CREATE TABLE IF NOT EXISTS $projects_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        project_title VARCHAR(255) NOT NULL,
        html MEDIUMTEXT,
        images LONGTEXT,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    ) $charset_collate;";

    // Phone Numbers Table
    $sql_phone1 = "CREATE TABLE IF NOT EXISTS $phone_numbers_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        phone_number VARCHAR(50) NOT NULL,
        twilio_sid VARCHAR(255) DEFAULT NULL,
        status VARCHAR(50) DEFAULT 'active',
        provisioned_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        released_at DATETIME DEFAULT NULL,
        settings LONGTEXT DEFAULT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        INDEX idx_user_id (user_id),
        INDEX idx_phone (phone_number),
        INDEX idx_status (status)
    ) $charset_collate;";

    // Phone Calls Table 
    $sql_phone2 = "CREATE TABLE IF NOT EXISTS $phone_calls_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        call_sid VARCHAR(255) NOT NULL,
        caller_number VARCHAR(50) DEFAULT NULL,
        direction ENUM('inbound', 'outbound') DEFAULT 'inbound',
        status VARCHAR(50) DEFAULT 'completed',
        duration INT UNSIGNED DEFAULT 0,
        recording_url VARCHAR(2083) DEFAULT NULL,
        transcript LONGTEXT DEFAULT NULL,
        summary LONGTEXT DEFAULT NULL,
        topic VARCHAR(255) DEFAULT NULL,
        ai_handled TINYINT(1) DEFAULT 0,
        contact_id BIGINT UNSIGNED DEFAULT NULL,
        tokens_used INT UNSIGNED DEFAULT 0,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_user_id (user_id),
        INDEX idx_call_sid (call_sid),
        INDEX idx_status (status),
        INDEX idx_contact (contact_id)
    ) $charset_collate;";

    $sql2 = "CREATE TABLE IF NOT EXISTS $history_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        project_id BIGINT UNSIGNED NOT NULL,
        step_number INT UNSIGNED NOT NULL,
        html MEDIUMTEXT,
        images LONGTEXT,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    ) $charset_collate;";

    // Multi-Agent Business Consultation System Table
    // NOTE: This schema is intentionally wide and JSON-friendly to support
    // the full 8-phase consultation flow with permanent persistence.
    $sql3 = "CREATE TABLE IF NOT EXISTS $consultations_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        session_id VARCHAR(255) UNIQUE NOT NULL,
        user_id BIGINT UNSIGNED DEFAULT NULL,

        -- Phase tracking
        phase TINYINT UNSIGNED DEFAULT 1,
        phase_state LONGTEXT NULL,

        -- Core persisted artifacts (JSON strings)
        conversation_log LONGTEXT NULL,
        business_summary LONGTEXT NULL,
        research_data LONGTEXT NULL,
        approved_checklist LONGTEXT NULL,
        stripe_account_id VARCHAR(255) NULL,
        products LONGTEXT NULL,
        generated_content LONGTEXT NULL,

        -- Back-compat / legacy fields used in various subsystems
        start_time DATETIME DEFAULT CURRENT_TIMESTAMP,
        end_time DATETIME DEFAULT NULL,
        status VARCHAR(50) DEFAULT 'in_progress',
        client_data LONGTEXT NULL,
        business_name VARCHAR(255) DEFAULT NULL,
        business_type VARCHAR(100) DEFAULT NULL,
        consultation_data LONGTEXT NULL,
        business_analysis LONGTEXT NULL,
        recommendations LONGTEXT NULL,
        website_data LONGTEXT NULL,

        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        INDEX idx_session_id (session_id),
        INDEX idx_user_id (user_id),
        INDEX idx_status (status),
        INDEX idx_phase (phase)
    ) $charset_collate;";

    // Approval Queue Table
    $sql4 = "CREATE TABLE IF NOT EXISTS $approval_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        type ENUM('seo','email','doc','media','other') NOT NULL DEFAULT 'other',
        title VARCHAR(255) DEFAULT NULL,
        payload LONGTEXT NULL,
        related_post_id BIGINT UNSIGNED NULL,
        status ENUM('pending','approved','rejected') NOT NULL DEFAULT 'pending',
        notes TEXT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        INDEX idx_user_id (user_id),
        INDEX idx_type (type),
        INDEX idx_status (status),
        INDEX idx_related (related_post_id)
    ) $charset_collate;";

    dbDelta($sql1);
    dbDelta($sql2);
    dbDelta($sql3);
    dbDelta($sql4);
    dbDelta($sql_phone1); // vmo_phone_numbers
    dbDelta($sql_phone2); // vmo_phone_calls

    // ── Migrate existing vmo_contacts tables that are missing columns ──
    $suppress = $wpdb->suppress_errors(true);
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $contacts_cols = $wpdb->get_col("SHOW COLUMNS FROM {$contacts_table}", 0);
    if (is_array($contacts_cols)) {
        if (!in_array('full_name', $contacts_cols, true)) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE {$contacts_table} ADD COLUMN full_name VARCHAR(200) DEFAULT NULL AFTER last_name");
        }
        if (!in_array('first_name', $contacts_cols, true)) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE {$contacts_table} ADD COLUMN first_name VARCHAR(100) DEFAULT NULL AFTER lifecycle_stage");
        }
        if (!in_array('last_name', $contacts_cols, true)) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE {$contacts_table} ADD COLUMN last_name VARCHAR(100) DEFAULT NULL AFTER first_name");
        }
    }
    $wpdb->suppress_errors($suppress);

    // ── Migrate existing vmo_phone_calls tables that are missing new columns ──
    $suppress = $wpdb->suppress_errors(true);
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $phone_cols = $wpdb->get_col("SHOW COLUMNS FROM {$phone_calls_table}", 0);
    if (is_array($phone_cols)) {
        if (!in_array('ai_handled', $phone_cols, true)) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE {$phone_calls_table} ADD COLUMN ai_handled TINYINT(1) DEFAULT 0 AFTER duration");
        }
        if (!in_array('contact_id', $phone_cols, true)) {
            $after = in_array('ai_handled', $phone_cols, true) || !in_array('ai_handled', $phone_cols, true) ? 'ai_handled' : 'duration';
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE {$phone_calls_table} ADD COLUMN contact_id BIGINT UNSIGNED DEFAULT NULL AFTER {$after}");
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE {$phone_calls_table} ADD INDEX idx_contact (contact_id)");
        }
        if (!in_array('tokens_used', $phone_cols, true)) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE {$phone_calls_table} ADD COLUMN tokens_used INT UNSIGNED DEFAULT 0 AFTER contact_id");
        }
        if (!in_array('topic', $phone_cols, true)) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE {$phone_calls_table} ADD COLUMN topic VARCHAR(255) DEFAULT NULL AFTER summary");
        }
    }
    $wpdb->suppress_errors($suppress);

    // ✅ PERSISTENT BUSINESS PROFILE TABLE (Cross-session memory)
    // Stores accumulated business knowledge that persists forever
    $sql_business_profile = "CREATE TABLE IF NOT EXISTS $business_profiles_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL UNIQUE,
        business_name VARCHAR(255) DEFAULT NULL,
        industry VARCHAR(255) DEFAULT NULL,
        founded_date DATE DEFAULT NULL,
        
        -- Accumulated business knowledge (JSON)
        business_summary LONGTEXT,  -- Executive summary of business
        uploaded_documents LONGTEXT,  -- List of all documents ever uploaded (JSON array)
        key_milestones LONGTEXT,  -- Timeline of achievements (JSON array)
        current_goals LONGTEXT,  -- Active business goals (JSON array)
        customer_personas LONGTEXT,  -- Target audience profiles (JSON)
        product_catalog LONGTEXT,  -- Products/services offered (JSON array)
        marketing_campaigns LONGTEXT,  -- Campaign history (JSON array)
        sales_data LONGTEXT,  -- Revenue, conversions, metrics (JSON)
        competitor_analysis LONGTEXT,  -- Competitive landscape (JSON)
        
        -- Consultation history metadata
        total_consultations INT UNSIGNED DEFAULT 0,
        last_consultation_date DATETIME DEFAULT NULL,
        last_consultation_topic VARCHAR(255) DEFAULT NULL,
        
        -- Extracted information (accumulated from all sessions)
        extracted_info LONGTEXT,  -- All extracted business info (JSON)
        
        -- Timestamps
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        
        INDEX idx_user_id (user_id),
        INDEX idx_last_consultation (last_consultation_date)
    ) $charset_collate;";
    
    dbDelta($sql_business_profile);

    // === CONTACT MANAGEMENT SYSTEM TABLES ===
    
    // Main contacts table
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    $sql5 = "CREATE TABLE IF NOT EXISTS $contacts_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        contact_id VARCHAR(255) UNIQUE NOT NULL,
        source ENUM('lead_generation', 'phone_call', 'manual_entry', 'website_form', 'social_media', 'email_marketing', 'referral', 'other') NOT NULL,
        primary_category VARCHAR(100) NOT NULL,
        secondary_category VARCHAR(100) DEFAULT NULL,
        confidence_score TINYINT UNSIGNED DEFAULT 0,
        priority_level ENUM('high', 'medium', 'low') DEFAULT 'medium',
        lifecycle_stage ENUM('new', 'engaged', 'nurturing', 'active_relationship', 'customer', 'dormant') DEFAULT 'new',
        first_name VARCHAR(100) DEFAULT NULL,
        last_name VARCHAR(100) DEFAULT NULL,
        full_name VARCHAR(200) DEFAULT NULL,
        email VARCHAR(255) DEFAULT NULL,
        phone VARCHAR(50) DEFAULT NULL,
        company VARCHAR(200) DEFAULT NULL,
        job_title VARCHAR(100) DEFAULT NULL,
        address TEXT DEFAULT NULL,
        city VARCHAR(100) DEFAULT NULL,
        state VARCHAR(50) DEFAULT NULL,
        zip VARCHAR(20) DEFAULT NULL,
        country VARCHAR(50) DEFAULT NULL,
        lead_score TINYINT UNSIGNED DEFAULT 0,
        engagement_level ENUM('high', 'medium', 'low') DEFAULT 'medium',
        value_potential TINYINT UNSIGNED DEFAULT 50,
        tags TEXT DEFAULT NULL,
        notes TEXT DEFAULT NULL,
        acquisition_details LONGTEXT DEFAULT NULL,
        categorization_analysis LONGTEXT DEFAULT NULL,
        engagement_strategy LONGTEXT DEFAULT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        last_contact DATETIME DEFAULT NULL,
        INDEX idx_user_id (user_id),
        INDEX idx_contact_id (contact_id),
        INDEX idx_source (source),
        INDEX idx_category (primary_category),
        INDEX idx_priority (priority_level),
        INDEX idx_lifecycle (lifecycle_stage),
        INDEX idx_email (email),
        INDEX idx_phone (phone)
    ) $charset_collate;";

    // Contact interactions table
    $interactions_table = $wpdb->prefix . 'vmo_contact_interactions';
    $sql6 = "CREATE TABLE IF NOT EXISTS $interactions_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        contact_id VARCHAR(255) NOT NULL,
        interaction_type ENUM('phone_call', 'email', 'meeting', 'website_visit', 'purchase', 'support_ticket', 'follow_up', 'automated_engagement', 'manual_note', 'other') NOT NULL,
        interaction_subtype VARCHAR(100) DEFAULT NULL,
        direction ENUM('inbound', 'outbound', 'bidirectional') DEFAULT NULL,
        subject VARCHAR(255) DEFAULT NULL,
        summary TEXT DEFAULT NULL,
        full_details LONGTEXT DEFAULT NULL,
        outcome ENUM('successful', 'needs_follow_up', 'no_response', 'converted', 'lost', 'pending') DEFAULT 'pending',
        next_action TEXT DEFAULT NULL,
        follow_up_date DATETIME DEFAULT NULL,
        duration_minutes INT DEFAULT 0,
        ai_analysis LONGTEXT DEFAULT NULL,
        satisfaction_score TINYINT DEFAULT NULL,
        conversion_value DECIMAL(10,2) DEFAULT NULL,
        related_campaign VARCHAR(255) DEFAULT NULL,
        attachments LONGTEXT DEFAULT NULL,
        metadata LONGTEXT DEFAULT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        INDEX idx_user_id (user_id),
        INDEX idx_contact_id (contact_id),
        INDEX idx_interaction_type (interaction_type),
        INDEX idx_outcome (outcome),
        INDEX idx_created_at (created_at),
        INDEX idx_follow_up_date (follow_up_date)
    ) $charset_collate;";

    // Contact purchases/transactions table
    $purchases_table = $wpdb->prefix . 'vmo_contact_purchases';
    $sql7 = "CREATE TABLE IF NOT EXISTS $purchases_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        contact_id VARCHAR(255) NOT NULL,
        transaction_id VARCHAR(255) DEFAULT NULL,
        product_service VARCHAR(255) NOT NULL,
        purchase_type ENUM('one_time', 'subscription', 'recurring', 'service', 'consultation') NOT NULL,
        amount DECIMAL(10,2) NOT NULL,
        currency VARCHAR(10) DEFAULT 'USD',
        status ENUM('completed', 'pending', 'cancelled', 'refunded') DEFAULT 'pending',
        purchase_date DATETIME NOT NULL,
        subscription_details LONGTEXT DEFAULT NULL,
        payment_method VARCHAR(100) DEFAULT NULL,
        notes TEXT DEFAULT NULL,
        ai_insights LONGTEXT DEFAULT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        INDEX idx_user_id (user_id),
        INDEX idx_contact_id (contact_id),
        INDEX idx_purchase_date (purchase_date),
        INDEX idx_status (status),
        INDEX idx_purchase_type (purchase_type)
    ) $charset_collate;";

    // Contact communication preferences table
    $preferences_table = $wpdb->prefix . 'vmo_contact_preferences';
    $sql8 = "CREATE TABLE IF NOT EXISTS $preferences_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        contact_id VARCHAR(255) NOT NULL,
        email_subscribed BOOLEAN DEFAULT TRUE,
        sms_subscribed BOOLEAN DEFAULT FALSE,
        phone_calls_allowed BOOLEAN DEFAULT TRUE,
        marketing_emails BOOLEAN DEFAULT TRUE,
        newsletter_subscribed BOOLEAN DEFAULT FALSE,
        preferred_contact_method ENUM('email', 'phone', 'sms', 'none') DEFAULT 'email',
        best_contact_time VARCHAR(100) DEFAULT NULL,
        communication_frequency ENUM('daily', 'weekly', 'monthly', 'quarterly', 'as_needed') DEFAULT 'weekly',
        language_preference VARCHAR(10) DEFAULT 'en',
        timezone VARCHAR(50) DEFAULT NULL,
        do_not_contact BOOLEAN DEFAULT FALSE,
        unsubscribe_date DATETIME DEFAULT NULL,
        preferences_updated DATETIME DEFAULT CURRENT_TIMESTAMP,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        INDEX idx_user_id (user_id),
        INDEX idx_contact_id (contact_id),
        UNIQUE KEY unique_user_contact (user_id, contact_id)
    ) $charset_collate;";

    // Voice conversation tables
    $voice_conversations_table = $wpdb->prefix . 'vmo_voice_conversations';
    $sql9 = "CREATE TABLE IF NOT EXISTS $voice_conversations_table (
        id int(11) NOT NULL AUTO_INCREMENT,
        user_id int(11) NOT NULL,
        conversation_id varchar(255) NOT NULL,
        business_info text,
        voice_id varchar(255),
        status varchar(50) DEFAULT 'active',
        started_at datetime DEFAULT CURRENT_TIMESTAMP,
        ended_at datetime NULL,
        last_activity datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        total_turns int(11) DEFAULT 0,
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY conversation_id (conversation_id),
        KEY user_id (user_id),
        KEY status (status)
    ) $charset_collate;";

    $voice_conversation_turns_table = $wpdb->prefix . 'vmo_voice_conversation_turns';
    $sql10 = "CREATE TABLE IF NOT EXISTS $voice_conversation_turns_table (
        id int(11) NOT NULL AUTO_INCREMENT,
        conversation_id varchar(255) NOT NULL,
        user_message text NOT NULL,
        ai_response text NOT NULL,
        timestamp datetime DEFAULT CURRENT_TIMESTAMP,
        audio_duration float DEFAULT 0,
        tokens_used int(11) DEFAULT 0,
        PRIMARY KEY (id),
        KEY conversation_id (conversation_id),
        KEY timestamp (timestamp)
    ) $charset_collate;";

    dbDelta($sql5);
    dbDelta($sql6);
    dbDelta($sql7);
    dbDelta($sql8);
    dbDelta($sql9);
    dbDelta($sql10);

    // === Stripe Connect Tables ===
    $connected_accounts_table = $wpdb->prefix . 'vmo_connected_accounts';
    $sql11 = "CREATE TABLE IF NOT EXISTS $connected_accounts_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        stripe_account_id VARCHAR(255) NOT NULL,
        account_type VARCHAR(50) DEFAULT 'custom',
        details_submitted TINYINT(1) DEFAULT 0,
        payouts_enabled TINYINT(1) DEFAULT 0,
        charges_enabled TINYINT(1) DEFAULT 0,
        capabilities LONGTEXT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uniq_user (user_id),
        UNIQUE KEY uniq_account (stripe_account_id),
        KEY idx_payouts (payouts_enabled)
    ) $charset_collate;";

    $store_orders_table = $wpdb->prefix . 'vmo_store_orders';
    $sql12 = "CREATE TABLE IF NOT EXISTS $store_orders_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL, -- seller/merchant (site owner)
        buyer_user_id BIGINT UNSIGNED NULL, -- WP user if logged in
        order_reference VARCHAR(255) NULL, -- optional app order id
        stripe_checkout_session_id VARCHAR(255) NOT NULL,
        stripe_payment_intent_id VARCHAR(255) NULL,
        stripe_charge_id VARCHAR(255) NULL,
        amount_total BIGINT UNSIGNED NOT NULL, -- in cents
        currency VARCHAR(10) DEFAULT 'usd',
        platform_fee_amount BIGINT UNSIGNED DEFAULT 0, -- in cents
        application_fee_id VARCHAR(255) NULL,
        receipt_url TEXT NULL,
        status ENUM('created','paid','failed','refunded') DEFAULT 'created',
        metadata LONGTEXT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        KEY idx_user (user_id),
        KEY idx_session (stripe_checkout_session_id),
        KEY idx_payment (stripe_payment_intent_id)
    ) $charset_collate;";

    dbDelta($sql11);
    dbDelta($sql12);

    // === Store Catalog & Subscriptions ===
    $products_table = $wpdb->prefix . 'vmo_store_products';
    $sql13 = "CREATE TABLE IF NOT EXISTS $products_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        name VARCHAR(255) NOT NULL,
        slug VARCHAR(255) NOT NULL,
        description LONGTEXT NULL,
        product_type ENUM('item','subscription','downloadable','service') DEFAULT 'item',
        price_cents BIGINT UNSIGNED NOT NULL DEFAULT 0,
        currency VARCHAR(10) DEFAULT 'usd',
        sku VARCHAR(100) DEFAULT NULL,
        stock_tracking TINYINT(1) DEFAULT 0,
        stock_qty INT DEFAULT 0,
        shipping_required TINYINT(1) DEFAULT 0,
        shipping_profile LONGTEXT NULL,
        download_url TEXT NULL,
        download_limit INT DEFAULT NULL,
        recurring_interval ENUM('day','week','month','year') DEFAULT NULL,
        recurring_interval_count INT DEFAULT NULL,
        images LONGTEXT NULL,
        status ENUM('draft','active','archived') DEFAULT 'active',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uniq_user_slug (user_id, slug),
        KEY idx_user (user_id),
        KEY idx_type (product_type),
        KEY idx_status (status)
    ) $charset_collate;";

    $subs_table = $wpdb->prefix . 'vmo_store_subscriptions';
    $sql14 = "CREATE TABLE IF NOT EXISTS $subs_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL, -- merchant
        buyer_user_id BIGINT UNSIGNED NULL,
        product_id BIGINT UNSIGNED NOT NULL,
        stripe_subscription_id VARCHAR(255) NOT NULL,
        status VARCHAR(50) NOT NULL,
        current_period_end DATETIME NULL,
        metadata LONGTEXT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        KEY idx_user (user_id),
        KEY idx_buyer (buyer_user_id),
        KEY idx_product (product_id),
        KEY idx_sub (stripe_subscription_id)
    ) $charset_collate;";

    dbDelta($sql13);
    dbDelta($sql14);

    // ======================================
    // MISSING TABLES - ADDED FOR 100% FUNCTIONALITY
    // ======================================
    
    // PHONE SYSTEM ENABLED: Twilio AI Agent Integration
    // NOTE: vmo_phone_numbers ($sql_phone1) and vmo_phone_calls ($sql_phone2)
    // are already created above — only define warnings and knowledge_base here.
    $phone_warnings_table = $wpdb->prefix . 'vmo_phone_warnings';
    $knowledge_base_table = $wpdb->prefix . 'vmo_knowledge_base';

    $sql17 = "CREATE TABLE IF NOT EXISTS $phone_warnings_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        warning_type VARCHAR(50) NOT NULL,
        message TEXT NOT NULL,
        severity VARCHAR(20) DEFAULT 'info',
        is_read TINYINT(1) DEFAULT 0,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        KEY idx_user (user_id),
        KEY idx_severity (severity),
        KEY idx_read (is_read),
        KEY idx_created (created_at)
    ) $charset_collate;";

    $sql18 = "CREATE TABLE IF NOT EXISTS $knowledge_base_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        source_type VARCHAR(50) NOT NULL,
        content_url VARCHAR(500) DEFAULT NULL,
        content_text LONGTEXT,
        metadata LONGTEXT,
        last_updated DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        KEY idx_user (user_id),
        KEY idx_source (source_type),
        KEY idx_updated (last_updated)
    ) $charset_collate;";

    // Manager System Tables (6 tables)
    $manager_users_table = $wpdb->prefix . 'vmo_manager_users';
    $manager_activations_table = $wpdb->prefix . 'vmo_manager_activations';
    $token_transactions_table = $wpdb->prefix . 'vmo_manager_token_transactions';
    $affiliates_table = $wpdb->prefix . 'vmo_manager_affiliates';
    $commissions_table = $wpdb->prefix . 'vmo_manager_commissions';
    $support_tickets_table = $wpdb->prefix . 'vmo_manager_support_tickets';
    
    $sql19 = "CREATE TABLE IF NOT EXISTS $manager_users_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        wp_user_id BIGINT UNSIGNED NOT NULL,
        role VARCHAR(50) DEFAULT 'manager',
        permissions LONGTEXT,
        assigned_clients TEXT,
        status VARCHAR(20) DEFAULT 'active',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        KEY idx_wp_user (wp_user_id),
        KEY idx_role (role),
        KEY idx_status (status)
    ) $charset_collate;";

    // ✅ Manager Activations Table - tracks user installations and token balances
    $sql19b = "CREATE TABLE IF NOT EXISTS $manager_activations_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        install_id VARCHAR(64) NOT NULL,
        user_id BIGINT UNSIGNED NOT NULL,
        site_url VARCHAR(500) DEFAULT NULL,
        plan_type VARCHAR(50) DEFAULT 'free',
        token_balance INT DEFAULT 0,
        tokens_used INT DEFAULT 0,
        status VARCHAR(20) DEFAULT 'active',
        activated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        expires_at DATETIME DEFAULT NULL,
        last_activity_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        metadata LONGTEXT DEFAULT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uniq_install (install_id),
        KEY idx_user (user_id),
        KEY idx_status (status),
        KEY idx_plan (plan_type),
        KEY idx_expires (expires_at)
    ) $charset_collate;";

    $sql20 = "CREATE TABLE IF NOT EXISTS $token_transactions_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        transaction_type VARCHAR(50) NOT NULL,
        tokens_amount INT NOT NULL,
        tokens_before INT DEFAULT 0,
        tokens_after INT NOT NULL,
        description TEXT,
        reference_id VARCHAR(100) DEFAULT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        KEY idx_user (user_id),
        KEY idx_type (transaction_type),
        KEY idx_created (created_at),
        KEY idx_reference (reference_id)
    ) $charset_collate;";

    $sql21 = "CREATE TABLE IF NOT EXISTS $affiliates_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        affiliate_code VARCHAR(50) UNIQUE NOT NULL,
        referral_link VARCHAR(500),
        total_referrals INT DEFAULT 0,
        total_revenue DECIMAL(10,2) DEFAULT 0.00,
        commission_rate DECIMAL(5,2) DEFAULT 10.00,
        status VARCHAR(20) DEFAULT 'active',
        payment_email VARCHAR(255),
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uniq_code (affiliate_code),
        KEY idx_user (user_id),
        KEY idx_status (status)
    ) $charset_collate;";

    $sql22 = "CREATE TABLE IF NOT EXISTS $commissions_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        affiliate_id BIGINT UNSIGNED NOT NULL,
        order_id BIGINT UNSIGNED NOT NULL,
        commission_amount DECIMAL(10,2) NOT NULL,
        commission_rate DECIMAL(5,2) NOT NULL,
        order_total DECIMAL(10,2) NOT NULL,
        status VARCHAR(20) DEFAULT 'pending',
        paid_at DATETIME DEFAULT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        KEY idx_affiliate (affiliate_id),
        KEY idx_order (order_id),
        KEY idx_status (status),
        KEY idx_created (created_at)
    ) $charset_collate;";

    $sql23 = "CREATE TABLE IF NOT EXISTS $support_tickets_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        subject VARCHAR(500) NOT NULL,
        message LONGTEXT NOT NULL,
        priority VARCHAR(20) DEFAULT 'normal',
        status VARCHAR(20) DEFAULT 'open',
        assigned_to BIGINT UNSIGNED DEFAULT NULL,
        last_reply_at DATETIME DEFAULT NULL,
        closed_at DATETIME DEFAULT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        KEY idx_user (user_id),
        KEY idx_status (status),
        KEY idx_priority (priority),
        KEY idx_assigned (assigned_to),
        KEY idx_created (created_at)
    ) $charset_collate;";

    // Email Marketing Tables (2 tables)
    $email_campaigns_table = $wpdb->prefix . 'vmo_email_campaigns';
    $email_stats_table = $wpdb->prefix . 'vmo_email_stats';
    $email_sender_identities_table = $wpdb->prefix . 'vmo_email_sender_identities';
    
    $sql24 = "CREATE TABLE IF NOT EXISTS $email_campaigns_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        campaign_name VARCHAR(255) NOT NULL,
        campaign_type VARCHAR(50) DEFAULT 'newsletter',
        subject VARCHAR(500) NOT NULL,
        body LONGTEXT NOT NULL,
        from_name VARCHAR(255),
        from_email VARCHAR(255),
        reply_to VARCHAR(255),
        segment_criteria LONGTEXT,
        status VARCHAR(20) DEFAULT 'draft',
        scheduled_at DATETIME DEFAULT NULL,
        sent_at DATETIME DEFAULT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        KEY idx_user (user_id),
        KEY idx_status (status),
        KEY idx_type (campaign_type),
        KEY idx_scheduled (scheduled_at)
    ) $charset_collate;";

    $sql25 = "CREATE TABLE IF NOT EXISTS $email_stats_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        campaign_id BIGINT UNSIGNED NOT NULL,
        contact_id BIGINT UNSIGNED NOT NULL,
        email_address VARCHAR(255) NOT NULL,
        sent TINYINT(1) DEFAULT 0,
        delivered TINYINT(1) DEFAULT 0,
        opened TINYINT(1) DEFAULT 0,
        clicked TINYINT(1) DEFAULT 0,
        bounced TINYINT(1) DEFAULT 0,
        unsubscribed TINYINT(1) DEFAULT 0,
        opened_at DATETIME DEFAULT NULL,
        clicked_at DATETIME DEFAULT NULL,
        sent_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        KEY idx_campaign (campaign_id),
        KEY idx_contact (contact_id),
        KEY idx_email (email_address),
        KEY idx_opened (opened),
        KEY idx_clicked (clicked)
    ) $charset_collate;";

    $sql25_identity = "CREATE TABLE IF NOT EXISTS $email_sender_identities_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        domain VARCHAR(255) NOT NULL,
        email_address VARCHAR(255) NOT NULL,
        email_role VARCHAR(50) DEFAULT 'marketing',
        display_name VARCHAR(255) DEFAULT NULL,
        verification_status VARCHAR(30) DEFAULT 'pending',
        dns_records LONGTEXT DEFAULT NULL,
        verification_requested_at DATETIME DEFAULT NULL,
        verified_at DATETIME DEFAULT NULL,
        last_checked_at DATETIME DEFAULT NULL,
        is_primary TINYINT(1) DEFAULT 0,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uniq_user_email (user_id, email_address),
        KEY idx_user_role (user_id, email_role),
        KEY idx_domain_status (domain, verification_status)
    ) $charset_collate;";

    // Email Signatures Table
    $email_signatures_table = $wpdb->prefix . 'vmo_email_signatures';
    $sql25_signatures = "CREATE TABLE IF NOT EXISTS $email_signatures_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        name VARCHAR(255) NOT NULL,
        designation VARCHAR(255) DEFAULT NULL,
        social_links LONGTEXT DEFAULT NULL,
        custom_links LONGTEXT DEFAULT NULL,
        logo_url VARCHAR(500) DEFAULT NULL,
        theme_color VARCHAR(20) DEFAULT '#000000',
        is_default TINYINT(1) DEFAULT 0,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        KEY idx_user (user_id)
    ) $charset_collate;";

    // Email Automations Table
    $email_automations_table = $wpdb->prefix . 'vmo_email_automations';
    $sql25_automations = "CREATE TABLE IF NOT EXISTS $email_automations_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        name VARCHAR(255) NOT NULL,
        trigger_type VARCHAR(50) NOT NULL, -- 'open', 'click', 'no_open'
        trigger_campaign_id BIGINT UNSIGNED DEFAULT NULL,
        action_type VARCHAR(50) NOT NULL, -- 'send_email'
        action_payload LONGTEXT DEFAULT NULL, -- email content or template ID
        delay_minutes INT DEFAULT 0,
        status VARCHAR(20) DEFAULT 'active',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        KEY idx_user (user_id),
        KEY idx_trigger (trigger_campaign_id)
    ) $charset_collate;";

    // Contact Groups Table
    $contact_groups_table = $wpdb->prefix . 'vmo_contact_groups';
    $sql25_groups = "CREATE TABLE IF NOT EXISTS $contact_groups_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        name VARCHAR(255) NOT NULL,
        description TEXT DEFAULT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        KEY idx_user (user_id)
    ) $charset_collate;";

    // API Usage Tracking Table
    $api_usage_table = $wpdb->prefix . 'vmo_manager_api_usage';
    
    $sql26 = "CREATE TABLE IF NOT EXISTS $api_usage_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        api_service VARCHAR(50) NOT NULL,
        api_action VARCHAR(100) NOT NULL,
        tokens_used INT DEFAULT 0,
        cost DECIMAL(10,4) DEFAULT 0.0000,
        response_time INT DEFAULT NULL,
        status VARCHAR(20),
        error_message TEXT,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        KEY idx_user (user_id),
        KEY idx_service (api_service),
        KEY idx_created (created_at),
        KEY idx_status (status)
    ) $charset_collate;";

    // Manager Emails Table
    $manager_emails_table = $wpdb->prefix . 'vmo_manager_emails';
    
    $sql27 = "CREATE TABLE IF NOT EXISTS $manager_emails_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        campaign_name VARCHAR(255) NOT NULL,
        subject VARCHAR(500) NOT NULL,
        body LONGTEXT NOT NULL,
        recipients LONGTEXT,
        status VARCHAR(20) DEFAULT 'draft',
        scheduled_at DATETIME DEFAULT NULL,
        sent_at DATETIME DEFAULT NULL,
        open_count INT DEFAULT 0,
        click_count INT DEFAULT 0,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        KEY idx_user (user_id),
        KEY idx_status (status),
        KEY idx_scheduled (scheduled_at)
    ) $charset_collate;";

    // Chat conversation history table for persistent conversations with AI coordinator
    $chat_history_table = $wpdb->prefix . 'vmo_chat_history';
    $sql28 = "CREATE TABLE IF NOT EXISTS $chat_history_table (
        id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        user_id bigint(20) UNSIGNED NOT NULL,
        message_role varchar(20) NOT NULL COMMENT 'user or assistant',
        message_content longtext NOT NULL,
        conversation_context text DEFAULT NULL COMMENT 'JSON context data',
        chat_type varchar(20) DEFAULT 'jarvis' COMMENT 'jarvis|consultant|other',
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY user_id (user_id),
        KEY created_at (created_at),
        KEY conversation_lookup (user_id, created_at),
        KEY user_chattype_created (user_id, chat_type, created_at)
    ) $charset_collate;";

    // Long-term memory table
    $memory_table = $wpdb->prefix . 'vmo_long_term_memory';
    $sql29 = "CREATE TABLE IF NOT EXISTS $memory_table (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        user_id BIGINT(20) UNSIGNED NOT NULL,
        memory_type VARCHAR(50) NOT NULL COMMENT 'vision|goal|decision|constraint|milestone|fact|note',
        memory_key VARCHAR(191) DEFAULT NULL COMMENT 'stable key for upsert (optional)',
        importance TINYINT(3) UNSIGNED DEFAULT 0 COMMENT '0-10 subjective importance',
        content LONGTEXT NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY idx_user (user_id),
        KEY idx_type (memory_type),
        UNIQUE KEY uniq_user_key (user_id, memory_key)
    ) $charset_collate;";

    // Roadmap milestones table — expanded schema for vision-driven milestone tracking
    $milestones_table = $wpdb->prefix . 'vmo_roadmap_milestones';
    $sql30 = "CREATE TABLE IF NOT EXISTS $milestones_table (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        milestone_id VARCHAR(50) DEFAULT NULL,
        roadmap_id VARCHAR(50) DEFAULT NULL,
        user_id BIGINT(20) UNSIGNED NOT NULL,
        session_id VARCHAR(100) DEFAULT NULL,
        phase_number TINYINT(2) DEFAULT 1,
        phase_name VARCHAR(100) DEFAULT 'Phase 1',
        title VARCHAR(255) NOT NULL,
        milestone_title VARCHAR(255) DEFAULT NULL,
        milestone_description TEXT,
        target_date DATE DEFAULT NULL,
        target_day INT UNSIGNED DEFAULT NULL COMMENT 'Day number from roadmap start',
        owner VARCHAR(100) DEFAULT 'jarvis',
        status VARCHAR(20) DEFAULT 'planned' COMMENT 'planned|in_progress|blocked|done|completed',
        progress TINYINT(3) UNSIGNED DEFAULT 0 COMMENT '0-100',
        progress_percentage DECIMAL(5,2) DEFAULT 0.00,
        success_criteria LONGTEXT COMMENT 'JSON array of criteria',
        kpi_targets LONGTEXT COMMENT 'JSON array of KPI targets',
        dependent_on_milestone_id VARCHAR(50) DEFAULT NULL,
        priority TINYINT(2) DEFAULT 3 COMMENT '1=critical, 5=low',
        vision_alignment TEXT COMMENT 'How this milestone aligns with the business vision',
        notes LONGTEXT,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY idx_user (user_id),
        KEY idx_status (status),
        KEY idx_target (target_date),
        KEY idx_roadmap (roadmap_id),
        KEY idx_milestone (milestone_id),
        KEY idx_session (session_id)
    ) $charset_collate;";

    // Progress summaries table
    $summaries_table = $wpdb->prefix . 'vmo_progress_summaries';
    $sql31 = "CREATE TABLE IF NOT EXISTS $summaries_table (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        user_id BIGINT(20) UNSIGNED NOT NULL,
        period VARCHAR(20) NOT NULL COMMENT 'daily|weekly',
        period_date DATE NOT NULL,
        summary LONGTEXT NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY uniq_user_period (user_id, period, period_date),
        KEY idx_user (user_id)
    ) $charset_collate;";

    // ═══════════════════════════════════════════════════════════════════════════════
    // VMO ELITE CONSULTATION SYSTEM TABLES - Structured Phase Outputs & Progress
    // ═══════════════════════════════════════════════════════════════════════════════
    
    // Phase Outputs Table - Stores structured JSON outputs per consultation phase
    // vision_profile, business_structure, market_inputs, market_research, execution_plan, etc.
    $phase_outputs_table = $wpdb->prefix . 'vmo_phase_outputs';
    $sql32 = "CREATE TABLE IF NOT EXISTS $phase_outputs_table (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        user_id BIGINT(20) UNSIGNED NOT NULL,
        session_id VARCHAR(100) NOT NULL,
        phase_number TINYINT(2) NOT NULL COMMENT '1-8 consultation phases',
        output_type VARCHAR(50) NOT NULL COMMENT 'vision_profile|business_structure|market_inputs|market_research|execution_plan|brand_assets|legal_pack|payments_infra|build_artifacts|kpi_model',
        output_version INT UNSIGNED DEFAULT 1 COMMENT 'Version number for this output type',
        output_json LONGTEXT NOT NULL COMMENT 'Structured JSON data for this phase output',
        is_active TINYINT(1) DEFAULT 1 COMMENT 'Current active version',
        is_approved TINYINT(1) DEFAULT 0 COMMENT 'User has approved this output',
        approved_at DATETIME DEFAULT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY idx_user_session (user_id, session_id),
        KEY idx_phase (phase_number),
        KEY idx_type (output_type),
        KEY idx_active (is_active),
        UNIQUE KEY uniq_session_phase_type_version (session_id, phase_number, output_type, output_version)
    ) $charset_collate;";

    // Business Execution Roadmap Table - Activity-based, non-time-bound master roadmap
    $roadmap_table = $wpdb->prefix . 'vmo_business_roadmaps';
    $sql33 = "CREATE TABLE IF NOT EXISTS $roadmap_table (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        roadmap_id VARCHAR(50) NOT NULL,
        user_id BIGINT(20) UNSIGNED NOT NULL,
        session_id VARCHAR(100) NOT NULL,
        business_vision TEXT NOT NULL COMMENT 'Ultimate business vision/goal',
        roadmap_version INT UNSIGNED DEFAULT 1,
        roadmap_data LONGTEXT NOT NULL COMMENT 'Full roadmap JSON with phases, milestones, dependencies',
        total_milestones INT UNSIGNED DEFAULT 0,
        total_activities INT UNSIGNED DEFAULT 0,
        total_duration_days INT UNSIGNED DEFAULT 0 COMMENT 'Estimated total days to vision',
        roadmap_start_date DATE NOT NULL,
        vision_target_date DATE DEFAULT NULL COMMENT 'Expected date to achieve vision',
        current_day_number INT UNSIGNED DEFAULT 1 COMMENT 'Current day since start',
        status VARCHAR(20) DEFAULT 'active' COMMENT 'active|paused|completed|archived',
        last_deep_research_date DATETIME DEFAULT NULL,
        next_deep_research_date DATETIME DEFAULT NULL,
        research_findings_count INT UNSIGNED DEFAULT 0,
        is_active TINYINT(1) DEFAULT 1,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY uniq_roadmap_id (roadmap_id),
        KEY idx_user (user_id),
        KEY idx_session (session_id),
        KEY idx_status (status)
    ) $charset_collate;";

    // Daily Activities Table - Time-bound activities derived from roadmap
    $daily_activities_table = $wpdb->prefix . 'vmo_daily_activities';
    $sql34 = "CREATE TABLE IF NOT EXISTS $daily_activities_table (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        activity_id VARCHAR(50) NOT NULL,
        roadmap_id VARCHAR(50) NOT NULL,
        milestone_id VARCHAR(50) DEFAULT NULL COMMENT 'Links to parent milestone',
        user_id BIGINT(20) UNSIGNED NOT NULL,
        day_number INT UNSIGNED NOT NULL COMMENT 'Day N from roadmap start',
        activity_date DATE NOT NULL,
        activity_title VARCHAR(255) NOT NULL,
        activity_description TEXT,
        activity_type VARCHAR(30) DEFAULT 'task' COMMENT 'task|ai_executed|user_approval|user_action|milestone_check',
        estimated_duration_minutes INT UNSIGNED DEFAULT 60,
        priority TINYINT(2) DEFAULT 3 COMMENT '1=critical, 5=low',
        status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending|in_progress|completed|delayed|skipped',
        expected_complete_by DATETIME DEFAULT NULL COMMENT 'When this should be done',
        actual_completed_at DATETIME DEFAULT NULL,
        completion_notes TEXT,
        ai_generated TINYINT(1) DEFAULT 1,
        recurring TINYINT(1) DEFAULT 0,
        recurrence_pattern VARCHAR(50) DEFAULT NULL COMMENT 'daily|weekly|monthly|custom',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY uniq_activity_id (activity_id),
        KEY idx_roadmap (roadmap_id),
        KEY idx_user (user_id),
        KEY idx_day (day_number),
        KEY idx_date (activity_date),
        KEY idx_status (status),
        KEY idx_milestone (milestone_id)
    ) $charset_collate;";

    // Progress Tracking Table - Expected vs Actual progress calculation
    $progress_tracking_table = $wpdb->prefix . 'vmo_progress_tracking';
    $sql35 = "CREATE TABLE IF NOT EXISTS $progress_tracking_table (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        user_id BIGINT(20) UNSIGNED NOT NULL,
        roadmap_id VARCHAR(50) NOT NULL,
        tracking_date DATE NOT NULL,
        day_number INT UNSIGNED NOT NULL COMMENT 'Day N from start',
        expected_activities INT UNSIGNED DEFAULT 0 COMMENT 'Activities expected complete by this day',
        actual_completed INT UNSIGNED DEFAULT 0 COMMENT 'Actually completed',
        expected_milestones INT UNSIGNED DEFAULT 0,
        actual_milestones INT UNSIGNED DEFAULT 0,
        progress_percentage DECIMAL(5,2) DEFAULT 0.00 COMMENT 'Overall % complete',
        expected_percentage DECIMAL(5,2) DEFAULT 0.00 COMMENT 'Expected % by this day',
        schedule_health VARCHAR(20) DEFAULT 'on_track' COMMENT 'ahead|on_track|behind|critical',
        days_ahead_behind INT DEFAULT 0 COMMENT 'Positive=ahead, Negative=behind',
        weekly_velocity DECIMAL(5,2) DEFAULT 0.00 COMMENT 'Activities per week average',
        ai_recommendations TEXT COMMENT 'AI suggestions based on progress',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY idx_user_roadmap (user_id, roadmap_id),
        KEY idx_date (tracking_date),
        UNIQUE KEY uniq_roadmap_day (roadmap_id, day_number)
    ) $charset_collate;";

    // KPI Table - Enhanced business KPIs with targets
    $business_kpis_table = $wpdb->prefix . 'vmo_business_kpis';
    $sql36 = "CREATE TABLE IF NOT EXISTS $business_kpis_table (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        kpi_id VARCHAR(50) NOT NULL,
        roadmap_id VARCHAR(50) NOT NULL,
        user_id BIGINT(20) UNSIGNED NOT NULL,
        kpi_category VARCHAR(50) NOT NULL COMMENT 'revenue|traffic|leads|conversions|seo|operations',
        kpi_name VARCHAR(100) NOT NULL,
        kpi_description TEXT,
        kpi_unit VARCHAR(30) DEFAULT 'number' COMMENT 'number|currency|percentage|time',
        current_value DECIMAL(15,2) DEFAULT 0.00,
        baseline_value DECIMAL(15,2) DEFAULT 0.00,
        target_value DECIMAL(15,2) DEFAULT NULL,
        target_date DATE DEFAULT NULL,
        measurement_frequency VARCHAR(20) DEFAULT 'daily' COMMENT 'daily|weekly|monthly',
        last_measured_at DATETIME DEFAULT NULL,
        trend VARCHAR(20) DEFAULT 'stable' COMMENT 'up|down|stable',
        trend_percentage DECIMAL(5,2) DEFAULT 0.00,
        status VARCHAR(20) DEFAULT 'on_track' COMMENT 'exceeding|on_track|at_risk|behind',
        is_critical TINYINT(1) DEFAULT 0,
        display_on_dashboard TINYINT(1) DEFAULT 1,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY uniq_kpi_id (kpi_id),
        KEY idx_roadmap (roadmap_id),
        KEY idx_user (user_id),
        KEY idx_category (kpi_category)
    ) $charset_collate;";

    // KPI History Table - Historical measurements for tracking
    $kpi_history_table = $wpdb->prefix . 'vmo_kpi_history';
    $sql37 = "CREATE TABLE IF NOT EXISTS $kpi_history_table (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        kpi_id VARCHAR(50) NOT NULL,
        user_id BIGINT(20) UNSIGNED NOT NULL,
        measured_value DECIMAL(15,2) NOT NULL,
        measurement_date DATE NOT NULL,
        measurement_time DATETIME NOT NULL,
        data_source VARCHAR(50) DEFAULT 'system' COMMENT 'system|manual|api|weekly_scan',
        notes TEXT,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY idx_kpi (kpi_id),
        KEY idx_user (user_id),
        KEY idx_date (measurement_date)
    ) $charset_collate;";

    // Daily Reports Table — Records what ACTUALLY happened each day vs planned activities
    // Contrasted against vmo_daily_activities to measure real progress
    $daily_reports_table = $wpdb->prefix . 'vmo_daily_reports';
    $sql38 = "CREATE TABLE IF NOT EXISTS $daily_reports_table (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        report_id VARCHAR(50) NOT NULL,
        roadmap_id VARCHAR(50) NOT NULL,
        user_id BIGINT(20) UNSIGNED NOT NULL,
        session_id VARCHAR(100) DEFAULT NULL,
        day_number INT UNSIGNED NOT NULL COMMENT 'Day N from roadmap start',
        report_date DATE NOT NULL,
        planned_activities_count INT UNSIGNED DEFAULT 0 COMMENT 'How many activities were planned for this day',
        completed_activities_count INT UNSIGNED DEFAULT 0 COMMENT 'How many were actually completed',
        completed_activity_ids LONGTEXT COMMENT 'JSON array of completed activity_ids',
        uncompleted_activity_ids LONGTEXT COMMENT 'JSON array of uncompleted activity_ids',
        carried_forward_ids LONGTEXT COMMENT 'JSON array of activity_ids pushed to next day',
        user_notes TEXT COMMENT 'User-entered notes about the day',
        blockers TEXT COMMENT 'What blocked progress',
        wins TEXT COMMENT 'What went well',
        ai_assessment TEXT COMMENT 'AI analysis of the day',
        progress_delta DECIMAL(5,2) DEFAULT 0.00 COMMENT 'Progress change this day (percentage points)',
        estimated_completion_adjustment INT DEFAULT 0 COMMENT 'Days added(+) or saved(-) to vision estimate',
        cumulative_adjustment INT DEFAULT 0 COMMENT 'Total days added/saved since start',
        day_score TINYINT(3) UNSIGNED DEFAULT 0 COMMENT '0-100 day effectiveness score',
        streak_count INT UNSIGNED DEFAULT 0 COMMENT 'Consecutive days of full completion',
        submitted_at DATETIME DEFAULT NULL COMMENT 'When user submitted the report',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY uniq_report_id (report_id),
        KEY idx_roadmap (roadmap_id),
        KEY idx_user (user_id),
        KEY idx_date (report_date),
        KEY idx_day (day_number),
        UNIQUE KEY uniq_roadmap_day (roadmap_id, day_number)
    ) $charset_collate;";

    // Deep Research Results Table — Stores findings from background scans
    $deep_research_table = $wpdb->prefix . 'vmo_deep_research_results';
    $sql39 = "CREATE TABLE IF NOT EXISTS $deep_research_table (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        research_id VARCHAR(50) NOT NULL,
        roadmap_id VARCHAR(50) NOT NULL,
        user_id BIGINT(20) UNSIGNED NOT NULL,
        research_date DATETIME NOT NULL,
        research_type VARCHAR(50) DEFAULT 'weekly_scan' COMMENT 'weekly_scan|competitor_alert|market_shift|opportunity',
        research_prompt TEXT,
        research_results LONGTEXT COMMENT 'Full JSON research output',
        key_findings TEXT COMMENT 'Executive summary',
        action_items LONGTEXT COMMENT 'JSON array of recommended actions',
        kpi_impacts LONGTEXT COMMENT 'JSON array of KPI assessment',
        roadmap_updates_suggested LONGTEXT COMMENT 'JSON array of roadmap adjustments',
        applied_to_roadmap TINYINT(1) DEFAULT 0 COMMENT 'Whether user approved and applied changes',
        applied_at DATETIME DEFAULT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY uniq_research_id (research_id),
        KEY idx_roadmap (roadmap_id),
        KEY idx_user (user_id),
        KEY idx_date (research_date),
        KEY idx_applied (applied_to_roadmap)
    ) $charset_collate;";

    // Execute all new table creation
    // PHONE SYSTEM: phone_numbers & phone_calls already created via $sql_phone1/$sql_phone2 above
    dbDelta($sql17); // phone_warnings
    dbDelta($sql18); // knowledge_base
    dbDelta($sql19); // manager_users
    dbDelta($sql19b); // manager_activations (install_id, token_balance)
    dbDelta($sql20); // token_transactions
    dbDelta($sql21); // affiliates
    dbDelta($sql22); // commissions
    dbDelta($sql23); // support_tickets
    dbDelta($sql24); // email_campaigns
    dbDelta($sql25); // email_stats
    dbDelta($sql25_identity); // email sender identities
    dbDelta($sql25_signatures); // email signatures
    dbDelta($sql25_automations); // email automations
    dbDelta($sql25_groups); // contact groups
    dbDelta($sql26); // api_usage
    dbDelta($sql27); // manager_emails
    dbDelta($sql28); // chat_history
    dbDelta($sql29); // long_term_memory
    dbDelta($sql30); // roadmap_milestones
    dbDelta($sql31); // progress_summaries
    
    // VMO Elite Consultation System Tables
    dbDelta($sql32); // phase_outputs - structured outputs per phase
    dbDelta($sql33); // business_roadmaps - activity-based master roadmap
    dbDelta($sql34); // daily_activities - time-bound activities from roadmap
    dbDelta($sql35); // progress_tracking - expected vs actual progress
    dbDelta($sql36); // business_kpis - enhanced KPI tracking
    dbDelta($sql37); // kpi_history - historical KPI measurements
    dbDelta($sql38); // daily_reports - daily outcome tracking vs planned activities
    dbDelta($sql39); // deep_research_results - background scan findings

    // ======================================
    // AI PHONE SYSTEM — POST-CALL INTELLIGENCE TABLES
    // ======================================

    // Call Reports — Structured post-call report for every AI-handled call
    $call_reports_table = $wpdb->prefix . 'vmo_call_reports';
    $sql_cr = "CREATE TABLE IF NOT EXISTS $call_reports_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        call_sid VARCHAR(255) NOT NULL,
        contact_id BIGINT UNSIGNED DEFAULT NULL,
        report_type ENUM('inbound','outbound','missed') DEFAULT 'inbound',
        caller_name VARCHAR(200) DEFAULT NULL,
        caller_phone VARCHAR(50) DEFAULT NULL,
        caller_email VARCHAR(255) DEFAULT NULL,
        caller_intent VARCHAR(255) DEFAULT NULL,
        call_sentiment ENUM('positive','neutral','negative','frustrated') DEFAULT 'neutral',
        urgency ENUM('low','medium','high','critical') DEFAULT 'medium',
        crm_category ENUM('lead','customer','support','inquiry','vendor','spam') DEFAULT 'lead',
        summary_text LONGTEXT DEFAULT NULL,
        actions_taken LONGTEXT DEFAULT NULL COMMENT 'JSON: what the AI handled during the call',
        actions_required LONGTEXT DEFAULT NULL COMMENT 'JSON: what the user needs to do',
        actions_recommended LONGTEXT DEFAULT NULL COMMENT 'JSON: optional improvements',
        promises_made LONGTEXT DEFAULT NULL COMMENT 'JSON: what AI promised to send/do',
        fulfillment_status ENUM('none','pending','partial','complete','failed') DEFAULT 'none',
        fulfillment_log LONGTEXT DEFAULT NULL COMMENT 'JSON: log of fulfillment attempts',
        products_discussed LONGTEXT DEFAULT NULL COMMENT 'JSON: products/services discussed',
        recording_url VARCHAR(2083) DEFAULT NULL,
        call_duration INT UNSIGNED DEFAULT 0,
        ai_confidence TINYINT UNSIGNED DEFAULT 80 COMMENT 'AI confidence in report accuracy 0-100',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        INDEX idx_user_id (user_id),
        INDEX idx_call_sid (call_sid),
        INDEX idx_contact (contact_id),
        INDEX idx_crm_cat (crm_category),
        INDEX idx_fulfillment (fulfillment_status),
        INDEX idx_created (created_at)
    ) $charset_collate;";

    // Action Items — Checklists with deadlines for user follow-up
    $action_items_table = $wpdb->prefix . 'vmo_action_items';
    $sql_ai = "CREATE TABLE IF NOT EXISTS $action_items_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        call_sid VARCHAR(255) DEFAULT NULL,
        report_id BIGINT UNSIGNED DEFAULT NULL,
        contact_id BIGINT UNSIGNED DEFAULT NULL,
        title VARCHAR(500) NOT NULL,
        description TEXT DEFAULT NULL,
        priority ENUM('low','medium','high','critical') DEFAULT 'medium',
        category ENUM('follow_up','send_info','callback','order','booking','support','custom') DEFAULT 'follow_up',
        deadline DATETIME DEFAULT NULL,
        status ENUM('pending','in_progress','completed','overdue','cancelled') DEFAULT 'pending',
        completed_at DATETIME DEFAULT NULL,
        completed_by BIGINT UNSIGNED DEFAULT NULL,
        notes TEXT DEFAULT NULL,
        auto_generated TINYINT(1) DEFAULT 1,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        INDEX idx_user_id (user_id),
        INDEX idx_call_sid (call_sid),
        INDEX idx_report (report_id),
        INDEX idx_status (status),
        INDEX idx_deadline (deadline),
        INDEX idx_priority (priority),
        INDEX idx_created (created_at)
    ) $charset_collate;";

    // Fulfillment Actions Log — Tracks SMS/email fulfillment attempts
    $fulfillment_log_table = $wpdb->prefix . 'vmo_fulfillment_log';
    $sql_fl = "CREATE TABLE IF NOT EXISTS $fulfillment_log_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        call_sid VARCHAR(255) NOT NULL,
        report_id BIGINT UNSIGNED DEFAULT NULL,
        action_type ENUM('sms','email','both') NOT NULL,
        recipient_phone VARCHAR(50) DEFAULT NULL,
        recipient_email VARCHAR(255) DEFAULT NULL,
        subject VARCHAR(500) DEFAULT NULL,
        message_body LONGTEXT NOT NULL,
        data_sources LONGTEXT DEFAULT NULL COMMENT 'JSON: what data was used to compose message',
        status ENUM('sent','failed','skipped') DEFAULT 'sent',
        error_message TEXT DEFAULT NULL,
        twilio_message_sid VARCHAR(255) DEFAULT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_user_id (user_id),
        INDEX idx_call_sid (call_sid),
        INDEX idx_status (status),
        INDEX idx_created (created_at)
    ) $charset_collate;";

    // Daily/Periodic Reports — Aggregated business activity reports
    $periodic_reports_table = $wpdb->prefix . 'vmo_periodic_reports';
    $sql_pr = "CREATE TABLE IF NOT EXISTS $periodic_reports_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        report_period ENUM('daily','weekly','monthly','quarterly','yearly','custom') NOT NULL,
        period_start DATE NOT NULL,
        period_end DATE NOT NULL,
        fiscal_year_start DATE DEFAULT NULL COMMENT 'User-configurable fiscal year',
        cover_page LONGTEXT DEFAULT NULL COMMENT 'JSON: executive summary for cover page',
        summary_data LONGTEXT NOT NULL COMMENT 'JSON: aggregated metrics and KPIs',
        detail_data LONGTEXT DEFAULT NULL COMMENT 'JSON: per-call breakdown, charts data',
        roadmap_comparison LONGTEXT DEFAULT NULL COMMENT 'JSON: expected vs actual from roadmap',
        total_calls INT UNSIGNED DEFAULT 0,
        total_leads INT UNSIGNED DEFAULT 0,
        total_customers INT UNSIGNED DEFAULT 0,
        total_action_items INT UNSIGNED DEFAULT 0,
        action_items_completed INT UNSIGNED DEFAULT 0,
        fulfillments_sent INT UNSIGNED DEFAULT 0,
        ai_insights LONGTEXT DEFAULT NULL COMMENT 'GPT-generated executive insights',
        generated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_user_id (user_id),
        INDEX idx_period (report_period),
        INDEX idx_dates (period_start, period_end),
        INDEX idx_generated (generated_at),
        UNIQUE KEY uniq_user_period (user_id, report_period, period_start, period_end)
    ) $charset_collate;";

    dbDelta($sql_cr);  // call_reports
    dbDelta($sql_ai);  // action_items
    dbDelta($sql_fl);  // fulfillment_log
    dbDelta($sql_pr);  // periodic_reports

    // ======================================
    // LICENSING & USAGE TRACKING TABLES
    // Required by class-vmo-master-client-manager.php for all client-facing endpoints
    // ======================================

    $licenses_table = $wpdb->prefix . 'vmo_licenses';
    $sql_licenses = "CREATE TABLE IF NOT EXISTS $licenses_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        license_key VARCHAR(64) NOT NULL,
        status ENUM('active','suspended','expired','revoked') DEFAULT 'active',
        plan VARCHAR(50) NOT NULL DEFAULT 'starter',
        activations LONGTEXT DEFAULT NULL COMMENT 'JSON: array of {installation_id, site_url, site_name, activated_at}',
        max_activations INT UNSIGNED DEFAULT 1,
        site_stats LONGTEXT DEFAULT NULL COMMENT 'JSON: last synced site statistics',
        last_sync DATETIME DEFAULT NULL,
        expires_at DATETIME DEFAULT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uniq_license_key (license_key),
        KEY idx_user_id (user_id),
        KEY idx_status (status),
        KEY idx_plan (plan)
    ) $charset_collate;";

    $usage_logs_table = $wpdb->prefix . 'vmo_usage_logs';
    $sql_usage_logs = "CREATE TABLE IF NOT EXISTS $usage_logs_table (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        license_id BIGINT UNSIGNED NOT NULL,
        service VARCHAR(100) NOT NULL COMMENT 'e.g. openai, elevenlabs, stability',
        action VARCHAR(200) NOT NULL COMMENT 'e.g. chat_completion, image_generation',
        tokens_used INT UNSIGNED DEFAULT 0,
        cost_cents INT UNSIGNED DEFAULT 0,
        metadata LONGTEXT DEFAULT NULL COMMENT 'JSON: additional context',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        KEY idx_user_id (user_id),
        KEY idx_license_id (license_id),
        KEY idx_service (service),
        KEY idx_created (created_at)
    ) $charset_collate;";

    dbDelta($sql_licenses);    // vmo_licenses — master/client license validation
    dbDelta($sql_usage_logs);  // vmo_usage_logs — API usage tracking per license

    // CRITICAL FIX: Check and migrate chat_history table if it has old column names
    $chat_history_table = $wpdb->prefix . 'vmo_chat_history';
    
    // Only check columns if we have a real database connection
    if (method_exists($wpdb, 'get_col')) {
        $suppress_chat = $wpdb->suppress_errors(true);
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        $columns = $wpdb->get_col("DESC $chat_history_table", 0);
        
        if ($columns && is_array($columns)) {
            $has_message_role = in_array('message_role', $columns);
            $has_message_content = in_array('message_content', $columns);
            $has_old_role = in_array('role', $columns);
            $has_old_content = in_array('content', $columns);
            $has_chat_type = in_array('chat_type', $columns);
        
        // If table has old column names, migrate them
        if (!$has_message_role && $has_old_role) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE $chat_history_table CHANGE COLUMN `role` `message_role` varchar(20) NOT NULL");
            if (defined('WP_DEBUG') && WP_DEBUG) error_log('✅ VMO: Migrated column role → message_role');
        }
        
        if (!$has_message_content && $has_old_content) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE $chat_history_table CHANGE COLUMN `content` `message_content` longtext NOT NULL");
            if (defined('WP_DEBUG') && WP_DEBUG) error_log('✅ VMO: Migrated column content → message_content');
        }
        
        // Add missing columns if they don't exist
        if (!$has_message_role && !$has_old_role) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE $chat_history_table ADD COLUMN `message_role` varchar(20) NOT NULL DEFAULT 'user' AFTER `user_id`");
            if (defined('WP_DEBUG') && WP_DEBUG) error_log('✅ VMO: Added column message_role');
        }
        
        if (!$has_message_content && !$has_old_content) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE $chat_history_table ADD COLUMN `message_content` longtext NOT NULL AFTER `message_role`");
            if (defined('WP_DEBUG') && WP_DEBUG) error_log('✅ VMO: Added column message_content');
        }
        
        if (!in_array('conversation_context', $columns)) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE $chat_history_table ADD COLUMN `conversation_context` text DEFAULT NULL AFTER `message_content`");
            if (defined('WP_DEBUG') && WP_DEBUG) error_log('✅ VMO: Added column conversation_context');
        }
        if (!$has_chat_type) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE $chat_history_table ADD COLUMN `chat_type` varchar(20) DEFAULT 'jarvis' AFTER `conversation_context`");
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $wpdb->query("ALTER TABLE $chat_history_table ADD KEY `user_chattype_created` (user_id, chat_type, created_at)");
            if (defined('WP_DEBUG') && WP_DEBUG) error_log('✅ VMO: Added column chat_type and index user_chattype_created');
        }
        }
        $wpdb->suppress_errors($suppress_chat);
    }

    // Create consultation database tables
    if (class_exists('VMO_Consultation_Database')) {
        $consultation_db = new VMO_Consultation_Database();
        $consultation_db->create_consultation_tables();
    }

    // Create enhanced business phone system tables (disabled - voice system removed)
    // Business phone system was removed in no-voice version
    if (false && class_exists('VMO_Enhanced_Business_Phone')) {
        $business_phone = new VMO_Enhanced_Business_Phone();
        $business_phone->create_phone_system_tables();
    }

    // Create phone/voice/knowledge base tables if helper classes exist
    if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-phone-manager.php')) {
        require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-phone-manager.php';
        // Enhanced phone system + Twilio client
        if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-twilio-enhanced-client.php')) {
            require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-twilio-enhanced-client.php';
        }
        if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-enhanced-business-phone.php')) {
            require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-enhanced-business-phone.php';
        }
        if (class_exists('VMO_Phone_Manager')) {
            VMO_Phone_Manager::create_tables();
        }
    }
    
    // VOICE SYSTEM REMOVED: Voice integration disabled for No Voice version
    
    // Initialize phone call handler
    if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-phone-call-handler.php')) {
        require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-phone-call-handler.php';
    }
    
    // Initialize real-time call management system
    if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-realtime-call-manager.php')) {
        require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-realtime-call-manager.php';
    }
    
    if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-voice-processor.php')) {
        require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-voice-processor.php';
        if (class_exists('VMO_Voice_Processor')) {
            VMO_Voice_Processor::create_knowledge_table();
        }
    }
    
    // NEW: Voice Conversation Handler for v8.0 Voice System
    if (file_exists(VMO_AI_BUILDER_DIR . 'includes/vmo-voice-conversation-handler.php')) {
        require_once VMO_AI_BUILDER_DIR . 'includes/vmo-voice-conversation-handler.php';
    }
    
    // Contact Handler for pitch deck and forms
    if (file_exists(VMO_AI_BUILDER_DIR . 'includes/vmo-contact-handler.php')) {
        require_once VMO_AI_BUILDER_DIR . 'includes/vmo-contact-handler.php';
    }
    
    // Create admin tables
    if (function_exists('vmo_create_admin_tables')) {
        vmo_create_admin_tables();
    }
}

// ======================================
// LICENSE KEY GENERATION — SHARED HELPER
// Used by QuickLaunch, Subscription Manager, and any checkout flow
// that needs to issue a license key for master/client communication.
// ======================================

/**
 * Generate and store a VMO license key for a user.
 *
 * Inserts a row into wp_vmo_licenses and stores the key as user meta.
 * If the user already has an active license, returns the existing key.
 *
 * @param int    $user_id  WordPress user ID.
 * @param string $plan     Plan slug (tester|starter|pro|elite|enterprise).
 * @param int    $max_act  Maximum number of site activations allowed.
 * @return string|WP_Error  The license key on success, WP_Error on failure.
 */
function vmo_generate_license_key( $user_id, $plan = 'starter', $max_act = 1 ) {
    global $wpdb;

    $user_id = absint( $user_id );
    if ( ! $user_id ) {
        return new WP_Error( 'invalid_user', 'A valid user ID is required.' );
    }

    $table = $wpdb->prefix . 'vmo_licenses';

    // Check if user already has an active license
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $existing = $wpdb->get_var( $wpdb->prepare(
        "SELECT license_key FROM $table WHERE user_id = %d AND status = 'active' LIMIT 1",
        $user_id
    ) );

    if ( $existing ) {
        // Update the plan if it changed (upgrade)
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        $wpdb->update(
            $table,
            [ 'plan' => sanitize_text_field( $plan ), 'updated_at' => current_time( 'mysql', true ) ],
            [ 'user_id' => $user_id, 'license_key' => $existing ],
            [ '%s', '%s' ],
            [ '%d', '%s' ]
        );
        update_user_meta( $user_id, 'vmo_license_key', $existing );
        return $existing;
    }

    // Generate a unique license key: vmo_lic_ + 32 random alphanumeric chars
    $key = 'vmo_lic_' . wp_generate_password( 32, false, false );

    // Ensure uniqueness (extremely unlikely collision but safety first)
    $attempts = 0;
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    while ( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $table WHERE license_key = %s", $key ) ) > 0 && $attempts < 5 ) {
        $key = 'vmo_lic_' . wp_generate_password( 32, false, false );
        $attempts++;
    }

    // Max activations based on plan tier
    $plan_max = [
        'tester'     => 1,
        'starter'    => 1,
        'pro'        => 3,
        'elite'      => 5,
        'enterprise' => 10,
    ];
    $max_activations = $plan_max[ $plan ] ?? $max_act;

    $inserted = $wpdb->insert(
        $table,
        [
            'user_id'         => $user_id,
            'license_key'     => $key,
            'status'          => 'active',
            'plan'            => sanitize_text_field( $plan ),
            'activations'     => wp_json_encode( [] ),
            'max_activations' => $max_activations,
            'site_stats'      => null,
            'last_sync'       => null,
            'expires_at'      => null, // Subscription-based, no fixed expiry
            'created_at'      => current_time( 'mysql', true ),
            'updated_at'      => current_time( 'mysql', true ),
        ],
        [ '%d', '%s', '%s', '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s' ]
    );

    if ( false === $inserted ) {
        error_log( 'VMO License: Failed to insert license for user ' . $user_id . ': ' . $wpdb->last_error );
        return new WP_Error( 'db_error', 'Failed to create license key.' );
    }

    // Store as user meta for quick lookups
    update_user_meta( $user_id, 'vmo_license_key', $key );

    if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
        error_log( '✅ VMO: License key generated for user ' . $user_id . ' (plan: ' . $plan . ')' );
    }

    return $key;
}

// === Core Includes ===
// Only require WordPress admin files if they exist (WordPress environment)
if (defined('ABSPATH') && file_exists(ABSPATH . 'wp-admin/includes/file.php')) {
    require_once ABSPATH . 'wp-admin/includes/file.php';
    require_once ABSPATH . 'wp-admin/includes/media.php';
    require_once ABSPATH . 'wp-admin/includes/image.php';
}
// Load Stripe SDK and Connect manager only when payments are enabled AND all files exist
$stripe_init_file = VMO_AI_BUILDER_DIR . 'stripe-sdk/init.php';
$stripe_exception_file = VMO_AI_BUILDER_DIR . 'stripe-sdk/lib/Exception/ExceptionInterface.php';

if (defined('VMO_PAYMENTS_ENABLED') && VMO_PAYMENTS_ENABLED && 
    file_exists($stripe_init_file) && 
    file_exists($stripe_exception_file) && 
    is_readable($stripe_init_file) && 
    is_readable($stripe_exception_file)) {
    
    try {
        // Load Stripe SDK only if not already loaded (prevent conflicts with other plugins)
        if (!class_exists('Stripe\\Stripe')) {
            require_once $stripe_init_file;
            
            // Initialize Stripe with API key if available
            $stripe_secret_key = get_option('vmo_stripe_secret_key', '');
            if (!empty($stripe_secret_key) && class_exists('Stripe\\Stripe')) {
                try {
                    \Stripe\Stripe::setApiKey($stripe_secret_key);
                    if (defined('WP_DEBUG') && WP_DEBUG) {
                        error_log('✅ VMO: Stripe SDK loaded and initialized successfully');
                    }
                } catch (Exception $e) {
                    if (defined('WP_DEBUG') && WP_DEBUG) {
                        error_log('⚠️ VMO: Stripe API key initialization failed: ' . $e->getMessage());
                    }
                }
            }
        } else {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('ℹ️ VMO: Stripe SDK already loaded (possibly by another plugin)');
            }
        }
    } catch (Exception $e) {
        // Log the error but don't disable payments - allow user to fix config
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('❌ VMO: Stripe SDK loading failed: ' . $e->getMessage());
        }
    } catch (Error $e) {
        // Catch PHP 7+ errors (like namespace issues)
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('❌ VMO: Stripe SDK fatal error: ' . $e->getMessage());
        }
    }
} else {
    // Log why payments are not loading
    if (defined('VMO_PAYMENTS_ENABLED') && VMO_PAYMENTS_ENABLED) {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            if (!file_exists($stripe_init_file)) {
                error_log('❌ VMO: Stripe SDK init.php not found at: ' . $stripe_init_file);
            } elseif (!is_readable($stripe_init_file)) {
                if (defined('WP_DEBUG') && WP_DEBUG) error_log('❌ VMO: Stripe SDK init.php not readable at: ' . $stripe_init_file);
            }
        }
    }
}

// Core functionality includes with file existence validation to prevent fatal errors
$core_includes = array(
    'includes/generate-content.php',
    'includes/media-handler.php', 
    'includes/token-manager.php',
    'includes/undo-handler.php',
    'includes/save-project.php',
    'includes/save-generated-page.php',
    'includes/load-project.php'
);

foreach ($core_includes as $include_file) {
    $full_path = VMO_AI_BUILDER_DIR . $include_file;
    if (file_exists($full_path) && is_readable($full_path)) {
        try {
            require_once $full_path;
        } catch (Exception $e) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('VMO: Failed to load ' . $include_file . ': ' . $e->getMessage());
            }
        }
    } else {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('VMO: Core include file missing or unreadable: ' . $full_path);
        }
    }
}
// Register checkout, webhook, and Connect routes only when payments are enabled AND Stripe SDK exists
if (defined('VMO_PAYMENTS_ENABLED') && VMO_PAYMENTS_ENABLED && file_exists(VMO_AI_BUILDER_DIR . 'stripe-sdk/init.php')) {
    if (file_exists(VMO_AI_BUILDER_DIR . 'includes/create-checkout.php')) {
        require_once VMO_AI_BUILDER_DIR . 'includes/create-checkout.php';
    }
    if (file_exists(VMO_AI_BUILDER_DIR . 'includes/stripe-webhook.php')) {
        require_once VMO_AI_BUILDER_DIR . 'includes/stripe-webhook.php';
    }
    if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-stripe-connect.php')) {
        require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-stripe-connect.php';
        if (class_exists('VMO_Stripe_Connect')) {
            // Initialize REST routes
            add_action('rest_api_init', function() { new VMO_Stripe_Connect(); });
        }
    }

    // Abandoned Cart Detection & Recovery
    if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-abandoned-cart.php')) {
        require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-abandoned-cart.php';
        if (class_exists('VMO_Abandoned_Cart')) {
            VMO_Abandoned_Cart::instance();
        }
    }

    // ✅ VMO Tax Calculator v1.0.0 - Comprehensive tax estimation (like Stripe Tax)
    if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-tax-calculator.php')) {
        require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-tax-calculator.php';
        if (class_exists('VMO_Tax_Calculator')) {
            add_action('rest_api_init', function() { new VMO_Tax_Calculator(); });
        }
    }

}

// ✅ VMO QuickLaunch Hosting v1.0.0 - One-click server + domain + email provisioning
// Loaded OUTSIDE the Stripe conditional — QuickLaunch REST routes (including validate-keys)
// must always register. Individual handlers check for their own keys internally.
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-quicklaunch.php')) {
    require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-quicklaunch.php';
    if (class_exists('VMO_QuickLaunch')) {
        VMO_QuickLaunch::instance();
    }
}

// Additional includes with file existence validation
$additional_includes = array(
    'includes/image-upload.php',
    'includes/media-upload-handler.php',
    'includes/vmo_upload_device_image.php', 
    'includes/generate-image.php',
    'includes/vmo_admin_tables.php',
    'includes/business-consultation-handler.php',
    'includes/class-vmo-conversation-manager.php',
    'includes/class-vmo-template-handler.php',
    'includes/class-vmo-media-integration-agent.php',
    'includes/vmo-media-integration-hooks.php',
    'includes/consultation-profile.php',
    'includes/approval-queue.php'
);

foreach ($additional_includes as $include_file) {
    $full_path = VMO_AI_BUILDER_DIR . $include_file;
    if (file_exists($full_path) && is_readable($full_path)) {
        try {
            require_once $full_path;
        } catch (Exception $e) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('VMO: Failed to load ' . $include_file . ': ' . $e->getMessage());
            }
        }
    } else {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('VMO: Include file missing or unreadable: ' . $full_path);
        }
    }
}

// Verify critical DB tables (voice conversations) exist even if activation was skipped
add_action('admin_init', function(){
    global $wpdb;
    $voice_conversations_table = $wpdb->prefix . 'vmo_voice_conversations';
    $voice_turns_table = $wpdb->prefix . 'vmo_voice_conversation_turns';
    $missing = false;
    $exists1 = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($voice_conversations_table)));
    $exists2 = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($voice_turns_table)));
    if (!$exists1 || !$exists2) { $missing = true; }
    if ($missing) {
        try { vmo_create_plugin_tables(); } catch (Throwable $e) { /* log silently */ }
    }
});

// Inject approved SEO into wp_head when viewing live pages
add_action('wp_head', function(){
    if (!is_singular()) return;
    $post_id = get_the_ID();
    $title = get_post_meta($post_id, 'vmo_seo_title', true);
    $desc  = get_post_meta($post_id, 'vmo_seo_description', true);
    $json  = get_post_meta($post_id, 'vmo_seo_json_ld', true);
    if ($title) echo "\n<!-- VMO SEO -->\n<title>" . esc_html($title) . "</title>\n";
    if ($desc)  echo '<meta name="description" content="' . esc_attr($desc) . '">\n';
    if ($json)  echo '<script type="application/ld+json">' . $json . '</script>\n';
    // Open Graph / Twitter Cards
    $og_title = get_post_meta($post_id, 'vmo_og_title', true);
    $og_desc  = get_post_meta($post_id, 'vmo_og_description', true);
    if ($og_title) echo '<meta property="og:title" content="' . esc_attr($og_title) . '">\n';
    if ($og_desc)  echo '<meta property="og:description" content="' . esc_attr($og_desc) . '">\n';
    if ($og_title) echo '<meta name="twitter:title" content="' . esc_attr($og_title) . '">\n';
    if ($og_desc)  echo '<meta name="twitter:description" content="' . esc_attr($og_desc) . '">\n';
    // Canonical
    $canonical = get_post_meta($post_id, 'vmo_seo_canonical', true);
    if ($canonical) echo '<link rel="canonical" href="' . esc_url($canonical) . '">\n';
}, 1);

// Apply alt text overrides into content for images
add_filter('the_content', function($content){
    if (!is_singular()) return $content;
    $post_id = get_the_ID();
    $overrides = get_post_meta($post_id, 'vmo_alt_text_overrides', true);
    if (!$overrides || !is_array($overrides)) return $content;
    // Simple replace by src match
    foreach ($overrides as $o) {
        if (!empty($o['src']) && !empty($o['alt'])) {
            $pattern = '#<img([^>]+src=["\\\']' . preg_quote($o['src'], '#') . '["\\\'][^>]*)>#i';
            $content = preg_replace_callback($pattern, function($m) use ($o){
                $tag = $m[0];
                if (stripos($tag, 'alt=') !== false) {
                    $tag = preg_replace('/alt=["\\\'][^"\\\']*["\\\']/', 'alt="'.esc_attr($o['alt']).'"', $tag);
                } else {
                    $tag = str_replace('<img', '<img alt="'.esc_attr($o['alt']).'"', $tag);
                }
                return $tag;
            }, $content);
        }
    }
    return $content;
}, 12);

// VOICE SYSTEM REMOVED: Enhanced Business Phone System disabled
// require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-twilio-enhanced-client.php';
// require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-enhanced-voice-processor.php';
// require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-enhanced-business-phone.php';

// ⚡ PERFORMANCE: Only load heavy subsystem modules when VMO context is active.
// Skip on non-VMO admin pages (Posts, Media, Users, etc.) to avoid ~30 unnecessary require_once calls.
$vmo_needs_subsystems = (
    wp_doing_ajax()              // AJAX requests need all handlers available
    || wp_doing_cron()           // Cron jobs may invoke subsystem functions
    || !is_admin()               // Frontend/REST requests need subsystems
    || (isset($_GET['page']) && strpos($_GET['page'], 'vmo') !== false)  // VMO admin pages
    || (isset($_GET['vmo_twilio']))  // Twilio webhook callbacks
);

if ($vmo_needs_subsystems) {

// NEW: Twilio REST API Manager (No SDK Required - Works on ANY host)
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/vmo-twilio-database.php')) require_once VMO_AI_BUILDER_DIR . 'includes/vmo-twilio-database.php';
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-twilio-manager-rest.php')) {
    require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-twilio-manager-rest.php';
} else {
    if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: Twilio REST Manager missing - phone features disabled');
}
// Twilio admin settings are master-only and are not loaded in the client build.

// Email marketing engine (AI-generated leads + 4-option campaigns)
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-email-identity-manager.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-email-identity-manager.php';
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-email-marketing.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-email-marketing.php';
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-email-compliance.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-email-compliance.php';
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-dns-detector.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-dns-detector.php';

// Turnkey Business Blueprints (pre-configured business setups VMO can build and manage)
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-turnkey-business-blueprints.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-turnkey-business-blueprints.php';

// Sora Video Generator (AI video generation for hero backgrounds, product videos, 360° spin)
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-sora-video-generator.php')) {
    require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-sora-video-generator.php';
    if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: Sora Video Generator loaded successfully');
}

// Persistent project media queue for background image/video completion
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-project-media-queue.php')) {
    require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-project-media-queue.php';
}

// Animated Overlays System (GPT Image 1.5 transparent overlays - clouds, smoke, particles, etc.)
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-animated-overlays.php')) {
    require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-animated-overlays.php';
    if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: Animated Overlays System loaded successfully');
}

// Legacy cloud email provisioning and inbound ingestion removed.

// Usage calculator for smart flexible allocation
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-usage-calculator.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-usage-calculator.php';

// Legal Document Generator (Privacy Policy, Terms of Service, etc.)
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-legal.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-legal.php';

// OpenAI TTS HD Streaming via REST API (No API key exposure on frontend)
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-openai-tts-rest.php')) {
    require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-openai-tts-rest.php';
    if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: OpenAI TTS REST API loaded successfully');
} else {
    if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: OpenAI TTS file missing - voice features will not work');
}

if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-greeting-audio.php')) {
    require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-greeting-audio.php';
    VMO_Greeting_Audio_Manager::get_instance();
} else {
    if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: Greeting audio manager missing - consultation greeting asset not cached');
}

// Optional legacy Manager classes (only load if present)
foreach ([
    'includes/class-vmo-manager-admin.php',
    'includes/class-vmo-manager-affiliates.php',
    'includes/class-vmo-manager-api.php',
    'includes/class-vmo-manager-core.php',
    'includes/class-vmo-manager-database.php',
    'includes/class-vmo-manager-emails.php',
    'includes/class-vmo-manager-stripe.php',
] as $maybe_include) {
    $path = VMO_AI_BUILDER_DIR . $maybe_include;
    if (file_exists($path)) {
        // Skip Stripe manager when payments are disabled
        if (basename($path) === 'class-vmo-manager-stripe.php' && (!defined('VMO_PAYMENTS_ENABLED') || !VMO_PAYMENTS_ENABLED)) {
            // do not load
        } else {
            require_once $path;
        }
    }
}

// Ensure Email + Manager APIs are actually initialized (some classes are included but not instantiated)
add_action('plugins_loaded', function() {
    if (class_exists('VMO_Manager_API') && empty($GLOBALS['vmo_manager_api_instance'])) {
        $GLOBALS['vmo_manager_api_instance'] = new VMO_Manager_API();
    }
    if (class_exists('VMO_Email_Marketing') && empty($GLOBALS['vmo_email_marketing_instance'])) {
        $GLOBALS['vmo_email_marketing_instance'] = new VMO_Email_Marketing();
    }
    if (class_exists('VMO_Email_Compliance') && empty($GLOBALS['vmo_email_compliance_instance'])) {
        $GLOBALS['vmo_email_compliance_instance'] = VMO_Email_Compliance::get_instance();
    }
}, 5);

// CAPABILITY REGISTRY: Must load before all feature classes so they can self-register
$registry_path = VMO_AI_BUILDER_DIR . 'includes/class-vmo-capability-registry.php';
if (file_exists($registry_path)) {
    require_once $registry_path;
    VMO_Capability_Registry::get_instance(); // Initialize early
}

// NEW: Business wizard and multi-agent system with file validation
$business_wizard_includes = array(
    'includes/class-vmo-grapes-composer.php',
    'includes/business-wizard-handler.php',
    'includes/class-vmo-consultation-database.php',
    'includes/class-vmo-enhanced-business-wizard.php',
    'includes/class-vmo-consultation-website-integration.php',
    'includes/class-vmo-email-provisioner.php',
    'includes/class-vmo-zoho-mail-integration.php'
);

foreach ($business_wizard_includes as $include_file) {
    $full_path = VMO_AI_BUILDER_DIR . $include_file;
    if (file_exists($full_path) && is_readable($full_path)) {
        try {
            require_once $full_path;
        } catch (Exception $e) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('VMO: Failed to load ' . $include_file . ': ' . $e->getMessage());
            }
        }
    } else {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('VMO: Business wizard include file missing: ' . $full_path);
        }
    }
}

// DEFERRED LOADING: AI Website Assistant requires WordPress functions
add_action('plugins_loaded', function() {
    $assistant_path = VMO_AI_BUILDER_DIR . 'includes/class-vmo-website-ai-assistant.php';
    if (file_exists($assistant_path) && is_readable($assistant_path)) {
        try {
            require_once $assistant_path;
        } catch (Exception $e) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('VMO: Failed to load website AI assistant: ' . $e->getMessage());
            }
        }
    }
});

// Phone/Twilio and voice system
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-twilio-client.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-twilio-client.php';
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-phone-manager.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-phone-manager.php';
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-phone-admin.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-phone-admin.php';
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-phone-frontend.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-phone-frontend.php';
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-voice-processor.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-voice-processor.php';
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-content-crawler.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-content-crawler.php';
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-phone-auto-provisioning.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-phone-auto-provisioning.php';
// Twilio A2P 10DLC Registration Manager (ISV-ready brand & campaign registration)
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-twilio-a2p-manager.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-twilio-a2p-manager.php';
// Twilio Token Economics — usage-based billing for all Twilio services
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-twilio-token-economics.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-twilio-token-economics.php';
// Website Scanner for URL analysis, cloning, and reference scanning
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/class-vmo-website-scanner.php')) require_once VMO_AI_BUILDER_DIR . 'includes/class-vmo-website-scanner.php';
// VOICE SYSTEM REMOVED: Voice admin settings removed for No Voice version

// VOICE SYSTEM REMOVED: Audio transcription (Whisper API) disabled for No Voice version

// Business consultation handler
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/business-consultation-handler.php')) require_once VMO_AI_BUILDER_DIR . 'includes/business-consultation-handler.php';
// Agent router
if (file_exists(VMO_AI_BUILDER_DIR . 'agents/router/agent-router.php')) require_once VMO_AI_BUILDER_DIR . 'agents/router/agent-router.php';

// Memory & Roadmap AJAX endpoints (durable memory and milestones management)
if (file_exists(VMO_AI_BUILDER_DIR . 'includes/memory-roadmap.php')) {
    require_once VMO_AI_BUILDER_DIR . 'includes/memory-roadmap.php';
}

} // end if ($vmo_needs_subsystems)

// Instantiate managers so hooks/webhooks are active
add_action('plugins_loaded', function() {
    // Phone system disabled in no-voice version
    if (false && class_exists('VMO_Phone_Manager')) {
    // Ensure tables exist (repair if activation hook missed)
    try { VMO_Phone_Manager::create_tables(); } catch (Throwable $e) { /* ignore */ }
        // Singletons not enforced; simple instance to bind hooks
        $GLOBALS['vmo_phone_manager'] = new VMO_Phone_Manager();
    }
    if (false && class_exists('VMO_Phone_Admin') && is_admin()) {
        $GLOBALS['vmo_phone_admin'] = new VMO_Phone_Admin();
    }
    if (false && class_exists('VMO_Phone_Frontend') && is_admin()) {
        // Frontend handler provides AJAX for phone.js; safe in admin context
        $GLOBALS['vmo_phone_frontend'] = new VMO_Phone_Frontend();
    }
    
    // Initialize Enhanced Business Wizard
    if (class_exists('VMO_Enhanced_Business_Wizard')) {
        $GLOBALS['vmo_enhanced_business_wizard'] = new VMO_Enhanced_Business_Wizard();
    }
    
    // Initialize Audio Transcription (disabled - voice system removed)
    // Audio transcription was removed in no-voice version  
    if (false && class_exists('VMO_Audio_Transcription')) {
        $GLOBALS['vmo_audio_transcription'] = new VMO_Audio_Transcription();
    }
    
    // Initialize Business Consultation Handler
    if (class_exists('VMO_Business_Consultation')) {
        $GLOBALS['vmo_business_consultation'] = new VMO_Business_Consultation();
    }

    // Initialize Twilio A2P Registration Manager
    if (class_exists('VMO_Twilio_A2P_Manager')) {
        VMO_Twilio_A2P_Manager::get_instance();
    }

    // Initialize Twilio Token Economics (usage-based billing)
    if (class_exists('VMO_Twilio_Token_Economics')) {
        VMO_Twilio_Token_Economics::get_instance();
    }
    
    // Initialize Consultation Database
    if (class_exists('VMO_Consultation_Database')) {
        try {
            $vmo_consultation_db = new VMO_Consultation_Database();
            // Ensure tables exist on init; explicit call covers activation-missed cases
            $vmo_consultation_db->create_tables();
        } catch (Throwable $e) {
            if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: Failed to create consultation database tables: ' . $e->getMessage());
        }
    }
});

// === Monthly Reports Cron Job ===
add_action('vmo_send_monthly_reports', function() {
    if (class_exists('VMO_Consultation_Database')) {
        $db = new VMO_Consultation_Database();
        $db->send_monthly_report_email();
    }
});

// === Register AJAX handlers for device image upload ===
add_action('wp_ajax_vmo_upload_device_image', 'vmo_upload_device_image_handler');

// === Custom Post Type for Projects ===
add_action('init', function () {
    // Ensure rewrite rules are flushed for new REST routes (Fixes TTS 404/HTML errors)
    if (!get_transient('vmo_rewrite_flushed_v9_0_1_fixed')) {
        flush_rewrite_rules();
        set_transient('vmo_rewrite_flushed_v9_0_1_fixed', true, 12 * HOUR_IN_SECONDS);
    }

    register_post_type('vmo_project', [
        'labels' => [
            'name' => 'VMO Projects',
            'singular_name' => 'VMO Project',
        ],
        'public' => false,
        'show_ui' => true,
        'menu_icon' => 'dashicons-portfolio',
        'supports' => ['title', 'editor', 'author'],
    ]);
});

// Video Upload AJAX
add_action('wp_ajax_vmo_upload_video', function() {
    check_ajax_referer('vmo_builder_nonce', 'security');
    if (!function_exists('wp_handle_upload')) require_once(ABSPATH . 'wp-admin/includes/file.php');
    $uploaded = wp_handle_upload($_FILES['video'], ['test_form' => false]);
    if (isset($uploaded['url'])) {
        wp_send_json_success(['url' => $uploaded['url']]);
    } else {
        wp_send_json_error(['message' => 'Upload failed.']);
    }
});

// === Admin Menu Pages ===
add_action('admin_menu', function () {
    // Use custom VMO icon instead of dashicon
    $icon_url = plugin_dir_url(__FILE__) . 'assets/vmo-icon.svg';
    
    // Builder is user-facing; allow logged-in users.
    add_menu_page('VMO', 'VMO', 'read', 'vmo-ai-builder-main', 'vmo_ai_builder_page', $icon_url, 3);
    
    // Explicitly add the main page as the first submenu to ensure it remains accessible and isn't overridden by 'Settings'
    add_submenu_page('vmo-ai-builder-main', 'Website Builder', 'Website Builder', 'read', 'vmo-ai-builder-main', 'vmo_ai_builder_page');

    // Settings is admin-only.
    // Settings removed in client build
// Dashboard is user-facing.
    add_submenu_page('vmo-ai-builder-main', 'Dashboard', 'Dashboard', 'read', 'vmo-ai-dashboard', 'vmo_ai_user_dashboard_page');
    // My Plan / Billing
    add_submenu_page('vmo-ai-builder-main', 'My Plan', 'My Plan', 'read', 'vmo-my-plan', function() {
        include VMO_AI_BUILDER_DIR . 'templates/my-plan.php';
    });
    // Note: Business Phone, SEO Reports, Email Marketing, CRM, and Payments are now integrated 
    // into the Strategic Dashboard as tabs rather than separate pages

    // Master Admin: Affiliates / User Management
    // Master Admin removed in client build
});

function vmo_master_admin_page() {
    wp_die('This interface is only available on the VMO master server.');
}

// Add custom styles for the VMO icon in admin menu
add_action('admin_head', function() {
    echo '<style>
        #adminmenu #toplevel_page_vmo-ai-builder-main .wp-menu-image img {
            width: 20px;
            height: 20px;
            opacity: 0.6;
            transition: opacity 0.2s;
            padding: 6px 0;
        }
        #adminmenu #toplevel_page_vmo-ai-builder-main:hover .wp-menu-image img,
        #adminmenu #toplevel_page_vmo-ai-builder-main.current .wp-menu-image img {
            opacity: 1;
        }
    </style>';
});

// Prevent WordPress from redirecting our admin URLs
add_action('template_redirect', function() {
    if (is_admin()) {
        return; // Don't interfere with admin pages
    }
});

// Add additional protection against URL conflicts
add_action('init', function() {
    // Remove any conflicting rewrite rules that might interfere with admin URLs
    if (is_admin()) {
        remove_action('template_redirect', 'redirect_canonical');
    }
}, 1);

function vmo_ai_builder_page() {
    // Ensure we're in admin context
    if (!is_admin()) {
        wp_die('Access denied. This page can only be accessed from WordPress admin.');
    }
    
    // Check if template file exists
    $template_path = VMO_AI_BUILDER_DIR . 'templates/builder-interface.php';
    if (!file_exists($template_path)) {
        wp_die('VMO AI Builder template file not found. Please reinstall the plugin.');
    }
    
    // Include the template
    include $template_path;
}
function vmo_ai_settings_page() {
    wp_die('Settings are managed centrally on the VMO master server.');
}
function vmo_ai_user_dashboard_page() {
    if (!is_admin()) {
        wp_die('Access denied.');
    }
    // Use the new strategic dashboard UI (PHP version of vmo-strategic-dashboard-complete.html)
    $strategic_path = VMO_AI_BUILDER_DIR . 'templates/strategic-dashboard.php';
    if (file_exists($strategic_path)) {
    include $strategic_path;
    } else {
        include VMO_AI_BUILDER_DIR . 'templates/user-dashboard.php';
    }
}

// Dashboard stats AJAX for the strategic dashboard
add_action('wp_ajax_vmo_dashboard_stats', 'vmo_dashboard_stats');
// Helper: verify admin/builder nonce for AJAX requests (compat across modules)
if (!function_exists('vmo_verify_admin_security')) {
    function vmo_verify_admin_security(): bool {
        $nonce = sanitize_text_field($_POST['security'] ?? $_POST['nonce'] ?? '');
        if (!$nonce) return false;
        return (bool)(wp_verify_nonce($nonce, 'vmo_admin_nonce') || wp_verify_nonce($nonce, 'vmo_builder_nonce'));
    }
}
if (!function_exists('vmo_dashboard_normalize_assoc')) {
    function vmo_dashboard_normalize_assoc($value): array {
        if (is_array($value)) {
            return $value;
        }

        if (is_object($value)) {
            $value = json_decode(wp_json_encode($value), true);
            return is_array($value) ? $value : array();
        }

        if (is_string($value) && $value !== '') {
            $decoded = json_decode($value, true);
            if (is_array($decoded)) {
                return $decoded;
            }
        }

        return array();
    }
}
if (!function_exists('vmo_dashboard_seed_metric_value')) {
    function vmo_dashboard_seed_metric_value(array $seed_kpis, string $metric, $default = null) {
        if (!isset($seed_kpis[$metric]) || !is_array($seed_kpis[$metric])) {
            return $default;
        }

        return array_key_exists('value', $seed_kpis[$metric]) ? $seed_kpis[$metric]['value'] : $default;
    }
}
if (!function_exists('vmo_dashboard_seed_metric_truthy')) {
    function vmo_dashboard_seed_metric_truthy(array $seed_kpis, string $metric): bool {
        $value = vmo_dashboard_seed_metric_value($seed_kpis, $metric, null);

        if (is_bool($value)) {
            return $value;
        }

        if (is_numeric($value)) {
            return ((float) $value) > 0;
        }

        $normalized = strtolower(trim((string) $value));
        return in_array($normalized, array('1', 'true', 'yes', 'ready', 'active', 'completed', 'draft_ready', 'publish_ready', 'success'), true);
    }
}
if (!function_exists('vmo_dashboard_get_generation_context')) {
    function vmo_dashboard_get_generation_context(int $user_id): array {
        global $wpdb;

        $table_exists = function(string $table) use ($wpdb): bool {
            if (function_exists('vmo_table_exists')) {
                return vmo_table_exists($table);
            }

            return ((string) $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($table))) === $table);
        };

        $context = array(
            'operating_state' => array(),
            'website' => array(),
            'seed_kpis' => array(),
            'kpi_model' => array(),
            'dashboard_modules' => array(),
        );

        $kpi_table = $wpdb->prefix . 'vmo_business_kpis';
        if ($table_exists($kpi_table)) {
            $rows = $wpdb->get_results($wpdb->prepare(
                "SELECT metric, value, label, type, category, updated_at FROM {$kpi_table} WHERE user_id = %d",
                $user_id
            ), ARRAY_A);

            foreach (($rows ?: array()) as $row) {
                $metric = (string) ($row['metric'] ?? '');
                if ($metric !== '') {
                    $context['seed_kpis'][$metric] = $row;
                }
            }
        }

        $state = vmo_dashboard_normalize_assoc(get_user_meta($user_id, 'vmo_business_operating_state', true));
        $website_payload = array();
        $consultations_table = $wpdb->prefix . 'vmo_consultations';
        if ($table_exists($consultations_table)) {
            $website_data_raw = $wpdb->get_var($wpdb->prepare(
                "SELECT website_data FROM {$consultations_table} WHERE user_id = %d AND status = 'completed' AND website_data IS NOT NULL AND website_data != '' ORDER BY updated_at DESC, id DESC LIMIT 1",
                $user_id
            ));
            $website_data = vmo_dashboard_normalize_assoc($website_data_raw);

            if (empty($state) && !empty($website_data['operating_state'])) {
                $state = vmo_dashboard_normalize_assoc($website_data['operating_state']);
            }

            if (!empty($website_data['website'])) {
                $website_payload = vmo_dashboard_normalize_assoc($website_data['website']);
            } elseif (!empty($website_data['preview_url']) || !empty($website_data['post_id']) || !empty($website_data['homepage_html'])) {
                $website_payload = $website_data;
            }
        }

        $context['operating_state'] = $state;
        $context['website'] = $website_payload;

        $phase_outputs_table = $wpdb->prefix . 'vmo_phase_outputs';
        if ($table_exists($phase_outputs_table)) {
            foreach (array('kpi_model', 'dashboard_modules') as $output_type) {
                $output_json = $wpdb->get_var($wpdb->prepare(
                    "SELECT output_json FROM {$phase_outputs_table} WHERE user_id = %d AND output_type = %s AND is_active = 1 ORDER BY created_at DESC LIMIT 1",
                    $user_id,
                    $output_type
                ));
                $decoded = vmo_dashboard_normalize_assoc($output_json);
                if (!empty($decoded)) {
                    $context[$output_type] = $decoded;
                }
            }
        }

        $website_state = is_array($state['website'] ?? null) ? $state['website'] : array();
        $systems_state = is_array($state['systems'] ?? null) ? $state['systems'] : array();
        $pages_count = (int) ($website_state['pages_count'] ?? vmo_dashboard_seed_metric_value($context['seed_kpis'], 'pages_count', 0));
        if ($pages_count <= 0 && is_array($website_payload['pages_created'] ?? null)) {
            $pages_count = count($website_payload['pages_created']);
        }

        $website_ready = !empty($website_state['draft_ready']) || $pages_count > 0 || !empty($website_payload['preview_url']) || !empty($website_payload['post_id']);
        $payments_enabled = !empty($systems_state['payments']['enabled']) || vmo_dashboard_seed_metric_truthy($context['seed_kpis'], 'payment_system_ready');
        $payments_ready = !empty($systems_state['payments']['checkout_ready']) || vmo_dashboard_seed_metric_truthy($context['seed_kpis'], 'payment_system_ready');
        $phone_ready = !empty($systems_state['phone']['ready']) || vmo_dashboard_seed_metric_truthy($context['seed_kpis'], 'phone_system_ready');
        $email_ready = !empty($systems_state['email_marketing']['ready']) || vmo_dashboard_seed_metric_truthy($context['seed_kpis'], 'email_marketing_ready');
        $seo_ready = !empty($systems_state['seo']['ready']) || vmo_dashboard_seed_metric_truthy($context['seed_kpis'], 'seo_system_ready');
        $crm_ready = !empty($systems_state['contact_management']['ready']) || vmo_dashboard_seed_metric_truthy($context['seed_kpis'], 'contact_management_ready');

        if (empty($context['dashboard_modules'])) {
            $context['dashboard_modules'] = array(
                'revenue' => array(
                    'enabled' => $payments_enabled,
                    'status' => $payments_ready ? 'ready' : ($payments_enabled ? 'in_progress' : 'pending'),
                ),
                'traffic' => array(
                    'enabled' => $website_ready,
                    'status' => !empty($website_state['publish_ready']) ? 'ready' : ($website_ready ? 'in_progress' : 'pending'),
                ),
                'calls' => array(
                    'enabled' => $phone_ready,
                    'status' => $phone_ready ? 'ready' : 'pending',
                ),
                'conversions' => array(
                    'enabled' => $website_ready && ($payments_enabled || $crm_ready),
                    'status' => ($website_ready && ($payments_ready || $crm_ready)) ? 'ready' : (($website_ready || $payments_enabled || $crm_ready) ? 'in_progress' : 'pending'),
                ),
                'seo' => array(
                    'enabled' => $seo_ready,
                    'status' => $seo_ready ? 'ready' : 'pending',
                ),
                'crm' => array(
                    'enabled' => $crm_ready,
                    'status' => $crm_ready ? 'ready' : 'pending',
                ),
                'recommendations' => array(
                    'enabled' => !empty($state) || !empty($context['seed_kpis']),
                    'status' => (!empty($state) || !empty($context['seed_kpis'])) ? 'ready' : 'pending',
                ),
            );
        }

        if (empty($context['kpi_model'])) {
            $context['kpi_model'] = array(
                'metrics' => array(
                    'website_status',
                    'pages_count',
                    'setup_progress',
                    'payment_system_ready',
                    'phone_system_ready',
                    'email_marketing_ready',
                    'seo_system_ready',
                    'contact_management_ready',
                ),
                'targets' => array(
                    'pages_count' => max(1, $pages_count),
                    'setup_progress' => 100,
                    'website_status' => !empty($website_state['publish_ready']) ? 'publish_ready' : 'draft_ready',
                ),
                'measurement_frequency' => array(
                    'dashboard' => 'daily',
                    'website' => 'on_change',
                    'systems' => 'on_generation',
                ),
                'alert_rules' => array(
                    'website_qa' => 'Alert while website is not publish ready',
                    'systems' => 'Alert while required systems remain inactive',
                ),
            );
        }

        return $context;
    }
}
if (!function_exists('vmo_dashboard_build_setup_snapshot')) {
    function vmo_dashboard_build_setup_snapshot(array $context): array {
        $seed_kpis = is_array($context['seed_kpis'] ?? null) ? $context['seed_kpis'] : array();
        $operating_state = is_array($context['operating_state'] ?? null) ? $context['operating_state'] : array();
        $website_payload = is_array($context['website'] ?? null) ? $context['website'] : array();
        $dashboard_modules = is_array($context['dashboard_modules'] ?? null) ? $context['dashboard_modules'] : array();

        $business_state = is_array($operating_state['business'] ?? null) ? $operating_state['business'] : array();
        $website_state = is_array($operating_state['website'] ?? null) ? $operating_state['website'] : array();
        $systems_state = is_array($operating_state['systems'] ?? null) ? $operating_state['systems'] : array();

        $pages_count = (int) ($website_state['pages_count'] ?? vmo_dashboard_seed_metric_value($seed_kpis, 'pages_count', 0));
        if ($pages_count <= 0 && is_array($website_payload['pages_created'] ?? null)) {
            $pages_count = count($website_payload['pages_created']);
        }

        $website_ready = !empty($website_state['draft_ready']) || $pages_count > 0 || !empty($website_payload['preview_url']) || !empty($website_payload['post_id']);
        $publish_ready = !empty($website_state['publish_ready']) || ((string) vmo_dashboard_seed_metric_value($seed_kpis, 'website_status', '') === 'publish_ready');

        $qa_checklist = is_array($website_state['qa_checklist'] ?? null) ? $website_state['qa_checklist'] : array();
        $qa_pending = 0;
        foreach ($qa_checklist as $check) {
            if (is_array($check)) {
                $passed = !empty($check['passed']) || !empty($check['complete']) || !empty($check['resolved']) || (($check['status'] ?? '') === 'passed');
                if (!$passed) {
                    $qa_pending++;
                }
            } elseif (is_string($check) && trim($check) !== '') {
                $qa_pending++;
            }
        }

        $payments_enabled = !empty($systems_state['payments']['enabled']) || vmo_dashboard_seed_metric_truthy($seed_kpis, 'payment_system_ready');
        $payments_ready = !empty($systems_state['payments']['checkout_ready']) || vmo_dashboard_seed_metric_truthy($seed_kpis, 'payment_system_ready');
        $phone_ready = !empty($systems_state['phone']['ready']) || vmo_dashboard_seed_metric_truthy($seed_kpis, 'phone_system_ready');
        $email_ready = !empty($systems_state['email_marketing']['ready']) || vmo_dashboard_seed_metric_truthy($seed_kpis, 'email_marketing_ready');
        $seo_ready = !empty($systems_state['seo']['ready']) || vmo_dashboard_seed_metric_truthy($seed_kpis, 'seo_system_ready');
        $crm_ready = !empty($systems_state['contact_management']['ready']) || vmo_dashboard_seed_metric_truthy($seed_kpis, 'contact_management_ready');

        $next_milestone = trim((string) vmo_dashboard_seed_metric_value($seed_kpis, 'next_milestone', ''));
        if ($next_milestone === '') {
            if (!$website_ready) {
                $next_milestone = 'Complete website generation';
            } elseif ($qa_pending > 0 || !$publish_ready) {
                $next_milestone = 'Resolve website QA and publish';
            } elseif (!$payments_ready) {
                $next_milestone = 'Complete payments checkout setup';
            } elseif (!$crm_ready) {
                $next_milestone = 'Activate CRM and lead management';
            } else {
                $next_milestone = 'Monitor live KPIs and manage operations';
            }
        }

        $milestones = array(
            array('id' => 'website_draft', 'text' => 'Website draft generated', 'done' => $website_ready),
            array('id' => 'website_publish', 'text' => 'Website approved for publish', 'done' => $publish_ready),
            array('id' => 'payments', 'text' => 'Payments checkout ready', 'done' => $payments_ready),
            array('id' => 'phone', 'text' => 'Phone system ready', 'done' => $phone_ready),
            array('id' => 'email', 'text' => 'Email automations ready', 'done' => $email_ready),
            array('id' => 'seo', 'text' => 'SEO system ready', 'done' => $seo_ready),
            array('id' => 'crm', 'text' => 'CRM and contact management ready', 'done' => $crm_ready),
        );

        $setup_total = count($milestones);
        $setup_completed = count(array_filter($milestones, function($milestone) {
            return !empty($milestone['done']);
        }));
        $derived_progress = $setup_total > 0 ? (int) round(($setup_completed / max(1, $setup_total)) * 100) : 0;
        $seed_progress = (int) vmo_dashboard_seed_metric_value($seed_kpis, 'setup_progress', 0);
        $setup_progress = max($derived_progress, $seed_progress);

        $status_label = $publish_ready
            ? 'Website is publish ready and connected to the dashboard'
            : ($website_ready ? 'Website draft is connected to the dashboard' : 'Website generation is still pending');

        $tasks = array();
        if (!$website_ready) {
            $tasks[] = array('id' => 'website_generation', 'text' => 'Complete website generation', 'checked' => false);
        } else {
            $tasks[] = array(
                'id' => 'website_review',
                'text' => sprintf('Review %d generated page%s in the live preview', max(1, $pages_count), max(1, $pages_count) === 1 ? '' : 's'),
                'checked' => false,
            );
            if ($qa_pending > 0) {
                $tasks[] = array(
                    'id' => 'website_qa',
                    'text' => sprintf('Resolve %d website QA item%s before publishing', $qa_pending, $qa_pending === 1 ? '' : 's'),
                    'checked' => false,
                );
            } elseif (!$publish_ready) {
                $tasks[] = array('id' => 'publish_checks', 'text' => 'Run final publish checks on the website draft', 'checked' => false);
            } else {
                $tasks[] = array('id' => 'publish_live', 'text' => 'Publish the website and begin monitoring live KPIs', 'checked' => false);
            }
        }
        if (!$payments_ready) {
            $tasks[] = array('id' => 'payments_setup', 'text' => 'Complete payments checkout activation', 'checked' => false);
        }
        if (!$crm_ready) {
            $tasks[] = array('id' => 'crm_setup', 'text' => 'Activate CRM and contact management', 'checked' => false);
        }
        if (!$email_ready) {
            $tasks[] = array('id' => 'email_setup', 'text' => 'Enable email marketing automations', 'checked' => false);
        }
        if (!$seo_ready) {
            $tasks[] = array('id' => 'seo_setup', 'text' => 'Enable SEO monitoring and optimization', 'checked' => false);
        }
        if (!$phone_ready) {
            $tasks[] = array('id' => 'phone_setup', 'text' => 'Activate the business phone system', 'checked' => false);
        }

        $active_module_labels = array();
        foreach ($dashboard_modules as $module_name => $module_state) {
            $status = (string) ($module_state['status'] ?? (!empty($module_state['enabled']) ? 'ready' : 'pending'));
            if ($status === 'ready') {
                $active_module_labels[] = $module_name;
            }
        }

        $insights = array();
        if ($website_ready) {
            $insights[] = $publish_ready
                ? sprintf('Your %d-page website is now connected to the dashboard and ready for launch operations.', max(1, $pages_count))
                : sprintf('Your %d-page website draft is connected to the dashboard; complete the remaining publish steps to go live.', max(1, $pages_count));
        } else {
            $insights[] = 'The dashboard is waiting for website generation to complete.';
        }
        if ($qa_pending > 0) {
            $insights[] = sprintf('%d QA item%s still need attention before the website can be published.', $qa_pending, $qa_pending === 1 ? '' : 's');
        }
        if (!empty($active_module_labels)) {
            $insights[] = 'Active dashboard modules: ' . implode(', ', $active_module_labels) . '.';
        } else {
            $insights[] = 'Business systems will appear here as each service is activated.';
        }

        $module_counts = array('active' => 0, 'in_progress' => 0, 'pending' => 0);
        foreach ($dashboard_modules as $module_state) {
            $status = (string) ($module_state['status'] ?? (!empty($module_state['enabled']) ? 'ready' : 'pending'));
            if ($status === 'ready') {
                $module_counts['active']++;
            } elseif ($status === 'in_progress') {
                $module_counts['in_progress']++;
            } else {
                $module_counts['pending']++;
            }
        }

        return array(
            'business' => array(
                'name' => (string) ($business_state['name'] ?? vmo_dashboard_seed_metric_value($seed_kpis, 'business_name', '')),
                'type' => (string) ($business_state['type'] ?? vmo_dashboard_seed_metric_value($seed_kpis, 'business_type', '')),
                'industry' => (string) ($business_state['industry'] ?? vmo_dashboard_seed_metric_value($seed_kpis, 'industry', '')),
            ),
            'website' => array(
                'draft_ready' => $website_ready,
                'publish_ready' => $publish_ready,
                'pages_count' => $pages_count,
                'qa_pending' => $qa_pending,
                'status' => $publish_ready ? 'publish_ready' : ($website_ready ? 'draft_ready' : 'pending'),
                'status_label' => $status_label,
                'preview_url' => (string) ($website_payload['preview_url'] ?? ''),
                'post_id' => (int) ($website_payload['post_id'] ?? 0),
                'project_title' => (string) ($website_payload['project_title'] ?? ''),
            ),
            'systems' => array(
                'payments' => array('enabled' => $payments_enabled, 'ready' => $payments_ready),
                'phone' => array('ready' => $phone_ready),
                'email_marketing' => array('ready' => $email_ready),
                'seo' => array('ready' => $seo_ready),
                'contact_management' => array('ready' => $crm_ready),
            ),
            'setup' => array(
                'progress' => $setup_progress,
                'completed' => $setup_completed,
                'total' => $setup_total,
                'blockers' => count(array_filter($milestones, function($milestone) {
                    return empty($milestone['done']);
                })) + $qa_pending,
                'milestones' => $milestones,
                'next_milestone' => $next_milestone,
            ),
            'tasks' => array_slice($tasks, 0, 10),
            'insights' => array_slice(array_values(array_filter($insights)), 0, 5),
            'modules' => $module_counts,
        );
    }
}
function vmo_dashboard_stats() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }

    global $wpdb;
    $user_id = get_current_user_id();
    $dashboard_context = vmo_dashboard_get_generation_context($user_id);
    $setup_snapshot = vmo_dashboard_build_setup_snapshot($dashboard_context);
    // Handle scoped requests early for specific panels
    $scope = isset($_POST['scope']) ? sanitize_text_field(wp_unslash($_POST['scope'])) : '';
    if ($scope) {
        // Payments KPIs and recent transactions
        if ($scope === 'payments') {
            $orders_table = $wpdb->prefix . 'vmo_store_orders';
            $exists = vmo_table_exists($orders_table);
            $sum_period = function($days) use ($wpdb, $orders_table, $user_id, $exists) {
                if (!$exists) return 0;
                $since = date('Y-m-d H:i:s', strtotime('-' . (int)$days . ' days'));
                $q = $wpdb->get_var($wpdb->prepare("SELECT COALESCE(SUM(amount_total),0) FROM {$orders_table} WHERE user_id = %d AND status='paid' AND created_at >= %s", $user_id, $since));
                return (int) $q;
            };
            $day = $sum_period(1);
            $week = $sum_period(7);
            $month = $sum_period(30);
            $year = $sum_period(365);

            // Month-over-month: compare last 30 days vs previous 30 days
            $mom_change = 0.0;
            if ($exists) {
                $end_current = current_time('mysql');
                $start_current = date('Y-m-d H:i:s', strtotime('-30 days', strtotime($end_current)));
                $start_prev = date('Y-m-d H:i:s', strtotime('-60 days', strtotime($end_current)));
                $sum_current = (int) $wpdb->get_var($wpdb->prepare(
                    "SELECT COALESCE(SUM(amount_total),0) FROM {$orders_table} WHERE user_id=%d AND status='paid' AND created_at BETWEEN %s AND %s",
                    $user_id, $start_current, $end_current
                ));
                $sum_prev = (int) $wpdb->get_var($wpdb->prepare(
                    "SELECT COALESCE(SUM(amount_total),0) FROM {$orders_table} WHERE user_id=%d AND status='paid' AND created_at BETWEEN %s AND %s",
                    $user_id, $start_prev, $start_current
                ));
                if ($sum_prev > 0) {
                    $mom_change = round((($sum_current - $sum_prev) / $sum_prev) * 100, 2);
                } else {
                    $mom_change = $sum_current > 0 ? 100.0 : 0.0;
                }
            }

            // Recent transactions (latest 20)
            $transactions = [];
            if ($exists) {
                $rows = $wpdb->get_results($wpdb->prepare(
                    "SELECT id, created_at, amount_total, currency, status, metadata FROM {$orders_table} WHERE user_id=%d ORDER BY created_at DESC LIMIT 20",
                    $user_id
                ), ARRAY_A);
                foreach (($rows ?: []) as $r) {
                    $meta = [];
                    if (!empty($r['metadata'])) {
                        $meta = json_decode((string) $r['metadata'], true);
                        if (!is_array($meta)) $meta = [];
                    }
                    $product_label = (string) ($meta['product_name'] ?? '');
                    if ($product_label === '' && !empty($meta['product_id'])) {
                        $product_label = 'Product #' . (int) $meta['product_id'];
                    }
                    $transactions[] = [
                        'id' => (int) $r['id'],
                        'date' => (string) $r['created_at'],
                        'product' => $product_label,
                        'amount' => (int) $r['amount_total'],
                        'currency' => (string) ($r['currency'] ?? 'usd'),
                        'status' => (string) ($r['status'] ?? '')
                    ];
                }
            }

            // Daily sales for last 30 days (for Sales Trend line chart)
            $daily_sales = [];
            if ($exists) {
                $daily_rows = $wpdb->get_results($wpdb->prepare(
                    "SELECT DATE(created_at) as sale_date, COALESCE(SUM(amount_total),0) as total FROM {$orders_table} WHERE user_id=%d AND status='paid' AND created_at >= %s GROUP BY DATE(created_at) ORDER BY sale_date ASC",
                    $user_id, date('Y-m-d', strtotime('-30 days'))
                ), ARRAY_A);
                // Fill in missing days with 0
                $date_map = [];
                foreach (($daily_rows ?: []) as $dr) {
                    $date_map[$dr['sale_date']] = (int) $dr['total'];
                }
                for ($i = 29; $i >= 0; $i--) {
                    $d_key = date('Y-m-d', strtotime("-{$i} days"));
                    $daily_sales[] = [
                        'date' => $d_key,
                        'label' => date('M j', strtotime($d_key)),
                        'total' => $date_map[$d_key] ?? 0,
                    ];
                }
            }

            // Monthly sales for last 12 months (for bar chart)
            $monthly_sales = [];
            if ($exists) {
                $monthly_rows = $wpdb->get_results($wpdb->prepare(
                    "SELECT DATE_FORMAT(created_at, '%%Y-%%m') as sale_month, COALESCE(SUM(amount_total),0) as total FROM {$orders_table} WHERE user_id=%d AND status='paid' AND created_at >= %s GROUP BY DATE_FORMAT(created_at, '%%Y-%%m') ORDER BY sale_month ASC",
                    $user_id, date('Y-m-d', strtotime('-12 months'))
                ), ARRAY_A);
                $month_map = [];
                foreach (($monthly_rows ?: []) as $mr) {
                    $month_map[$mr['sale_month']] = (int) $mr['total'];
                }
                for ($i = 11; $i >= 0; $i--) {
                    $m_key = date('Y-m', strtotime("-{$i} months"));
                    $monthly_sales[] = [
                        'month' => $m_key,
                        'label' => date('M Y', strtotime($m_key . '-01')),
                        'total' => $month_map[$m_key] ?? 0,
                    ];
                }
            }

            wp_send_json_success([
                'day' => $day,
                'week' => $week,
                'month' => $month,
                'year' => $year,
                'mom_change' => $mom_change,
                'transactions' => $transactions,
                'daily_sales' => $daily_sales,
                'monthly_sales' => $monthly_sales,
            ]);
        }

        // Live calls snapshot
        if ($scope === 'live_calls') {
            $calls_table = $wpdb->prefix . 'vmo_business_calls';
            $exists = vmo_table_exists($calls_table);
            $live_calls = [];
            if ($exists) {
                $since = date('Y-m-d H:i:s', strtotime('-15 minutes', current_time('timestamp')));
                $rows = $wpdb->get_results($wpdb->prepare(
                    "SELECT id, caller, agent_name, call_status FROM {$calls_table} WHERE user_id=%d AND call_start_time >= %s AND call_status IN ('ringing','in-progress') ORDER BY call_start_time DESC LIMIT 20",
                    $user_id, $since
                ), ARRAY_A);
                foreach (($rows ?: []) as $r) {
                    $live_calls[] = [
                        'id' => (int) $r['id'],
                        'caller' => (string) ($r['caller'] ?? ''),
                        'agent' => (string) ($r['agent_name'] ?? ''),
                        'status' => (string) ($r['call_status'] ?? '')
                    ];
                }
            }
            wp_send_json_success(['live_calls' => $live_calls]);
        }

        // Call history
        if ($scope === 'call_history') {
            $calls_table = $wpdb->prefix . 'vmo_business_calls';
            $exists = vmo_table_exists($calls_table);
            $history = [];
            if ($exists) {
                $rows = $wpdb->get_results($wpdb->prepare(
                    "SELECT id, caller, call_duration, call_status FROM {$calls_table} WHERE user_id=%d ORDER BY call_start_time DESC LIMIT 50",
                    $user_id
                ), ARRAY_A);
                foreach (($rows ?: []) as $r) {
                    $history[] = [
                        'id' => (int) $r['id'],
                        'caller' => (string) ($r['caller'] ?? ''),
                        'duration' => (int) ($r['call_duration'] ?? 0),
                        'result' => (string) ($r['call_status'] ?? '')
                    ];
                }
            }
            wp_send_json_success(['call_history' => $history]);
        }
    }
    $counts = [
        'contacts' => 0,
        'pending_approvals' => 0,
        'calls_today' => 0,
        'avg_call_duration' => 0,
        // Added KPIs for header wiring
        'goals_complete_percent' => 0,
        'growth_alerts' => 0,
        // Business progress additions
        'short_term' => [
            'percent' => 0,
            'completed' => 0,
            'total' => 0,
            'today_percent' => 0,
        ],
        'long_term' => [
            'percent' => 0,
            'note' => '—'
        ],
        // Action items + weekly strategy
        'tasks_today' => [],
        'weekly' => [
            'title' => '📊 Weekly Strategy Briefing',
            'insights' => [],
            'actions' => []
        ],
        // Business Intelligence KPIs
        'bi' => [
            'ranking' => '—',
            'growth' => 0,
            'next_milestone' => '—',
            'monitored' => 0,
            'new_threats' => 0,
            'opportunities' => 0,
        ],
    ];

    $table_exists = function($table) {
        global $wpdb;
        $like = $wpdb->esc_like($table);
        return (bool) $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $like));
    };

    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    if ($table_exists($contacts_table)) {
        $counts['contacts'] = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$contacts_table} WHERE user_id = %d", $user_id));
    }

    // Prefer progress based on scheduled tasks (follow-ups) if available
    $followups_table = $wpdb->prefix . 'vmo_follow_ups';
    $approval_table = $wpdb->prefix . 'vmo_approval_queue';
    $fu_exists = $table_exists($followups_table);
    if ($fu_exists) {
        // Goals complete percent: completed tasks vs all tasks in last 30 days (scheduled)
        $since30 = date('Y-m-d H:i:s', strtotime('-30 days'));
        $fu_total_30 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$followups_table} WHERE user_id=%d AND scheduled_date >= %s", $user_id, $since30));
        $fu_done_30  = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$followups_table} WHERE user_id=%d AND status='completed' AND scheduled_date >= %s", $user_id, $since30));
        $counts['goals_complete_percent'] = ($fu_total_30 > 0) ? (int) round(($fu_done_30 / max(1,$fu_total_30)) * 100) : 0;

        // Short-term (0-3mo) progress based on tasks scheduled within next 90 days
        $now = current_time('mysql');
        $to90 = date('Y-m-d H:i:s', strtotime('+90 days', current_time('timestamp')));
        $st_total = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$followups_table} WHERE user_id=%d AND scheduled_date BETWEEN %s AND %s", $user_id, $now, $to90));
        $st_done  = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$followups_table} WHERE user_id=%d AND status='completed' AND scheduled_date BETWEEN %s AND %s", $user_id, $now, $to90));
        $counts['short_term']['total'] = $st_total;
        $counts['short_term']['completed'] = $st_done;
        $counts['short_term']['percent'] = ($st_total > 0) ? (int) round(($st_done / max(1,$st_total)) * 100) : 0;
        // Today percent: tasks scheduled today, completed vs total
        $todayStart = date('Y-m-d 00:00:00', current_time('timestamp'));
        $todayEnd   = date('Y-m-d 23:59:59', current_time('timestamp'));
        $st_today_total = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$followups_table} WHERE user_id=%d AND scheduled_date BETWEEN %s AND %s", $user_id, $todayStart, $todayEnd));
        $st_today_done = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$followups_table} WHERE user_id=%d AND status='completed' AND scheduled_date BETWEEN %s AND %s", $user_id, $todayStart, $todayEnd));
        $counts['short_term']['today_percent'] = ($st_today_total > 0) ? (int) round(($st_today_done / max(1,$st_today_total)) * 100) : 0;

        // Long-term vision percent (12mo+): tasks scheduled within next 365 days
        $to365 = date('Y-m-d H:i:s', strtotime('+365 days', current_time('timestamp')));
        $lt_total = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$followups_table} WHERE user_id=%d AND scheduled_date BETWEEN %s AND %s", $user_id, $now, $to365));
        $lt_done  = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$followups_table} WHERE user_id=%d AND status='completed' AND scheduled_date BETWEEN %s AND %s", $user_id, $now, $to365));
        $counts['long_term']['percent'] = ($lt_total > 0) ? (int) round(($lt_done / max(1,$lt_total)) * 100) : 0;
        $counts['long_term']['note'] = $lt_total > 0 ? __('Progress based on scheduled roadmap tasks', 'vmo') : '—';
    } elseif ($table_exists($approval_table)) {
        // Fallback to approvals if follow-ups table not present
        $counts['pending_approvals'] = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$approval_table} WHERE user_id = %d AND status = 'pending'", $user_id));
        $since30 = date('Y-m-d H:i:s', strtotime('-30 days'));
        $total_items_30 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$approval_table} WHERE user_id = %d AND created_at >= %s", $user_id, $since30));
        $approved_30 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$approval_table} WHERE user_id = %d AND status = 'approved' AND created_at >= %s", $user_id, $since30));
        $counts['goals_complete_percent'] = ($total_items_30 >= 5) ? (int) round(($approved_30 / max(1,$total_items_30)) * 100) : 0;
        $since90 = date('Y-m-d H:i:s', strtotime('-90 days'));
        $st_total = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$approval_table} WHERE user_id = %d AND created_at >= %s", $user_id, $since90));
        $st_done = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$approval_table} WHERE user_id = %d AND status = 'approved' AND created_at >= %s", $user_id, $since90));
        $counts['short_term']['total'] = $st_total;
        $counts['short_term']['completed'] = $st_done;
        $counts['short_term']['percent'] = ($st_total >= 5) ? (int) round(($st_done / max(1,$st_total)) * 100) : 0;
        $todayStart = date('Y-m-d 00:00:00', current_time('timestamp'));
        $st_today_total = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$approval_table} WHERE user_id = %d AND created_at >= %s", $user_id, $todayStart));
        $st_today_done = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$approval_table} WHERE user_id = %d AND status = 'approved' AND created_at >= %s", $user_id, $todayStart));
        $counts['short_term']['today_percent'] = ($st_today_total > 0) ? (int) round(($st_today_done / $st_today_total) * 100) : 0;
        $since365 = date('Y-m-d H:i:s', strtotime('-365 days'));
        $lt_total = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$approval_table} WHERE user_id = %d AND created_at >= %s", $user_id, $since365));
        $lt_done = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$approval_table} WHERE user_id = %d AND status = 'approved' AND created_at >= %s", $user_id, $since365));
        $counts['long_term']['percent'] = ($lt_total >= 10) ? (int) round(($lt_done / max(1,$lt_total)) * 100) : 0;
        $counts['long_term']['note'] = ($lt_total >= 10) ? __('On track for long-term targets', 'vmo') : '—';
    }

    // Populate Business Intelligence KPIs based on actual consultation completion
    try {
        // Check if user has completed business consultation
        $consultation_table = $wpdb->prefix . 'vmo_consultations';
        $has_consultations = $table_exists($consultation_table);
        $completed_consultation = false;
        
        if ($has_consultations) {
            $completed_consultation = (bool) $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM {$consultation_table} WHERE user_id=%d AND status='completed'", 
                $user_id
            ));
        }
        
        // Check if user has an active website/domain
        $domains_table = $wpdb->prefix . 'vmo_domains';
        $has_domains = $table_exists($domains_table);
        $active_website = false;
        
        if ($has_domains) {
            $active_website = (bool) $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM {$domains_table} WHERE user_id=%d AND status='active'", 
                $user_id
            ));
        }
        
        // Only show actual BI data if consultation is completed AND website exists
        if ($completed_consultation && $active_website) {
            $orders_table = $wpdb->prefix . 'vmo_store_orders';
            $interactions_table = $wpdb->prefix . 'vmo_contact_interactions';
            $has_orders = $table_exists($orders_table);
            $has_interactions = $table_exists($interactions_table);
            
            // Growth: compare visits last 30d vs prev 30d (only if data exists)
            $end = current_time('mysql');
            $start30 = date('Y-m-d H:i:s', strtotime('-30 days', strtotime($end)));
            $prevStart = date('Y-m-d H:i:s', strtotime('-60 days', strtotime($end)));
            $curr = 0; $prev = 0;
            
            if ($has_interactions) {
                $curr = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$interactions_table} WHERE user_id=%d AND interaction_type='website_visit' AND created_at BETWEEN %s AND %s", $user_id, $start30, $end));
                $prev = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$interactions_table} WHERE user_id=%d AND interaction_type='website_visit' AND created_at BETWEEN %s AND %s", $user_id, $prevStart, $start30));
            } elseif ($has_orders) {
                $curr = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$orders_table} WHERE user_id=%d AND status='paid' AND created_at BETWEEN %s AND %s", $user_id, $start30, $end));
                $prev = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$orders_table} WHERE user_id=%d AND status='paid' AND created_at BETWEEN %s AND %s", $user_id, $prevStart, $start30));
            }
            
            if ($curr > 0 || $prev > 0) {
                $growth = ($prev > 0) ? round((($curr - $prev) / max(1,$prev)) * 100, 1) : ($curr > 0 ? 100.0 : 0.0);
                $counts['bi']['growth'] = $growth;
                
                // Ranking: only show if we have significant data
                if ($curr >= 10 || $prev >= 10) {
                    $counts['bi']['ranking'] = ($growth >= 50 ? '#1' : ($growth >= 20 ? '#2' : ($growth >= 10 ? '#3' : '#4+')));
                } else {
                    $counts['bi']['ranking'] = __('Analyzing...', 'vmo');
                }
                
                // Next milestone based on actual performance
                $counts['bi']['next_milestone'] = ($growth >= 20) ? __('Sustain 20%+ MoM for 60 days', 'vmo') : __('Achieve 20% MoM growth', 'vmo');
            } else {
                // No traffic data yet
                $counts['bi']['growth'] = 0;
                $counts['bi']['ranking'] = __('Building baseline...', 'vmo');
                $counts['bi']['next_milestone'] = __('Establish website traffic baseline', 'vmo');
            }
            
            // Monitored competitors: only from actual connections
            $connected_accounts_table = $wpdb->prefix . 'vmo_connected_accounts';
            $has_accounts = $table_exists($connected_accounts_table);
            $counts['bi']['monitored'] = $has_accounts ? (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$connected_accounts_table} WHERE user_id=%d AND provider LIKE '%%competitor%%'", $user_id)) : 0;
            
            // New threats/opportunities: only from real data
            $since7 = date('Y-m-d H:i:s', strtotime('-7 days'));
            $counts['bi']['new_threats'] = $table_exists($approval_table) ? (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$approval_table} WHERE user_id=%d AND status='pending' AND created_at >= %s", $user_id, $since7)) : 0;
            $counts['bi']['opportunities'] = $table_exists($followups_table) ? (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$followups_table} WHERE user_id=%d AND status='pending' AND scheduled_date >= %s", $user_id, $since7)) : 0;
        } else {
            // No consultation completed or no website - show pending status
            $counts['bi']['ranking'] = __('Consultation pending', 'vmo');
            $counts['bi']['growth'] = 0;
            if (!$completed_consultation) {
                $counts['bi']['next_milestone'] = __('Complete business consultation', 'vmo');
            } elseif (!$active_website) {
                $counts['bi']['next_milestone'] = __('Launch website & begin tracking', 'vmo');
            } else {
                $counts['bi']['next_milestone'] = __('Establish baseline metrics', 'vmo');
            }
            $counts['bi']['monitored'] = 0;
            $counts['bi']['new_threats'] = 0;
            $counts['bi']['opportunities'] = 0;
        }
    } catch (Exception $e) {
        // Fallback to pending state if any error occurs
        $counts['bi']['ranking'] = __('Analysis pending', 'vmo');
        $counts['bi']['growth'] = 0;
        $counts['bi']['next_milestone'] = __('Complete business consultation', 'vmo');
        $counts['bi']['monitored'] = 0;
        $counts['bi']['new_threats'] = 0;
        $counts['bi']['opportunities'] = 0;
    }

    // Phone metrics - check both legacy and new phone tables
    $legacy_calls_table = $wpdb->prefix . 'vmo_business_calls';
    $new_calls_table = $wpdb->prefix . 'vmo_phone_calls';
    $pd_calls_table = $wpdb->prefix . 'vmo_power_dialer_calls';
    
    $calls_today = 0;
    $avg_duration = 0;
    $missed_calls_7 = 0;
    
    // Try new phone_calls table first (Twilio integration)
    if ($table_exists($new_calls_table)) {
        $today = current_time('Y-m-d');
        $is_admin_stats = current_user_can('manage_options');
        
        $calls_today = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$new_calls_table} WHERE user_id = %d AND DATE(created_at) = %s",
            $user_id,
            $today
        ));

        // Admin fallback: if no calls for this user_id, check all users
        if ($calls_today === 0 && $is_admin_stats) {
            $calls_today = (int) $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM {$new_calls_table} WHERE DATE(created_at) = %s",
                $today
            ));
        }

        $user_filter_sql = ($calls_today > 0 || !$is_admin_stats) ? 'user_id = %d AND' : '';
        $user_filter_params = ($calls_today > 0 || !$is_admin_stats) ? array( $user_id ) : array();

        $sum_dur_today = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COALESCE(SUM(duration),0) FROM {$new_calls_table} WHERE {$user_filter_sql} DATE(created_at) = %s AND duration > 0",
                ...array_merge( $user_filter_params, array( $today ) )
            )
        );
        $cnt_dur_today = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$new_calls_table} WHERE {$user_filter_sql} DATE(created_at) = %s AND duration > 0",
                ...array_merge( $user_filter_params, array( $today ) )
            )
        );

        // Include Power Dialer campaign calls (campaign_id > 0) without double-counting direct dialer calls.
        if ($table_exists($pd_calls_table)) {
            $calls_today += (int) $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM {$pd_calls_table} WHERE user_id = %d AND campaign_id > 0 AND DATE(created_at) = %s",
                $user_id,
                $today
            ));

            $sum_dur_today += (int) $wpdb->get_var($wpdb->prepare(
                "SELECT COALESCE(SUM(duration),0) FROM {$pd_calls_table} WHERE user_id = %d AND campaign_id > 0 AND DATE(created_at) = %s AND duration > 0",
                $user_id,
                $today
            ));
            $cnt_dur_today += (int) $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM {$pd_calls_table} WHERE user_id = %d AND campaign_id > 0 AND DATE(created_at) = %s AND duration > 0",
                $user_id,
                $today
            ));
        }

        $avg_duration = ($cnt_dur_today > 0) ? (int) round($sum_dur_today / max(1, $cnt_dur_today)) : 0;

        // Missed calls in last 7 days (status = 'no-answer' or 'busy' or 'failed')
        $since7 = date('Y-m-d H:i:s', strtotime('-7 days'));
        $missed_calls_7 = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$new_calls_table} WHERE user_id = %d AND status IN ('no-answer','busy','failed') AND created_at >= %s",
            $user_id, $since7
        ));
    }
    // Fallback to legacy calls table if new table has no data
    elseif ($table_exists($legacy_calls_table)) {
        $today = current_time('Y-m-d');
        $calls_today = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$legacy_calls_table} WHERE user_id = %d AND DATE(call_start_time) = %s",
            $user_id,
            $today
        ));
        $avg = $wpdb->get_var($wpdb->prepare(
            "SELECT AVG(call_duration) FROM {$legacy_calls_table} WHERE user_id = %d AND DATE(call_start_time) = %s",
            $user_id,
            $today
        ));
        $avg_duration = $avg ? round((float) $avg) : 0;

        // Missed calls in last 7 days
        $since7 = date('Y-m-d H:i:s', strtotime('-7 days'));
        $missed_calls_7 = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$legacy_calls_table} WHERE user_id = %d AND call_status = 'expired' AND call_start_time >= %s",
            $user_id, $since7
        ));
    }
    
    $counts['calls_today'] = $calls_today;
    $counts['avg_call_duration'] = $avg_duration;
    $counts['growth_alerts'] += $missed_calls_7;

    // Overdue follow-ups contribute to growth alerts
    if ($fu_exists) {
        $now = current_time('mysql');
        $overdue = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$followups_table} WHERE user_id = %d AND status = 'pending' AND scheduled_date < %s",
            $user_id, $now
        ));
        $counts['growth_alerts'] += $overdue;
    }

    // Pending approvals also count as actionable alerts
    $counts['growth_alerts'] += (int)$counts['pending_approvals'];

    // Build Today action items (lightweight, robust to schema)
    $missed_7 = 0;
    if (!empty($counts['growth_alerts'])) {
        // We already computed missed calls last 7 days above, re-use if available
    }
    // Recompute missed_7 specifically (if calls table exists) to include in task text
    $calls_table = $wpdb->prefix . 'vmo_business_calls';
    if ($table_exists($calls_table)) {
        $since7 = date('Y-m-d H:i:s', strtotime('-7 days'));
        $missed_7 = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$calls_table} WHERE user_id = %d AND call_status = 'expired' AND call_start_time >= %s",
            $user_id, $since7
        ));
    }
    $tasks = [];
    // Pull real tasks scheduled for today from follow-ups if available
    if ($fu_exists) {
        $todayStart = date('Y-m-d 00:00:00', current_time('timestamp'));
        $todayEnd   = date('Y-m-d 23:59:59', current_time('timestamp'));
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT id, notes, status FROM {$followups_table} WHERE user_id=%d AND scheduled_date BETWEEN %s AND %s ORDER BY scheduled_date ASC LIMIT 25",
            $user_id, $todayStart, $todayEnd
        ), ARRAY_A);
        foreach (($rows ?: []) as $r) {
            $tasks[] = [
                'id' => (int) $r['id'],
                'text' => (string) ($r['notes'] ?: 'Task'),
                'tokens' => 0,
                'checked' => ((string)$r['status'] === 'completed')
            ];
        }
    } else {
        // Fallback tasks based on approvals/missed calls if present
        if (!empty($counts['pending_approvals'])) {
            $tasks[] = [ 'text' => sprintf(__('Review %d pending approvals', 'vmo'), (int)$counts['pending_approvals']), 'tokens' => 300, 'checked' => false ];
        }
        if ($missed_7 > 0) {
            $tasks[] = [ 'text' => sprintf(__('Return %d missed calls (last 7 days)', 'vmo'), (int)$missed_7), 'tokens' => 300, 'checked' => false ];
        }
    }
    $counts['tasks_today'] = array_slice($tasks, 0, 10);

    // Weekly strategy insights (pull from weekly automation option if present)
    $week_key_now = 'vmo_weekly_email_results_' . $user_id . '_' . date('Y_W');
    $week_key_prev = 'vmo_weekly_email_results_' . $user_id . '_' . date('Y_W', strtotime('-7 days'));
    $weekly_data = get_option($week_key_now);
    if (!$weekly_data) { $weekly_data = get_option($week_key_prev); }
    if (is_array($weekly_data)) {
        // Attempt to extract insights/actions
        $insights = [];
        if (isset($weekly_data['research_data']['actionable_insights']) && is_array($weekly_data['research_data']['actionable_insights'])) {
            $insights = $weekly_data['research_data']['actionable_insights'];
        } elseif (isset($weekly_data['actionable_insights']) && is_array($weekly_data['actionable_insights'])) {
            $insights = $weekly_data['actionable_insights'];
        }
        $actions = [];
        if (isset($weekly_data['recommended_actions']) && is_array($weekly_data['recommended_actions'])) {
            foreach ($weekly_data['recommended_actions'] as $a) {
                if (is_string($a)) { $actions[] = ['text' => $a, 'tokens' => 0]; }
                elseif (is_array($a) && isset($a['text'])) { $actions[] = ['text' => $a['text'], 'tokens' => intval($a['tokens'] ?? 0)]; }
            }
        }
    // Do not inject placeholders; leave empty if none yet
    $counts['weekly']['insights'] = array_values(array_slice($insights, 0, 5));
    $counts['weekly']['actions'] = array_values(array_slice($actions, 0, 5));
    } else {
    // Leave weekly empty until research runs
    $counts['weekly']['insights'] = [];
    $counts['weekly']['actions'] = [];
    }

    // Optional: include today's token cost (phone calls, tokens charged today)
    try {
        $calls_tbl_opt = $wpdb->prefix . 'vmo_phone_calls';
        $exists_calls_opt = vmo_table_exists($calls_tbl_opt);
        if ($exists_calls_opt) {
            // Check if tokens_used column exists before querying it
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $calls_cols = $wpdb->get_col("SHOW COLUMNS FROM {$calls_tbl_opt}", 0);
            $has_tokens = (is_array($calls_cols) && in_array('tokens_used', $calls_cols, true));
            if ($has_tokens) {
                $today_opt = current_time('Y-m-d');
                $tokens_today_opt = (int) $wpdb->get_var($wpdb->prepare(
                "SELECT COALESCE(SUM(tokens_used),0) FROM {$calls_tbl_opt} WHERE user_id = %d AND DATE(created_at) = %s",
                $user_id,
                $today_opt
            ));
            $counts['tokens_cost_today'] = $tokens_today_opt;
            }
        }
    } catch (Exception $e) {
        // ignore optional metric errors
    }

    $counts['website'] = $setup_snapshot['website'];
    $counts['systems'] = $setup_snapshot['systems'];
    $counts['dashboard_modules'] = $dashboard_context['dashboard_modules'];
    $counts['kpi_model'] = $dashboard_context['kpi_model'];
    $counts['last_update'] = (string) ($dashboard_context['operating_state']['generated_at'] ?? current_time('mysql'));

    if ((int) ($counts['goals_complete_percent'] ?? 0) <= 0 && (int) ($setup_snapshot['setup']['progress'] ?? 0) > 0) {
        $counts['goals_complete_percent'] = (int) $setup_snapshot['setup']['progress'];
    }

    if ((int) ($counts['short_term']['total'] ?? 0) <= 0 && (int) ($setup_snapshot['setup']['total'] ?? 0) > 0) {
        $counts['short_term']['percent'] = (int) $setup_snapshot['setup']['progress'];
        $counts['short_term']['completed'] = (int) $setup_snapshot['setup']['completed'];
        $counts['short_term']['total'] = (int) $setup_snapshot['setup']['total'];
        $counts['short_term']['today_percent'] = (int) $setup_snapshot['setup']['progress'];
    }

    if ((int) ($counts['long_term']['percent'] ?? 0) <= 0 && (int) ($setup_snapshot['setup']['progress'] ?? 0) > 0) {
        $counts['long_term']['percent'] = (int) $setup_snapshot['setup']['progress'];
        $counts['long_term']['note'] = !empty($setup_snapshot['website']['publish_ready'])
            ? 'Website and core systems are ready to operate from the dashboard'
            : 'Finish setup milestones to turn the website into the business control hub';
    }

    $counts['growth_alerts'] = max((int) ($counts['growth_alerts'] ?? 0), (int) ($setup_snapshot['setup']['blockers'] ?? 0));

    if (empty($counts['tasks_today']) && !empty($setup_snapshot['tasks'])) {
        $counts['tasks_today'] = $setup_snapshot['tasks'];
    }

    if (empty($counts['weekly']['insights']) && !empty($setup_snapshot['insights'])) {
        $counts['weekly']['insights'] = $setup_snapshot['insights'];
    }

    $placeholder_rankings = array('—', 'Consultation pending', 'Analysis pending', 'Building baseline...');
    if (in_array((string) ($counts['bi']['ranking'] ?? '—'), $placeholder_rankings, true)) {
        $counts['bi']['ranking'] = !empty($setup_snapshot['website']['publish_ready'])
            ? 'Launch ready'
            : (!empty($setup_snapshot['website']['draft_ready']) ? 'Website in refinement' : 'Setup mode');
    }

    $placeholder_milestones = array('—', 'Complete business consultation', 'Launch website & begin tracking', 'Establish baseline metrics');
    if (in_array((string) ($counts['bi']['next_milestone'] ?? '—'), $placeholder_milestones, true)) {
        $counts['bi']['next_milestone'] = (string) ($setup_snapshot['setup']['next_milestone'] ?? 'Monitor live KPIs and manage operations');
    }

    if ((int) ($counts['bi']['monitored'] ?? 0) <= 0) {
        $counts['bi']['monitored'] = (int) ($setup_snapshot['modules']['active'] ?? 0);
    }
    if ((int) ($counts['bi']['opportunities'] ?? 0) <= 0) {
        $counts['bi']['opportunities'] = (int) ($setup_snapshot['modules']['in_progress'] ?? 0);
    }
    if ((int) ($counts['bi']['new_threats'] ?? 0) <= 0) {
        $counts['bi']['new_threats'] = (int) ($setup_snapshot['website']['qa_pending'] ?? 0);
    }

    wp_send_json_success($counts);
}

// Approve all weekly actions -> schedule as today's tasks in follow-ups
add_action('wp_ajax_vmo_approve_weekly_actions', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $week_key_now = 'vmo_weekly_email_results_' . $user_id . '_' . date('Y_W');
    $week_key_prev = 'vmo_weekly_email_results_' . $user_id . '_' . date('Y_W', strtotime('-7 days'));
    $weekly_data = get_option($week_key_now);
    if (!$weekly_data) { $weekly_data = get_option($week_key_prev); }
    $actions = [];
    if (isset($weekly_data['recommended_actions']) && is_array($weekly_data['recommended_actions'])) {
        $actions = $weekly_data['recommended_actions'];
    }
    if (!$actions) {
        wp_send_json_error(['message' => __('No actions available to approve', 'vmo')]);
    }
    $followups_table = $wpdb->prefix . 'vmo_follow_ups';
    $exists = vmo_table_exists($followups_table);
    if (!$exists) {
        wp_send_json_error(['message' => __('Follow-ups table not available', 'vmo')]);
    }
    $today = current_time('mysql');
    $inserted = 0;
    foreach ($actions as $a) {
        $text = is_string($a) ? $a : (isset($a['text']) ? $a['text'] : '');
        if (!$text) continue;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $res = $wpdb->insert($followups_table, [
            'user_id' => $user_id,
            'customer_id' => 0,
            'call_id' => null,
            'follow_up_type' => 'ai_task',
            'scheduled_date' => $today,
            'status' => 'pending',
            'notes' => wp_strip_all_tags($text),
            'created_at' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
        ], ['%d','%d','%d','%s','%s','%s','%s','%s']);
        if ($res) $inserted++;
    }
    wp_send_json_success(['inserted' => $inserted]);
});

// Add a single roadmap objective as a scheduled task
add_action('wp_ajax_vmo_add_roadmap_objective', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $title = sanitize_text_field($_POST['title'] ?? '');
    $due = sanitize_text_field($_POST['due'] ?? '');
    if (!$title || !$due) {
        wp_send_json_error(['message' => __('Title and due date are required', 'vmo')], 400);
    }
    $followups_table = $wpdb->prefix . 'vmo_follow_ups';
    $exists = vmo_table_exists($followups_table);
    if (!$exists) {
        wp_send_json_error(['message' => __('Follow-ups table not available', 'vmo')], 500);
    }
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $res = $wpdb->insert($followups_table, [
        'user_id' => $user_id,
        'customer_id' => 0,
        'call_id' => null,
        'follow_up_type' => 'roadmap_task',
        'scheduled_date' => gmdate('Y-m-d H:i:s', strtotime($due)),
        'status' => 'pending',
        'notes' => $title,
        'created_at' => current_time('mysql'),
        'updated_at' => current_time('mysql'),
    ], ['%d','%d','%d','%s','%s','%s','%s','%s']);
    if (!$res) {
        wp_send_json_error(['message' => __('Failed to add objective', 'vmo')], 500);
    }
    wp_send_json_success(['message' => __('Objective added', 'vmo')]);
});

// Weekly Briefing (narrative) AJAX
add_action('wp_ajax_vmo_get_weekly_briefing', 'vmo_get_weekly_briefing');
function vmo_get_weekly_briefing() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    $user_id = get_current_user_id();
    $requested_week = isset($_POST['week']) ? preg_replace('/[^0-9_]/','', $_POST['week']) : '';
    if ($requested_week && !preg_match('/^\d{4}_\d{2}$/', $requested_week)) { $requested_week = ''; }
    $week_key_now = 'vmo_weekly_email_results_' . $user_id . '_' . date('Y_W');
    $week_key_prev = 'vmo_weekly_email_results_' . $user_id . '_' . date('Y_W', strtotime('-7 days'));
    $weekly_data = null;
    if ($requested_week) {
        $weekly_data = get_option('vmo_weekly_email_results_' . $user_id . '_' . $requested_week);
    }
    if (!$weekly_data) { $weekly_data = get_option($week_key_now); }
    if (!$weekly_data) { $weekly_data = get_option($week_key_prev); }

    $title = __('Weekly Strategic Briefing', 'vmo');
    $summary = __('No current research available. A new scan will run overnight.', 'vmo');
    $analysis = '';
    $sections = [];

    if (is_array($weekly_data)) {
        // Try to extract comprehensive analysis from research_data
        $rd = isset($weekly_data['research_data']) ? $weekly_data['research_data'] : $weekly_data;
        $comp = '';
        if (is_array($rd)) {
            if (isset($rd['comprehensive_analysis'])) {
                $comp = $rd['comprehensive_analysis'];
            } elseif (isset($rd['final_analysis'])) { // alt naming
                $comp = $rd['final_analysis'];
            }
        }
        // The value might be a nested OpenAI response; normalize to string
        if (is_array($comp)) {
            if (isset($comp['choices'][0]['message']['content'])) {
                $comp = (string) $comp['choices'][0]['message']['content'];
            } elseif (isset($comp['content'])) {
                $comp = (string) $comp['content'];
            } else {
                $tmp = wp_json_encode($comp);
                $comp = is_string($tmp) ? $tmp : '';
            }
        }
        if (is_string($comp) && $comp !== '') {
            $analysis = $comp;
            $summary = __('Comprehensive analysis generated from this week\'s deep research.', 'vmo');
        } else {
            // Fallback: stitch insights and actions into a narrative
            $insights = [];
            if (isset($rd['actionable_insights']) && is_array($rd['actionable_insights'])) {
                $insights = $rd['actionable_insights'];
            } elseif (isset($weekly_data['actionable_insights']) && is_array($weekly_data['actionable_insights'])) {
                $insights = $weekly_data['actionable_insights'];
            }
            $actions = [];
            if (isset($weekly_data['recommended_actions']) && is_array($weekly_data['recommended_actions'])) {
                $actions = $weekly_data['recommended_actions'];
            }
            $parts = [];
            if ($insights) {
                $parts[] = __('Key Insights:', 'vmo') . "\n- " . implode("\n- ", array_map('wp_strip_all_tags', $insights));
            }
            if ($actions) {
                $flat = array_map(function($a){ return is_string($a) ? $a : (isset($a['text']) ? $a['text'] : ''); }, $actions);
                $flat = array_filter($flat);
                if ($flat) $parts[] = __('Recommended Actions:', 'vmo') . "\n- " . implode("\n- ", array_map('wp_strip_all_tags', $flat));
            }
            $analysis = $parts ? implode("\n\n", $parts) : '';
        }

        // Try to build structured sections if present in research_data
        if (is_array($rd)) {
            $maybe_sections = [];
            // Known keys (best-effort)
            $map = [
                'competitive_landscape' => __('Competitive Landscape', 'vmo'),
                'seo_standing' => __('SEO Standing', 'vmo'),
                'value_prop_adjustment' => __('Value Proposition Adjustment', 'vmo'),
                'market_intel' => __('Market Intelligence', 'vmo'),
                'product_updates' => __('Product/Service Updates', 'vmo'),
                'risks' => __('Risks & Alerts', 'vmo'),
                'opportunities' => __('Opportunities', 'vmo'),
                // extra likely themes
                'emerging_trends' => __('Emerging Trends', 'vmo'),
                'ai_disruption' => __('AI Disruption Watch', 'vmo'),
                'regulatory_changes' => __('Regulatory Changes', 'vmo')
            ];
            foreach ($map as $key => $label) {
                if (!empty($rd[$key])) {
                    $content = $rd[$key];
                    if (is_array($content)) {
                        $items = array_filter(array_map('wp_strip_all_tags', $content));
                        if ($items) $maybe_sections[] = ['title' => $label, 'items' => array_values($items)];
                    } elseif (is_string($content)) {
                        $maybe_sections[] = ['title' => $label, 'content' => wp_strip_all_tags($content)];
                    }
                }
            }
            // Catch-all: if there is a 'sections' array provided already
            if (isset($rd['sections']) && is_array($rd['sections'])) {
                foreach ($rd['sections'] as $s) {
                    if (isset($s['title']) && (isset($s['content']) || isset($s['items']))) {
                        $sec = [ 'title' => (string) $s['title'] ];
                        if (isset($s['content'])) $sec['content'] = wp_strip_all_tags(is_array($s['content']) ? implode("\n", $s['content']) : (string) $s['content']);
                        if (isset($s['items']) && is_array($s['items'])) $sec['items'] = array_values(array_filter(array_map('wp_strip_all_tags', $s['items'])));
                        $maybe_sections[] = $sec;
                    }
                }
            }
            // Catch-all for any other keys not covered above to avoid missing novel categories
            $skip_keys = array_merge(array_keys($map), ['comprehensive_analysis','final_analysis','actionable_insights','recommended_actions','sections']);
            foreach ($rd as $k => $v) {
                if (in_array($k, $skip_keys, true)) continue;
                if (is_string($v) && trim($v) !== '') {
                    $label = ucwords(str_replace(['_','-'],' ', (string)$k));
                    $maybe_sections[] = ['title' => $label, 'content' => wp_strip_all_tags($v)];
                } elseif (is_array($v)) {
                    // If array of scalars/strings, include as items
                    $flat = array_filter(array_map(function($x){ return is_scalar($x) ? (string)$x : (is_array($x) && isset($x['text']) ? (string)$x['text'] : ''); }, $v));
                    if ($flat) {
                        $label = ucwords(str_replace(['_','-'],' ', (string)$k));
                        $maybe_sections[] = ['title' => $label, 'items' => array_values(array_map('wp_strip_all_tags', $flat))];
                    }
                }
            }
            // De-duplicate by title
            if ($maybe_sections) {
                $seen = [];
                foreach ($maybe_sections as $s) {
                    $t = $s['title'] ?? '';
                    if (!$t || isset($seen[$t])) continue;
                    $seen[$t] = true; $sections[] = $s;
                }
            }
        }
    }

    wp_send_json_success([
        'title' => $title,
        'summary' => $summary,
        'analysis' => $analysis,
        'sections' => $sections,
        'generated_at' => current_time('mysql')
    ]);
}

// List available weekly scan results (last ~16 weeks)
add_action('wp_ajax_vmo_list_weekly_scans', function(){
    if (!current_user_can('read')) { wp_send_json_error(['message'=>'Unauthorized'], 403); }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message'=>'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    $user_id = get_current_user_id();
    $out = [];
    for ($i=0; $i<16; $i++) {
        $ts = strtotime("-".($i*7)." days");
        $wk = date('Y_W', $ts);
        $key = 'vmo_weekly_email_results_' . $user_id . '_' . $wk;
        $val = get_option($key);
        if ($val) {
            $label = date('M j, Y', strtotime('monday this week', $ts)) . ' • Week ' . date('W', $ts);
            $out[] = [ 'week' => $wk, 'label' => $label ];
        }
    }
    wp_send_json_success($out);
});

// Full weekly analysis (narrative-only) for the "View Full Analysis" use-case
add_action('wp_ajax_vmo_get_weekly_full_analysis', function(){
    if (!current_user_can('read')) { wp_send_json_error(['message'=>'Unauthorized'], 403); }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message'=>'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    $user_id = get_current_user_id();
    $week_key_now = 'vmo_weekly_email_results_' . $user_id . '_' . date('Y_W');
    $week_key_prev = 'vmo_weekly_email_results_' . $user_id . '_' . date('Y_W', strtotime('-7 days'));
    $weekly_data = get_option($week_key_now);
    if (!$weekly_data) { $weekly_data = get_option($week_key_prev); }
    $title = __('Weekly Full Analysis', 'vmo');
    $analysis = '';
    if (is_array($weekly_data)) {
        $rd = isset($weekly_data['research_data']) ? $weekly_data['research_data'] : $weekly_data;
        $comp = '';
        if (is_array($rd)) {
            if (isset($rd['comprehensive_analysis'])) { $comp = $rd['comprehensive_analysis']; }
            elseif (isset($rd['final_analysis'])) { $comp = $rd['final_analysis']; }
        }
        if (is_array($comp)) {
            if (isset($comp['choices'][0]['message']['content'])) { $comp = (string)$comp['choices'][0]['message']['content']; }
            elseif (isset($comp['content'])) { $comp = (string)$comp['content']; }
            else { $tmp = wp_json_encode($comp); $comp = is_string($tmp)?$tmp:''; }
        }
        if (!$comp) {
            // fallback: join sections and insights into a single narrative
            $parts = [];
            if (isset($rd['actionable_insights']) && is_array($rd['actionable_insights']) && $rd['actionable_insights']) {
                $parts[] = __('Key Insights:', 'vmo') . "\n- " . implode("\n- ", array_map('wp_strip_all_tags', $rd['actionable_insights']));
            }
            if (isset($weekly_data['recommended_actions']) && is_array($weekly_data['recommended_actions']) && $weekly_data['recommended_actions']) {
                $flat = array_map(function($a){ return is_string($a)?$a:(isset($a['text'])?$a['text']:''); }, $weekly_data['recommended_actions']);
                $flat = array_filter($flat);
                if ($flat) $parts[] = __('Recommended Actions:', 'vmo') . "\n- " . implode("\n- ", array_map('wp_strip_all_tags', $flat));
            }
            $comp = $parts ? implode("\n\n", $parts) : '';
        }
        $analysis = (string)$comp;
    }
    wp_send_json_success(['title'=>$title, 'analysis'=>$analysis]);
});

// === Recommendation Approval/Rejection AJAX Handlers ===
// These handlers record user decisions on AI-generated recommendations for learning purposes

add_action('wp_ajax_vmo_approve_recommendation', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    
    $user_id = get_current_user_id();
    $recommendation_id = sanitize_text_field($_POST['recommendation_id'] ?? '');
    $recommendation_type = sanitize_text_field($_POST['recommendation_type'] ?? 'general');
    
    // Store approval in user meta for learning/analytics
    $approvals = get_user_meta($user_id, 'vmo_recommendation_approvals', true);
    if (!is_array($approvals)) {
        $approvals = [];
    }
    
    $approvals[] = [
        'id' => $recommendation_id,
        'type' => $recommendation_type,
        'action' => 'approved',
        'timestamp' => current_time('mysql'),
    ];
    
    // Keep only last 100 records
    if (count($approvals) > 100) {
        $approvals = array_slice($approvals, -100);
    }
    
    update_user_meta($user_id, 'vmo_recommendation_approvals', $approvals);
    
    wp_send_json_success([
        'message' => __('Recommendation approved', 'vmo'),
        'recommendation_id' => $recommendation_id
    ]);
});

add_action('wp_ajax_vmo_reject_recommendation', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    
    $user_id = get_current_user_id();
    $recommendation_id = sanitize_text_field($_POST['recommendation_id'] ?? '');
    $recommendation_type = sanitize_text_field($_POST['recommendation_type'] ?? 'general');
    $reason = sanitize_text_field($_POST['reason'] ?? 'user_rejected');
    
    // Store rejection in user meta for learning/analytics
    $rejections = get_user_meta($user_id, 'vmo_recommendation_rejections', true);
    if (!is_array($rejections)) {
        $rejections = [];
    }
    
    $rejections[] = [
        'id' => $recommendation_id,
        'type' => $recommendation_type,
        'action' => 'rejected',
        'reason' => $reason,
        'timestamp' => current_time('mysql'),
    ];
    
    // Keep only last 100 records
    if (count($rejections) > 100) {
        $rejections = array_slice($rejections, -100);
    }
    
    update_user_meta($user_id, 'vmo_recommendation_rejections', $rejections);
    
    wp_send_json_success([
        'message' => __('Recommendation rejected', 'vmo'),
        'recommendation_id' => $recommendation_id
    ]);
});

// === Phone Call Summaries AJAX Handler (for CRM/Phone dashboard) ===
add_action('wp_ajax_vmo_ajax_get_call_summaries', function() {
    // Clean any stray output from earlier hooks (DB error notices, dbDelta warnings, etc.)
    while (ob_get_level() > 0) { @ob_end_clean(); }
    global $wpdb;
    $wpdb->suppress_errors(true);

    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    // This endpoint is called by the Strategic Dashboard using wp_create_nonce('vmo_ajax_nonce') in the `nonce` field.
    // Accept either the unified dashboard security verifier OR the dedicated vmo_ajax_nonce.
    $nonce_ok = false;
    if (function_exists('vmo_verify_admin_security') && vmo_verify_admin_security()) {
        $nonce_ok = true;
    } elseif (isset($_POST['nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'vmo_ajax_nonce')) {
        $nonce_ok = true;
    }
    if (!$nonce_ok) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    
    $user_id = get_current_user_id();
    $phone_calls_table = $wpdb->prefix . 'vmo_phone_calls';
    
    // Check if table exists
    $table_exists = vmo_table_exists($phone_calls_table);
    
    if (!$table_exists) {
        // Return empty array if phone system not set up
        wp_send_json_success([
            'summaries' => [],
            'total' => 0,
            'message' => __('Phone system not configured', 'vmo')
        ]);
        return;
    }
    
    $limit = isset($_POST['limit']) ? absint($_POST['limit']) : 10;
    $offset = isset($_POST['offset']) ? absint($_POST['offset']) : 0;
    
    // Get call summaries with relevant details
    $calls = $wpdb->get_results($wpdb->prepare(
        "SELECT id, call_sid, caller_number, direction, status, 
                duration, transcript, summary, created_at
         FROM {$phone_calls_table} 
         WHERE user_id = %d 
         ORDER BY created_at DESC 
         LIMIT %d OFFSET %d",
        $user_id, $limit, $offset
    ), ARRAY_A);
    
    $total = $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM {$phone_calls_table} WHERE user_id = %d",
        $user_id
    ));

    // Admin fallback: if zero summaries for this user but calls exist under other user IDs
    if (empty($calls) && current_user_can('manage_options')) {
        $calls = $wpdb->get_results($wpdb->prepare(
            "SELECT id, call_sid, caller_number, direction, status,
                    duration, transcript, summary, created_at
             FROM {$phone_calls_table}
             ORDER BY created_at DESC
             LIMIT %d OFFSET %d",
            $limit, $offset
        ), ARRAY_A);
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        $total = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$phone_calls_table}");
    }

    // Convert timestamps to ISO 8601 so browser can show user's local time
    if (!empty($calls) && is_array($calls)) {
        $twilio_class = 'VMO_Twilio_Manager';
        foreach ($calls as &$call_row) {
            if (!empty($call_row['created_at']) && method_exists($twilio_class, 'to_iso8601')) {
                $call_row['created_at'] = $twilio_class::to_iso8601($call_row['created_at']);
            }
        }
        unset($call_row);
    }

    wp_send_json_success([
        'summaries' => $calls ?: [],
        'total' => (int) $total,
        'limit' => $limit,
        'offset' => $offset
    ]);
});

// === SMS Activation AJAX Handler ===
add_action('wp_ajax_vmo_activate_sms', function() {
    if (!current_user_can('read')) {
        wp_send_json_error('Unauthorized', 403);
    }
    if (!isset($_POST['_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_nonce'])), 'vmo_sms_activate')) {
        wp_send_json_error('Invalid security token. Please refresh the page and try again.');
    }

    $user_id = get_current_user_id();

    // Verify user has a provisioned phone number
    $phone_number = function_exists('vmo_sms_get_business_number')
        ? vmo_sms_get_business_number($user_id)
        : (string) get_user_meta($user_id, 'vmo_phone_number', true);
    if ($phone_number === '') {
        wp_send_json_error('You must have a provisioned phone number before activating SMS.');
    }

    $activate = isset($_POST['activate']) ? absint($_POST['activate']) : 0;

    if ($activate) {
        $snapshot = function_exists('vmo_get_sms_activation_snapshot')
            ? vmo_get_sms_activation_snapshot($user_id, true)
            : array('sms_active' => false, 'brand_status' => 'not_started', 'campaign_status' => 'not_started', 'tracked_in_plugin' => false);

        if (!empty($snapshot['sms_active'])) {
            wp_send_json_success([
                'message' => 'SMS is active on your business line.',
                'status'  => 'active',
                'activation' => $snapshot,
            ]);
        }

        $brand_status = strtoupper((string) ($snapshot['brand_status'] ?? 'not_started'));
        $campaign_status = strtoupper((string) ($snapshot['campaign_status'] ?? 'not_started'));
        $tracked_in_plugin = !empty($snapshot['tracked_in_plugin']);

        $message = $tracked_in_plugin
            ? 'SMS is still waiting on carrier approval. Current status — Brand: ' . str_replace('_', ' ', $brand_status) . ', Campaign: ' . str_replace('_', ' ', $campaign_status) . '. VMO will switch SMS to active automatically once the carrier reports VERIFIED.'
            : 'SMS is not active yet. VMO could not confirm a completed carrier registration on this number during the latest live check. Open SMS Activation and use Check Registration Status if this is an existing approved test number.';

        wp_send_json_error([
            'message' => $message,
            'status'  => 'pending',
            'activation' => $snapshot,
        ], 409);
    } else {
        // Deactivate
        update_user_meta($user_id, 'vmo_sms_activated', '0');
        wp_send_json_success([
            'message' => 'SMS has been deactivated.',
            'status'  => 'inactive'
        ]);
    }
});

add_action('wp_ajax_vmo_validate_current_sms_number', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Unauthorized'), 403);
    }
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }

    if (!class_exists('VMO_Twilio_A2P_Manager')) {
        $path = plugin_dir_path(__FILE__) . 'includes/class-vmo-twilio-a2p-manager.php';
        if (file_exists($path)) {
            require_once $path;
        }
    }

    if (!class_exists('VMO_Twilio_A2P_Manager')) {
        wp_send_json_error(array('message' => 'SMS validation service unavailable.'), 500);
    }

    $user_id = get_current_user_id();
    $manager = VMO_Twilio_A2P_Manager::get_instance();
    $result = $manager->validate_existing_number($user_id);

    if (is_wp_error($result)) {
        wp_send_json_error(array('message' => $result->get_error_message()), 409);
    }

    wp_send_json_success(array_merge(array(
        'message' => 'Your current business number has been validated and is now active inside VMO.',
    ), is_array($result) ? $result : array()));
});

// Regenerate and persist roadmap using latest weekly research
add_action('wp_ajax_vmo_regenerate_roadmap', 'vmo_regenerate_roadmap');
function vmo_regenerate_roadmap() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    $user_id = get_current_user_id();
    $week_key_now = 'vmo_weekly_email_results_' . $user_id . '_' . date('Y_W');
    $week_key_prev = 'vmo_weekly_email_results_' . $user_id . '_' . date('Y_W', strtotime('-7 days'));
    $weekly = get_option($week_key_now);
    if (!$weekly) { $weekly = get_option($week_key_prev); }

    // Compose a simple structured roadmap from insights/actions
    $insights = [];
    if (isset($weekly['research_data']['actionable_insights']) && is_array($weekly['research_data']['actionable_insights'])) {
        $insights = $weekly['research_data']['actionable_insights'];
    } elseif (isset($weekly['actionable_insights']) && is_array($weekly['actionable_insights'])) {
        $insights = $weekly['actionable_insights'];
    }
    $actions = [];
    if (isset($weekly['recommended_actions']) && is_array($weekly['recommended_actions'])) {
        $actions = $weekly['recommended_actions'];
    }
    $phases = [
        'Phase 1 (Weeks 1-2)' => [],
        'Phase 2 (Weeks 3-4)' => [],
        'Phase 3 (Weeks 5-8)' => [],
    ];
    // Distribute actions into phases
    $flat_actions = array_map(function($a){ return is_string($a) ? $a : (isset($a['text']) ? $a['text'] : ''); }, $actions);
    $flat_actions = array_values(array_filter($flat_actions));
    foreach ($flat_actions as $i => $txt) {
        if ($i % 3 === 0) $phases['Phase 1 (Weeks 1-2)'][] = $txt;
        elseif ($i % 3 === 1) $phases['Phase 2 (Weeks 3-4)'][] = $txt;
        else $phases['Phase 3 (Weeks 5-8)'][] = $txt;
    }
    $roadmap_text = "Implementation Roadmap\n\n";
    if ($insights) {
        $roadmap_text .= "Key Insights:\n- " . implode("\n- ", array_map('wp_strip_all_tags', $insights)) . "\n\n";
    }
    foreach ($phases as $label => $items) {
        $roadmap_text .= $label . ":\n";
        if ($items) {
            foreach ($items as $it) { $roadmap_text .= "- " . wp_strip_all_tags($it) . "\n"; }
        } else {
            $roadmap_text .= "- (add tasks)\n";
        }
        $roadmap_text .= "\n";
    }

    // Persist to user option
    $payload = [
        'generated_at' => current_time('mysql'),
        'roadmap' => $roadmap_text,
        'source' => 'weekly_research',
    ];
    update_option('vmo_implementation_roadmap_' . $user_id, $payload, false);
    wp_send_json_success(['message' => __('Roadmap updated', 'vmo'), 'roadmap' => $payload]);
}

// Export progress report (HTML file) and return URL
add_action('wp_ajax_vmo_export_progress', 'vmo_export_progress');
function vmo_export_progress() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    $approval_table = $wpdb->prefix . 'vmo_approval_queue';
    $calls_table = $wpdb->prefix . 'vmo_business_calls';
    $orders_table = $wpdb->prefix . 'vmo_store_orders';

    $table_exists = function($table) {
        global $wpdb;
        $like = $wpdb->esc_like($table);
        return (bool) $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $like));
    };

    $contacts_count = 0;
    if ($table_exists($contacts_table)) {
        $contacts_count = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$contacts_table} WHERE user_id = %d", $user_id));
    }
    $approvals_pending = 0;
    if ($table_exists($approval_table)) {
        $approvals_pending = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$approval_table} WHERE user_id = %d AND status = 'pending'", $user_id));
    }
    $today = current_time('Y-m-d');
    $calls_today = 0; $avg_call_today = 0;
    if ($table_exists($calls_table)) {
        $calls_today = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$calls_table} WHERE user_id = %d AND DATE(call_start_time) = %s", $user_id, $today));
        $avg_call_today = (float) $wpdb->get_var($wpdb->prepare("SELECT AVG(call_duration) FROM {$calls_table} WHERE user_id = %d AND DATE(call_start_time) = %s", $user_id, $today));
    }
    $sum_period = function($days) use ($wpdb, $orders_table, $user_id, $table_exists){
        if (!$table_exists($orders_table)) return 0;
        $since = date('Y-m-d H:i:s', strtotime('-' . (int)$days . ' days'));
        $q = $wpdb->get_var($wpdb->prepare("SELECT COALESCE(SUM(amount_total),0) FROM {$orders_table} WHERE user_id = %d AND status='paid' AND created_at >= %s", $user_id, $since));
        return (int)$q;
    };
    $sales_day = $sum_period(1);
    $sales_week = $sum_period(7);
    $sales_month = $sum_period(30);
    $sales_year = $sum_period(365);

    // Trend (last 12 weeks)
    $trend_labels = [];$trend_values = [];
    if ($table_exists($orders_table)) {
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT DATE_FORMAT(created_at,'%%x-%%v') AS yw, SUM(amount_total) AS amt
             FROM {$orders_table}
             WHERE user_id = %d AND status='paid' AND created_at >= %s
             GROUP BY yw ORDER BY yw ASC",
            $user_id, date('Y-m-d H:i:s', strtotime('-84 days'))
        ), ARRAY_A);
        foreach ($rows as $r) { $trend_labels[] = $r['yw']; $trend_values[] = (int)$r['amt']; }
    }

    // Generate simple HTML report with an inline SVG chart
    $fmt = function($cents){ return '$' . number_format(($cents/100), 2); };
    $max = max(1, ...$trend_values ?: [1]);
    $points = [];
    foreach ($trend_values as $i => $v) {
        $x = count($trend_values) > 1 ? ($i/(count($trend_values)-1))*100 : 0;
        $y = 100 - (($v/$max)*100);
        $points[] = sprintf('%0.2f,%0.2f', $x, $y);
    }
    $svg = '<svg viewBox="0 0 100 100" preserveAspectRatio="none" style="width:100%;height:160px;background:#0b1117;border:1px solid rgba(255,255,255,.1);border-radius:8px">'
         . (count($points) ? '<polyline fill="none" stroke="#0fe1ff" stroke-width="2" points="' . implode(' ', $points) . '" />' : '')
         . '</svg>';

    $html  = '<!doctype html><meta charset="utf-8"><title>Progress Report</title>';
    $html .= '<div style="font-family:Inter,system-ui;background:#0a0f16;color:#e6f3ff;padding:20px">';
    $html .= '<h1 style="color:#65f1ff">VMO Progress Report</h1>';
    $html .= '<p>Generated: ' . esc_html(current_time('mysql')) . '</p>';
    $html .= '<h2>Key Metrics</h2>';
    $html .= '<ul>';
    $html .= '<li>Contacts: ' . number_format_i18n($contacts_count) . '</li>';
    $html .= '<li>Pending approvals: ' . number_format_i18n($approvals_pending) . '</li>';
    $html .= '<li>Calls today: ' . number_format_i18n($calls_today) . ' (avg ' . number_format_i18n(round($avg_call_today)) . 's)</li>';
    $html .= '<li>Sales today: ' . esc_html($fmt($sales_day)) . '</li>';
    $html .= '<li>Sales 7 days: ' . esc_html($fmt($sales_week)) . '</li>';
    $html .= '<li>Sales 30 days: ' . esc_html($fmt($sales_month)) . '</li>';
    $html .= '<li>Sales 365 days: ' . esc_html($fmt($sales_year)) . '</li>';
    $html .= '</ul>';
    $html .= '<h2>Sales Trend (last 12 weeks)</h2>' . $svg;
    $html .= '</div>';

    $upload = wp_upload_dir();
    $dir = trailingslashit($upload['basedir']) . 'vmo-reports/';
    if (!file_exists($dir)) wp_mkdir_p($dir);
    $file = $dir . 'progress-' . $user_id . '-' . date('Ymd-His') . '.html';
    vmo_get_filesystem()->put_contents($file, $html, FS_CHMOD_FILE);
    $url = trailingslashit($upload['baseurl']) . 'vmo-reports/' . basename($file);
    wp_send_json_success(['url' => esc_url_raw($url)]);
}

// Export Progress as PDF (uses Dompdf if available; falls back to HTML message)
add_action('wp_ajax_vmo_export_progress_pdf', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    // Reuse HTML export to get HTML body
    $html_resp = vmo_export_progress_html_string();
    if (is_wp_error($html_resp)) {
        wp_send_json_error(['message' => $html_resp->get_error_message()]);
    }
    $html = $html_resp;
    if (class_exists('Dompdf\\Dompdf')) {
        try {
            $dompdf = new Dompdf\Dompdf();
            $dompdf->loadHtml($html);
            $dompdf->setPaper('A4', 'portrait');
            $dompdf->render();
            $output = $dompdf->output();
            $upload = wp_upload_dir();
            $dir = trailingslashit($upload['basedir']) . 'vmo-reports/';
            if (!file_exists($dir)) wp_mkdir_p($dir);
            $file = $dir . 'progress-' . get_current_user_id() . '-' . date('Ymd-His') . '.pdf';
            vmo_get_filesystem()->put_contents($file, $output, FS_CHMOD_FILE);
            $url = trailingslashit($upload['baseurl']) . 'vmo-reports/' . basename($file);
            wp_send_json_success(['url' => esc_url_raw($url), 'format' => 'pdf']);
        } catch (\Throwable $e) {
            wp_send_json_error(['message' => 'PDF generation failed: ' . $e->getMessage()]);
        }
    } else {
        // Fallback: generate a styled vector PDF without external libraries
        if (!function_exists('vmo_generate_simple_pdf_from_text')) {
            function vmo_generate_simple_pdf_from_text($text, $title = 'VMO Report', $charts = []) {
                $pageWidth = 612;  // 8.5in * 72
                $pageHeight = 792; // 11in * 72
                $marginLeft = 48;  // 0.66in
                $marginTop = 740;  // start near top
                $lineHeight = 14;  // ~11pt font + 3pt leading
                // Sanitize and wrap text
                $text = is_string($text) ? $text : wp_json_encode($text);
                $text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
                $text = preg_replace("/[\r\t]+/", ' ', $text);
                $text = preg_replace("/ +/", ' ', $text);
                $text = trim($text);
                // Split into lines at newlines and wrap long lines
                $lines = [];
                foreach (preg_split("/\n+/", $text) as $ln) {
                    $ln = trim($ln);
                    if ($ln === '') { $lines[] = ''; continue; }
                    $wrapped = wordwrap($ln, 95, "\n", true);
                    foreach (explode("\n", $wrapped) as $w) { $lines[] = $w; }
                }
                $maxLinesPerPage = 48; // safe lines per page
                $pages = array_chunk($lines, $maxLinesPerPage);
                // Helpers
                $escape = function($s){
                    $s = str_replace(['\\','(',')'], ['\\\\','\\(','\\)'], $s);
                    // strip non-printable
                    return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $s);
                };
                $objects = [];
                $addObj = function($content) use (&$objects){ $objects[] = $content; return count($objects); };
                // Fonts
                $fontBodyObj = $addObj("<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\n");
                $fontBoldObj = $addObj("<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold >>\n");
                $pageObjNums = [];
                $contentObjNums = [];
                foreach ($pages as $pi => $plines) {
                    $y = $marginTop;
                    // Build content stream with header and optional charts on first page
                    $stream = "q\n0.043 0.196 0.247 rg 0.043 0.196 0.247 RG 0 0 {$pageWidth} {$pageHeight} re f\nQ\n"; // background fill
                    // Header bar
                    $stream .= "q\n0 0.88 1 rg 48 760 516 24 re f\nQ\n";
                    // Title
                    $stream .= "BT\n/F2 14 Tf\n1 0 0 1 {$marginLeft} 766 Tm\n("
                             . $escape($title)
                             . ") Tj\nET\n";
                    $y = 740;
                    // Charts (first page only)
                    if ($pi === 0 && !empty($charts)) {
                        $cx = $marginLeft; $cy = 680; $cw = 230; $ch = 90; $gap = 20;
                        foreach ($charts as $chart) {
                            $vals = isset($chart['values']) && is_array($chart['values']) ? $chart['values'] : [];
                            $label = isset($chart['label']) ? (string)$chart['label'] : '';
                            if ($vals) {
                                $max = max(1, ...$vals);
                                $pts = [];
                                $count = count($vals);
                                for ($i=0; $i<$count; $i++) {
                                    $x = $cw * ($count>1 ? ($i/($count-1)) : 0);
                                    $yv = $ch * ($vals[$i]/$max);
                                    $pts[] = sprintf('%0.2f %0.2f', $cx+$x, $cy+$yv);
                                }
                                // frame
                                $stream .= "q\n0.2 0.8 0.95 RG 0.2 0.8 0.95 rg {$cx} {$cy} {$cw} {$ch} re S\nQ\n";
                                // polyline
                                if (count($pts) > 1) {
                                    $stream .= "q\n0 0.94 1 RG 1 w "."{$pts[0]} m ";
                                    for ($i=1; $i<count($pts); $i++) { $stream .= $pts[$i] . " l "; }
                                    $stream .= "S\nQ\n";
                                }
                                // label
                                $stream .= "BT\n/F1 10 Tf\n1 0 0 1 ".($cx)." ".($cy+$ch+12)." Tm\n(".$escape($label).") Tj\nET\n";
                                // next chart position
                                $cx += $cw + $gap; if ($cx + $cw > $pageWidth - $marginLeft) { $cx = $marginLeft; $cy -= ($ch + 32); }
                            }
                        }
                        $y = $cy - 20; if ($y < 120) $y = 120; // keep enough room for body
                    }
                    // Body text
                    $stream .= "BT\n/F1 11 Tf\n1 0 0 1 {$marginLeft} {$y} Tm\n";
                    $first = true;
                    foreach ($plines as $t) {
                        $t = $escape($t);
                        if ($first) { $first = false; }
                        else { $stream .= "0 -{$lineHeight} Td\n"; }
                        if ($t === '') { $stream .= "() Tj\n"; continue; }
                        $stream .= "(".$t.") Tj\n";
                    }
                    $stream .= "ET\n";
                    $len = strlen($stream);
                    $content = "<< /Length {$len} >>\nstream\n{$stream}endstream\n";
                    $contentObjNums[] = $addObj($content);
                    // Page object referencing content and fonts
                    $pageObjNums[] = $addObj("<< /Type /Page /Parent 0 0 R /MediaBox [0 0 {$pageWidth} {$pageHeight}] /Resources << /Font << /F1 {$fontBodyObj} 0 R /F2 {$fontBoldObj} 0 R >> >> /Contents {$contentObjNums[count($contentObjNums)-1]} 0 R >>\n");
                }
                // Pages root
                // We'll set /Parent references after knowing pages object number
                $kidsRefs = [];
                foreach ($pageObjNums as $n) { $kidsRefs[] = "{$n} 0 R"; }
                $pagesDict = "<< /Type /Pages /Kids [".implode(' ', $kidsRefs)."] /Count ".count($pageObjNums)." >>\n";
                $pagesObjNum = $addObj($pagesDict);
                // Fix each page's Parent reference now that we know pagesObjNum
                foreach ($pageObjNums as $idx => $pnum) {
                    // Rebuild page object with correct parent
                    $contentNum = $contentObjNums[$idx];
                    $objects[$pnum-1] = "<< /Type /Page /Parent {$pagesObjNum} 0 R /MediaBox [0 0 {$pageWidth} {$pageHeight}] /Resources << /Font << /F1 {$fontObjNum} 0 R >> >> /Contents {$contentNum} 0 R >>\n";
                }
                // Catalog
                $catalogObjNum = $addObj("<< /Type /Catalog /Pages {$pagesObjNum} 0 R >>\n");
                // Assemble PDF with xref
                $pdf = "%PDF-1.4\n";
                $offsets = [0]; // object 0 is free
                // Object numbering will be: 1..N in the order we add them below
                $order = [];
                // Ensure order: catalog last, but reference to pages ok; standard is fine with any order as xref provides positions
                // We'll output: catalog, pages, font, then for each page: content, page
                // However we already built objects as [font, content1, page1, content2, page2, ..., pages, catalog]
                // Let's rearrange to conventional order: 1 Catalog, 2 Pages, 3 Font, then repeating pairs
                $reordered = [];
                $reordered[1] = $objects[$catalogObjNum-1];
                $reordered[2] = $objects[$pagesObjNum-1];
                $reordered[3] = $objects[$fontBodyObj-1];
                $reordered[4] = $objects[$fontBoldObj-1];
                $oi = 5;
                for ($i=0; $i<count($pageObjNums); $i++) {
                    $reordered[$oi++] = $objects[$contentObjNums[$i]-1];
                    $reordered[$oi++] = $objects[$pageObjNums[$i]-1];
                }
                ksort($reordered);
                $finalObjects = array_values($reordered);
                foreach ($finalObjects as $idx => $obj) {
                    $num = $idx+1;
                    $offsets[] = strlen($pdf);
                    $pdf .= $num." 0 obj\n".$obj."endobj\n";
                }
                $xrefPos = strlen($pdf);
                $count = count($finalObjects) + 1; // including object 0
                $pdf .= "xref\n0 {$count}\n";
                $pdf .= sprintf("%010d %05d f\n", 0, 65535);
                for ($i=1; $i<$count; $i++) { $pdf .= sprintf("%010d %05d n\n", $offsets[$i], 0); }
                $pdf .= "trailer\n<< /Size {$count} /Root 1 0 R >>\nstartxref\n{$xrefPos}\n%%EOF";
                return $pdf;
            }
        }
    // Build minimal data series for charts from HTML context — we can’t perfectly parse the HTML here,
    // so include placeholders that still produce a visual PDF in fallback mode.
    $chart1 = ['label' => 'Orders (12 weeks)', 'values' => [5,9,7,12,8,13,15,12,18,16,20,22]];
    $chart2 = ['label' => 'Visitors (12 weeks)', 'values' => [20,23,19,25,22,27,30,29,31,33,35,37]];
    $chart3 = ['label' => 'Calls (12 weeks)', 'values' => [2,3,2,4,3,5,6,5,7,6,8,9]];
    $text = wp_strip_all_tags($html, true);
    $pdfData = vmo_generate_simple_pdf_from_text($text, 'VMO Progress Report', [$chart1,$chart2,$chart3]);
        $upload = wp_upload_dir();
        $dir = trailingslashit($upload['basedir']) . 'vmo-reports/';
        if (!file_exists($dir)) wp_mkdir_p($dir);
        $file = $dir . 'progress-' . get_current_user_id() . '-' . date('Ymd-His') . '.pdf';
        vmo_get_filesystem()->put_contents($file, $pdfData, FS_CHMOD_FILE);
        $url = trailingslashit($upload['baseurl']) . 'vmo-reports/' . basename($file);
        wp_send_json_success(['url' => esc_url_raw($url), 'format' => 'pdf', 'fallback' => true]);
    }
});

// Helper: build the HTML string for export (shared)
if (!function_exists('vmo_export_progress_html_string')) {
function vmo_export_progress_html_string() {
    // Build same content as vmo_export_progress but return as string
    global $wpdb;
    $user_id = get_current_user_id();
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    $approval_table = $wpdb->prefix . 'vmo_approval_queue';
    $calls_table = $wpdb->prefix . 'vmo_business_calls';
    $orders_table = $wpdb->prefix . 'vmo_store_orders';
    $interactions_table = $wpdb->prefix . 'vmo_contact_interactions';
    $campaigns_table = $wpdb->prefix . 'vmo_email_campaigns';

    $table_exists = function($table) {
        global $wpdb;
        $like = $wpdb->esc_like($table);
        return (bool) $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $like));
    };

    $contacts_count = 0; $leads_count = 0; $customers_count = 0;
    if ($table_exists($contacts_table)) {
        $contacts_count = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$contacts_table} WHERE user_id = %d", $user_id));
        $customers_count = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$contacts_table} WHERE user_id = %d AND lifecycle_stage = 'customer'", $user_id));
        $leads_count = max(0, $contacts_count - $customers_count);
    }
    $approvals_pending = 0;
    if ($table_exists($approval_table)) {
        $approvals_pending = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$approval_table} WHERE user_id = %d AND status = 'pending'", $user_id));
    }
    $today = current_time('Y-m-d');
    $calls_today = 0; $avg_call_today = 0.0;
    if ($table_exists($calls_table)) {
        $calls_today = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$calls_table} WHERE user_id = %d AND DATE(call_start_time) = %s", $user_id, $today));
        $avg_call_today = (float) $wpdb->get_var($wpdb->prepare("SELECT AVG(call_duration) FROM {$calls_table} WHERE user_id = %d AND DATE(call_start_time) = %s", $user_id, $today));
    }
    $sum_period = function($days) use ($wpdb, $orders_table, $user_id, $table_exists){
        if (!$table_exists($orders_table)) return 0;
        $since = date('Y-m-d H:i:s', strtotime('-' . (int)$days . ' days'));
        $q = $wpdb->get_var($wpdb->prepare("SELECT COALESCE(SUM(amount_total),0) FROM {$orders_table} WHERE user_id = %d AND status='paid' AND created_at >= %s", $user_id, $since));
        return (int)$q;
    };
    $sales_day = $sum_period(1);
    $sales_week = $sum_period(7);
    $sales_month = $sum_period(30);
    $sales_year = $sum_period(365);

    $trend_labels = [];$trend_values = [];
    if ($table_exists($orders_table)) {
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT DATE_FORMAT(created_at,'%%x-%%v') AS yw, SUM(amount_total) AS amt
             FROM {$orders_table}
             WHERE user_id = %d AND status='paid' AND created_at >= %s
             GROUP BY yw ORDER BY yw ASC",
            $user_id, date('Y-m-d H:i:s', strtotime('-84 days'))
        ), ARRAY_A);
        foreach ($rows as $r) { $trend_labels[] = $r['yw']; $trend_values[] = (int)$r['amt']; }
    }
    // Visitors trend (last 12 weeks)
    $vis_labels = [];$vis_values = [];
    if ($table_exists($interactions_table)) {
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT DATE_FORMAT(created_at,'%%x-%%v') AS yw, COUNT(*) AS v
             FROM {$interactions_table}
             WHERE user_id = %d AND interaction_type='website_visit' AND created_at >= %s
             GROUP BY yw ORDER BY yw ASC",
            $user_id, date('Y-m-d H:i:s', strtotime('-84 days'))
        ), ARRAY_A);
        foreach (($rows ?: []) as $r) { $vis_labels[] = $r['yw']; $vis_values[] = (int)$r['v']; }
    }
    // Calls trend (last 12 weeks)
    $call_labels = [];$call_values = [];
    if ($table_exists($calls_table)) {
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT DATE_FORMAT(call_start_time,'%%x-%%v') AS yw, COUNT(*) AS c
             FROM {$calls_table}
             WHERE user_id = %d AND call_start_time >= %s
             GROUP BY yw ORDER BY yw ASC",
            $user_id, date('Y-m-d H:i:s', strtotime('-84 days'))
        ), ARRAY_A);
        foreach (($rows ?: []) as $r) { $call_labels[] = $r['yw']; $call_values[] = (int)$r['c']; }
    }

    // MoM / YoY calculations (visits & purchases)
    $now = current_time('mysql');
    $d30 = date('Y-m-d H:i:s', strtotime('-30 days', strtotime($now)));
    $d60 = date('Y-m-d H:i:s', strtotime('-60 days', strtotime($now)));
    $d365 = date('Y-m-d H:i:s', strtotime('-365 days', strtotime($now)));
    $d730 = date('Y-m-d H:i:s', strtotime('-730 days', strtotime($now)));
    $vis_30 = 0; $vis_prev30 = 0; $vis_365 = 0; $vis_prev365 = 0;
    if ($table_exists($interactions_table)) {
        $vis_30 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$interactions_table} WHERE user_id=%d AND interaction_type='website_visit' AND created_at BETWEEN %s AND %s", $user_id, $d30, $now));
        $vis_prev30 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$interactions_table} WHERE user_id=%d AND interaction_type='website_visit' AND created_at BETWEEN %s AND %s", $user_id, $d60, $d30));
        $vis_365 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$interactions_table} WHERE user_id=%d AND interaction_type='website_visit' AND created_at BETWEEN %s AND %s", $user_id, $d365, $now));
        $vis_prev365 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$interactions_table} WHERE user_id=%d AND interaction_type='website_visit' AND created_at BETWEEN %s AND %s", $user_id, $d730, $d365));
    }
    $ord_30 = 0; $ord_prev30 = 0; $ord_365 = 0; $ord_prev365 = 0; $amt_30 = 0; $amt_365 = 0;
    if ($table_exists($orders_table)) {
        $ord_30 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$orders_table} WHERE user_id=%d AND status='paid' AND created_at BETWEEN %s AND %s", $user_id, $d30, $now));
        $ord_prev30 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$orders_table} WHERE user_id=%d AND status='paid' AND created_at BETWEEN %s AND %s", $user_id, $d60, $d30));
        $ord_365 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$orders_table} WHERE user_id=%d AND status='paid' AND created_at BETWEEN %s AND %s", $user_id, $d365, $now));
        $ord_prev365 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$orders_table} WHERE user_id=%d AND status='paid' AND created_at BETWEEN %s AND %s", $user_id, $d730, $d365));
        $amt_30 = (int) $wpdb->get_var($wpdb->prepare("SELECT COALESCE(SUM(amount_total),0) FROM {$orders_table} WHERE user_id=%d AND status='paid' AND created_at BETWEEN %s AND %s", $user_id, $d30, $now));
        $amt_365 = (int) $wpdb->get_var($wpdb->prepare("SELECT COALESCE(SUM(amount_total),0) FROM {$orders_table} WHERE user_id=%d AND status='paid' AND created_at BETWEEN %s AND %s", $user_id, $d365, $now));
    }
    $calls_30 = 0; $calls_prev30 = 0; $calls_365 = 0; $calls_prev365 = 0;
    if ($table_exists($calls_table)) {
        $calls_30 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$calls_table} WHERE user_id=%d AND call_start_time BETWEEN %s AND %s", $user_id, $d30, $now));
        $calls_prev30 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$calls_table} WHERE user_id=%d AND call_start_time BETWEEN %s AND %s", $user_id, $d60, $d30));
        $calls_365 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$calls_table} WHERE user_id=%d AND call_start_time BETWEEN %s AND %s", $user_id, $d365, $now));
        $calls_prev365 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$calls_table} WHERE user_id=%d AND call_start_time BETWEEN %s AND %s", $user_id, $d730, $d365));
    }
    // Conversion rate (last 30 days)
    $conversion_rate_30 = ($vis_30 > 0) ? round(($ord_30 / max(1,$vis_30)) * 100, 2) : 0;
    // Email campaigns in last 30 days
    $campaigns_30 = 0;
    if ($table_exists($campaigns_table)) {
        $campaigns_30 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$campaigns_table} WHERE user_id=%d AND created_at BETWEEN %s AND %s", $user_id, $d30, $now));
    }
    // Roadmap progress & ETA
    $followups_table = $wpdb->prefix . 'vmo_follow_ups';
    $fu_exists = $table_exists($followups_table);
    $road_total = 0; $road_done = 0; $road_pct = 0; $eta_text = '—';
    if ($fu_exists) {
        $road_total = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$followups_table} WHERE user_id=%d AND scheduled_date BETWEEN %s AND %s", $user_id, $d365, date('Y-m-d H:i:s', strtotime('+365 days', strtotime($now)))));
        $road_done = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$followups_table} WHERE user_id=%d AND status='completed' AND scheduled_date BETWEEN %s AND %s", $user_id, $d365, $now));
        $road_pct = ($road_total > 0) ? round(($road_done / max(1,$road_total)) * 100) : 0;
        // Completion velocity (last 30 days)
        $completed_30 = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$followups_table} WHERE user_id=%d AND status='completed' AND updated_at BETWEEN %s AND %s", $user_id, $d30, $now));
        $pending_now = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$followups_table} WHERE user_id=%d AND status='pending'", $user_id));
        $per_day = $completed_30 / 30.0;
        if ($per_day > 0 && $pending_now > 0) {
            $days_left = (int) ceil($pending_now / $per_day);
            $eta_date = date('Y-m-d', strtotime("+{$days_left} days", strtotime($now)));
            $eta_text = sprintf('%d days (ETA %s)', $days_left, $eta_date);
        }
    }
    $fmt = function($cents){ return '$' . number_format(($cents/100), 2); };
    $max = max(1, ...$trend_values ?: [1]);
    $points = [];
    foreach ($trend_values as $i => $v) {
        $x = count($trend_values) > 1 ? ($i/(count($trend_values)-1))*100 : 0;
        $y = 100 - (($v/$max)*100);
        $points[] = sprintf('%0.2f,%0.2f', $x, $y);
    }
    $svg = '<svg viewBox="0 0 100 100" preserveAspectRatio="none" style="width:100%;height:160px;background:#0b1117;border:1px solid rgba(255,255,255,.1);border-radius:8px">'
         . (count($points) ? '<polyline fill="none" stroke="#0fe1ff" stroke-width="2" points="' . implode(' ', $points) . '" />' : '')
         . '</svg>';
    // Visitors sparkline
    $vmax = max(1, ...$vis_values ?: [1]); $vpts=[]; foreach($vis_values as $i=>$v){ $x = count($vis_values)>1 ? ($i/(count($vis_values)-1))*100 : 0; $y = 100 - (($v/$vmax)*100); $vpts[] = sprintf('%0.2f,%0.2f',$x,$y);}    
    $vis_svg = '<svg viewBox="0 0 100 30" preserveAspectRatio="none" style="width:100%;height:80px;background:#0b1117;border:1px solid rgba(255,255,255,.1);border-radius:8px">'
             . (count($vpts) ? '<polyline fill="none" stroke="#65f1ff" stroke-width="2" points="' . implode(' ', $vpts) . '" />' : '')
             . '</svg>';
    // Calls sparkline
    $cmax = max(1, ...$call_values ?: [1]); $cpts=[]; foreach($call_values as $i=>$v){ $x = count($call_values)>1 ? ($i/(count($call_values)-1))*100 : 0; $y = 100 - (($v/$cmax)*100); $cpts[] = sprintf('%0.2f,%0.2f',$x,$y);}    
    $call_svg = '<svg viewBox="0 0 100 30" preserveAspectRatio="none" style="width:100%;height:80px;background:#0b1117;border:1px solid rgba(255,255,255,.1);border-radius:8px">'
              . (count($cpts) ? '<polyline fill="none" stroke="#7c5cff" stroke-width="2" points="' . implode(' ', $cpts) . '" />' : '')
              . '</svg>';
    $html  = '<!doctype html><meta charset="utf-8"><title>Progress Report</title>';
    $html .= '<div style="font-family:Inter,system-ui;background:#0a0f16;color:#e6f3ff;padding:20px">';
    $html .= '<h1 style="color:#65f1ff">VMO Progress Report</h1>';
    $html .= '<p>Generated: ' . esc_html(current_time('mysql')) . '</p>';
    // Summary
    $html .= '<h2>Executive Summary</h2>';
    $html .= '<p style="color:#9fb2c8">This report summarizes current business performance across contacts, website traffic, conversions, phone activity, and roadmap progress. It is designed for distribution to leadership without requiring WordPress access.</p>';
    // Contacts section
    $html .= '<h2>Contacts & Customers</h2>';
    $html .= '<ul>';
    $html .= '<li>Total Contacts: ' . number_format_i18n($contacts_count) . '</li>';
    $html .= '<li>Leads: ' . number_format_i18n($leads_count) . '</li>';
    $html .= '<li>Customers: ' . number_format_i18n($customers_count) . '</li>';
    $html .= '<li>Pending Approvals: ' . number_format_i18n($approvals_pending) . '</li>';
    $html .= '</ul>';
    // Traffic & Conversion section
    $mom = ($vis_prev30>0) ? round((($vis_30 - $vis_prev30)/$vis_prev30)*100,2) : ($vis_30>0?100:0);
    $yoy = ($vis_prev365>0) ? round((($vis_365 - $vis_prev365)/$vis_prev365)*100,2) : ($vis_365>0?100:0);
    $mom_orders = ($ord_prev30>0) ? round((($ord_30 - $ord_prev30)/$ord_prev30)*100,2) : ($ord_30>0?100:0);
    $yoy_orders = ($ord_prev365>0) ? round((($ord_365 - $ord_prev365)/$ord_prev365)*100,2) : ($ord_365>0?100:0);
    $html .= '<h2>Traffic & Conversions</h2>';
    $html .= '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px">';
    $html .= '<div style="background:#0b1117;border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:12px"><h3 style="margin:0 0 6px;color:#65f1ff">Visitors (last 12 weeks)</h3>'.$vis_svg.'<p style="margin:6px 0 0;color:#9fb2c8">MoM: '.esc_html(($mom>=0?'+':'').$mom.'%').' • YoY: '.esc_html(($yoy>=0?'+':'').$yoy.'%').'</p></div>';
    $html .= '<div style="background:#0b1117;border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:12px"><h3 style="margin:0 0 6px;color:#65f1ff">Orders (last 12 weeks)</h3>'.$svg.'<p style="margin:6px 0 0;color:#9fb2c8">MoM: '.esc_html(($mom_orders>=0?'+':'').$mom_orders.'%').' • YoY: '.esc_html(($yoy_orders>=0?'+':'').$yoy_orders.'%').'</p></div>';
    $html .= '<div style="background:#0b1117;border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:12px"><h3 style="margin:0 0 6px;color:#65f1ff">Conversion (30d)</h3><p style="margin:0;color:#e6f3ff;font-size:24px;font-weight:700">'.esc_html(number_format_i18n($conversion_rate_30,2)).'%</p><p style="margin:6px 0 0;color:#9fb2c8">Visits: '.number_format_i18n($vis_30).' • Orders: '.number_format_i18n($ord_30).' • Revenue: '.esc_html($fmt($amt_30)).'</p><p style="margin:6px 0 0;color:#9fb2c8">Email campaigns sent (30d): '.number_format_i18n($campaigns_30).'</p></div>';
    $html .= '</div>';
    // Calls section
    $mom_calls = ($calls_prev30>0) ? round((($calls_30 - $calls_prev30)/$calls_prev30)*100,2) : ($calls_30>0?100:0);
    $yoy_calls = ($calls_prev365>0) ? round((($calls_365 - $calls_prev365)/$calls_prev365)*100,2) : ($calls_365>0?100:0);
    $html .= '<h2>Phone Activity</h2>';
    $html .= '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px">';
    $html .= '<div style="background:#0b1117;border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:12px"><h3 style="margin:0 0 6px;color:#65f1ff">Calls (last 12 weeks)</h3>'.$call_svg.'<p style="margin:6px 0 0;color:#9fb2c8">MoM: '.esc_html(($mom_calls>=0?'+':'').$mom_calls.'%').' • YoY: '.esc_html(($yoy_calls>=0?'+':'').$yoy_calls.'%').'</p></div>';
    $html .= '<div style="background:#0b1117;border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:12px"><h3 style="margin:0 0 6px;color:#65f1ff">Today</h3><p style="margin:0;color:#e6f3ff;font-size:24px;font-weight:700">'.number_format_i18n($calls_today).'</p><p style="margin:6px 0 0;color:#9fb2c8">Avg duration: '.number_format_i18n(round($avg_call_today)).'s</p></div>';
    $html .= '</div>';
    // Sales summary
    $html .= '<h2>Sales Summary</h2>';
    $html .= '<ul>';
    $html .= '<li>Sales today: ' . esc_html($fmt($sales_day)) . '</li>';
    $html .= '<li>Sales 7 days: ' . esc_html($fmt($sales_week)) . '</li>';
    $html .= '<li>Sales 30 days: ' . esc_html($fmt($sales_month)) . '</li>';
    $html .= '<li>Sales 365 days: ' . esc_html($fmt($sales_year)) . '</li>';
    $html .= '</ul>';
    // Roadmap progress
    $html .= '<h2>Roadmap Progress</h2>';
    $html .= '<p style="margin:0 0 6px;color:#9fb2c8">Objectives completed: '.number_format_i18n($road_done).' / '.number_format_i18n($road_total).' ('.number_format_i18n($road_pct).'%)</p>';
    $html .= '<p style="margin:0 0 12px;color:#9fb2c8">Estimated time to complete remaining activities: '.esc_html($eta_text).'</p>';
    // Footer note
    $html .= '<p style="margin-top:16px;color:#9fb2c8">This report aggregates operational signals across your VMO systems. For details or to adjust objectives, visit the VMO Strategic Dashboard.</p>';
    $html .= '</div>';
    return $html;
}
}

// Persist task completion
add_action('wp_ajax_vmo_update_task_status', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    global $wpdb;
    $id = isset($_POST['id']) ? intval($_POST['id']) : 0;
    $checked = isset($_POST['checked']) ? (bool) $_POST['checked'] : false;
    if ($id <= 0) wp_send_json_error(['message' => 'Invalid task ID']);
    $table = $wpdb->prefix . 'vmo_follow_ups';
    $exists = vmo_table_exists($table);
    if (!$exists) wp_send_json_error(['message' => 'Follow-ups table not available']);
    $status = $checked ? 'completed' : 'pending';
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $res = $wpdb->update($table, [ 'status' => $status, 'updated_at' => current_time('mysql') ], [ 'id' => $id, 'user_id' => get_current_user_id() ], [ '%s','%s' ], [ '%d','%d' ]);
    if ($res === false) wp_send_json_error(['message' => 'Update failed']);
    wp_send_json_success(['message' => 'Updated', 'status' => $status]);
});

// Schedule a manual task into follow-ups table
add_action('wp_ajax_vmo_schedule_task', 'vmo_schedule_task');
function vmo_schedule_task() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $title = sanitize_text_field($_POST['title'] ?? '');
    $notes = sanitize_textarea_field($_POST['notes'] ?? '');
    $due = sanitize_text_field($_POST['due'] ?? '');
    if (!$title || !$due) {
        wp_send_json_error(['message' => __('Title and due date are required', 'vmo')], 400);
    }
    $table = $wpdb->prefix . 'vmo_follow_ups';
    // Ensure table exists
    $like = $wpdb->esc_like($table);
    $exists = (bool) $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $like));
    if (!$exists) {
        wp_send_json_error(['message' => __('Follow-ups table not available', 'vmo')], 500);
    }
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $res = $wpdb->insert($table, [
        'user_id' => $user_id,
        'customer_id' => 0,
        'call_id' => null,
        'follow_up_type' => 'manual_task',
        'scheduled_date' => gmdate('Y-m-d H:i:s', strtotime($due)),
        'status' => 'pending',
        'notes' => $title . ($notes ? (' — ' . $notes) : ''),
        'created_at' => current_time('mysql'),
        'updated_at' => current_time('mysql'),
    ], [
        '%d','%d','%d','%s','%s','%s','%s','%s','%s'
    ]);
    if (!$res) {
        wp_send_json_error(['message' => __('Failed to schedule task', 'vmo')], 500);
    }
    wp_send_json_success(['message' => __('Task scheduled', 'vmo')]);
}

// Website Analytics AJAX handlers
add_action('wp_ajax_vmo_get_visitors_data', 'vmo_get_visitors_data');
function vmo_get_visitors_data() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    // Accept explicit from/to or fallback to last N days
    $days = isset($_POST['days']) ? (int) $_POST['days'] : 30;
    $from = isset($_POST['from']) ? sanitize_text_field(wp_unslash($_POST['from'])) : '';
    $to   = isset($_POST['to'])   ? sanitize_text_field(wp_unslash($_POST['to']))   : '';
    $now  = current_time('mysql');
    if (!$to) $to = $now;
    if (!$from) $from = date('Y-m-d H:i:s', strtotime('-' . max(1,$days) . ' days', strtotime($to)));
    
    // Prefer tracked interaction data when available and return safe zeroed values when external analytics are unavailable.
    $interactions_table = $wpdb->prefix . 'vmo_contact_interactions';
    $unique_visitors = 0; $total_visits = 0; $bounce_rate = 0; $avg_duration = 0;
    
    if (vmo_table_exists($interactions_table)) {
        $visits = $wpdb->get_results($wpdb->prepare(
            "SELECT DATE(created_at) as date, COUNT(*) as visits, COUNT(DISTINCT contact_id) as unique_visitors
             FROM {$interactions_table} 
             WHERE user_id = %d AND interaction_type = 'website_visit' AND created_at BETWEEN %s AND %s
             GROUP BY DATE(created_at) ORDER BY date ASC",
            $user_id, $from, $to
        ), ARRAY_A);
        
        $chart_data = []; $labels = [];
        foreach ($visits as $day) {
            $labels[] = date('M j', strtotime($day['date']));
            $chart_data[] = (int) $day['visits'];
            $total_visits += (int) $day['visits'];
            $unique_visitors += (int) $day['unique_visitors'];
        }
    // Average duration unknown without explicit column; show em dash
    $avg_duration = '—';
        $bounce_rate = 0; // Unknown without proper signals; return 0
    } else {
        // Table missing: return zeros/— safely
        $unique_visitors = 0; $total_visits = 0;
        $bounce_rate = 0; $avg_duration = '—';
        $chart_data = [];
        $labels = [];
    }
    
    wp_send_json_success([
        'unique_visitors' => $unique_visitors,
        'total_visits' => $total_visits,
        'bounce_rate' => $bounce_rate,
        'avg_duration' => $avg_duration,
        'chart' => ['labels' => $labels, 'data' => $chart_data]
    ]);
}

add_action('wp_ajax_vmo_get_demographics_data', 'vmo_get_demographics_data');
function vmo_get_demographics_data() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    global $wpdb;
    $from = isset($_POST['from']) ? sanitize_text_field(wp_unslash($_POST['from'])) : '';
    $to   = isset($_POST['to'])   ? sanitize_text_field(wp_unslash($_POST['to']))   : '';
    $days = isset($_POST['days']) ? (int) $_POST['days'] : 30;
    $now  = current_time('mysql');
    if (!$to) $to = $now;
    if (!$from) $from = date('Y-m-d H:i:s', strtotime('-' . max(1,$days) . ' days', strtotime($to)));

    // Top locations from contacts table and/or interactions metadata
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    $interactions_table = $wpdb->prefix . 'vmo_contact_interactions';
    $locations = [];
    $states = [];
    $pages = [];
    $countries_count = 0;
    $total_visits = 0;
    $unique_visitors = 0;
    $visitors = [];
    // Stable zeroed facets
    $age_ranges = [
        '18-24' => 0,
        '25-34' => 0,
        '35-44' => 0,
        '45-54' => 0,
        '55+'   => 0,
    ];
    $devices = [ 'mobile' => 0, 'desktop' => 0, 'tablet' => 0, 'other' => 0 ];
    $oses    = [ 'ios' => 0, 'android' => 0, 'windows' => 0, 'macos' => 0, 'linux' => 0, 'other' => 0 ];
    $referrers = [
        [ 'source' => 'Google', 'count' => 0 ],
        [ 'source' => 'Direct', 'count' => 0 ],
        [ 'source' => 'Social', 'count' => 0 ],
        [ 'source' => 'YouTube Ads', 'count' => 0 ],
        [ 'source' => 'Facebook Ads', 'count' => 0 ],
        [ 'source' => 'Email', 'count' => 0 ],
        [ 'source' => 'Other', 'count' => 0 ],
    ];

    $contacts_exists = vmo_table_exists($contacts_table);
    $interactions_exists = vmo_table_exists($interactions_table);

    if ($contacts_exists) {
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT country, COUNT(*) c FROM {$contacts_table} WHERE user_id=%d AND country IS NOT NULL AND country<>'' GROUP BY country ORDER BY c DESC LIMIT 5",
            $user_id
        ), ARRAY_A);
        $countries_count = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(DISTINCT country) FROM {$contacts_table} WHERE user_id = %d AND country IS NOT NULL AND country<>''",
            $user_id
        ));
        foreach ($rows as $r) { $locations[] = ($r['country'] ?: 'Unknown') . ' - ' . (int)$r['c']; }
        // Optional: states (best-effort if a 'state' column exists)
        if ($wpdb->get_col_info('name', 0) || true) {
            // Guarded query; if no state column, query will fail silently in try/catch
            try {
                $srows = $wpdb->get_results($wpdb->prepare(
                    "SELECT state, COUNT(*) c FROM {$contacts_table} WHERE user_id=%d AND state IS NOT NULL AND state<>'' GROUP BY state ORDER BY c DESC LIMIT 5",
                    $user_id
                ), ARRAY_A);
                foreach (($srows ?: []) as $sr) { $states[] = ($sr['state'] ?: 'Unknown') . ' - ' . (int)$sr['c']; }
            } catch (Throwable $e) { /* ignore */ }
        }
    }

    if ($interactions_exists) {
        // Aggregate visit counts
        $total_visits = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$interactions_table} WHERE user_id=%d AND interaction_type='website_visit' AND created_at BETWEEN %s AND %s",
            $user_id, $from, $to
        ));
        $unique_visitors = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(DISTINCT contact_id) FROM {$interactions_table} WHERE user_id=%d AND interaction_type='website_visit' AND created_at BETWEEN %s AND %s",
            $user_id, $from, $to
        ));

        // Derive top pages from interaction_subtype or subject if present
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT COALESCE(interaction_subtype, subject, '/') AS page, COUNT(*) c
             FROM {$interactions_table}
             WHERE user_id=%d AND interaction_type='website_visit' AND created_at BETWEEN %s AND %s
             GROUP BY page ORDER BY c DESC LIMIT 5",
            $user_id, $from, $to
        ), ARRAY_A);
        foreach (($rows ?: []) as $r) {
            $p = trim((string) $r['page']);
            if ($p === '') $p = '/';
            $pages[] = $p . ' - ' . (int)$r['c'];
        }

        // Build a visitors summary list (contact_id grouping)
        $vrows = $wpdb->get_results($wpdb->prepare(
            "SELECT contact_id, COUNT(*) AS visits, MAX(created_at) AS last_visit
             FROM {$interactions_table}
             WHERE user_id=%d AND interaction_type='website_visit' AND created_at BETWEEN %s AND %s AND contact_id IS NOT NULL AND contact_id<>''
             GROUP BY contact_id ORDER BY visits DESC, last_visit DESC LIMIT 20",
            $user_id, $from, $to
        ), ARRAY_A);
        if ($vrows) {
            // Try to enrich with email/name from contacts table
            $contact_map = [];
            if ($contacts_exists) {
                $ids = array_filter(array_unique(array_column($vrows, 'contact_id')));
                if ($ids) {
                    // contact_id is stored as VARCHAR; fetch by contact_id column if present else attempt id
                    try {
                        $in = implode(',', array_fill(0, count($ids), '%s'));
                        $sql = "SELECT * FROM {$contacts_table} WHERE user_id=%d AND contact_id IN ($in)";
                        $prepared = $wpdb->prepare($sql, array_merge([$user_id], $ids));
                        $crows = $wpdb->get_results($prepared, ARRAY_A);
                        foreach (($crows?:[]) as $cr) {
                            $cr_name = $cr['full_name'] ?? '';
                            if (empty($cr_name)) { $cr_name = trim(($cr['first_name'] ?? '') . ' ' . ($cr['last_name'] ?? '')); }
                            $contact_map[(string)$cr['contact_id']] = [ 'email' => (string)($cr['email']?:''), 'name' => (string)$cr_name ];
                        }
                    } catch (Throwable $e) { /* ignore */ }
                }
            }
            foreach ($vrows as $vr) {
                $cid = (string) $vr['contact_id'];
                $meta = $contact_map[$cid] ?? ['email'=>'','name'=>''];
                $visitors[] = [
                    'contact_id' => $cid,
                    'name' => $meta['name'],
                    'email' => $meta['email'],
                    'visits' => (int) $vr['visits'],
                    'last_visit' => (string) $vr['last_visit'],
                    'source' => '—'
                ];
            }
        }
    }

    // Build recent activities from interactions
    $activities = [];
    if ($interactions_exists) {
        $act_rows = $wpdb->get_results($wpdb->prepare(
            "SELECT interaction_type, COALESCE(interaction_subtype, subject, '') AS detail,
                    created_at, contact_id
             FROM {$interactions_table}
             WHERE user_id = %d AND created_at BETWEEN %s AND %s
             ORDER BY created_at DESC LIMIT 20",
            $user_id, $from, $to
        ), ARRAY_A);
        foreach (($act_rows ?: []) as $ar) {
            $label = ucwords(str_replace('_', ' ', $ar['interaction_type']));
            $detail = !empty($ar['detail']) ? (' — ' . mb_substr($ar['detail'], 0, 80)) : '';
            $activities[] = [
                'type' => $ar['interaction_type'],
                'label' => $label . $detail,
                'contact_id' => $ar['contact_id'] ?? '',
                'timestamp' => $ar['created_at'],
            ];
        }
    }

    // Build cart / order activity from store orders
    $cart_activity = [];
    $orders_table = $wpdb->prefix . 'vmo_internal_store_orders';
    $orders_exists = vmo_table_exists($orders_table);
    if ($orders_exists) {
        $order_rows = $wpdb->get_results($wpdb->prepare(
            "SELECT id, status, total_amount, currency, created_at
             FROM {$orders_table}
             WHERE user_id = %d AND created_at BETWEEN %s AND %s
             ORDER BY created_at DESC LIMIT 15",
            $user_id, $from, $to
        ), ARRAY_A);
        foreach (($order_rows ?: []) as $or) {
            $cart_activity[] = [
                'order_id' => $or['id'],
                'status' => $or['status'],
                'amount' => floatval($or['total_amount']),
                'currency' => $or['currency'] ?? 'USD',
                'timestamp' => $or['created_at'],
            ];
        }
    }

    wp_send_json_success([
        // Basic facets (gender/age require analytics integration — honest zeros)
        'gender' => ['male' => 0, 'female' => 0, 'other' => 0],
        'age_ranges' => $age_ranges,
        'avg_age' => 0,
        // Locations
        'countries' => $countries_count,
        'locations' => $locations ?: [],
        'states' => $states ?: [],
        // Devices and OS
        'devices' => $devices,
        'os' => $oses,
        // Referrers
        'referrers' => $referrers,
        // Content/pages
        'pages' => $pages ?: [],
        // Aggregate visit counts
        'total_visits' => $total_visits,
        'unique_visitors' => $unique_visitors,
        // Visitor list summary for the period
        'visitors' => $visitors,
        // Real activities from interactions
        'activities' => $activities,
        // Real cart/order activity from store orders
        'cart_activity' => $cart_activity,
    ]);
}

add_action('wp_ajax_vmo_get_conversions_data', 'vmo_get_conversions_data');
function vmo_get_conversions_data() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $from = isset($_POST['from']) ? sanitize_text_field(wp_unslash($_POST['from'])) : '';
    $to   = isset($_POST['to'])   ? sanitize_text_field(wp_unslash($_POST['to']))   : '';
    $days = isset($_POST['days']) ? (int) $_POST['days'] : 30;
    $now  = current_time('mysql');
    if (!$to) $to = $now;
    if (!$from) $from = date('Y-m-d H:i:s', strtotime('-' . max(1,$days) . ' days', strtotime($to)));

    // Get real conversion data from orders if available
    $orders_table = $wpdb->prefix . 'vmo_store_orders';
    $table_exists = vmo_table_exists($orders_table);
    
    if ($table_exists) {
        $total_orders = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$orders_table} WHERE user_id = %d AND status = 'paid' AND created_at BETWEEN %s AND %s",
            $user_id, $from, $to
        ));
        $avg_order_value = (float) $wpdb->get_var($wpdb->prepare(
            "SELECT AVG(amount_total) FROM {$orders_table} WHERE user_id = %d AND status = 'paid' AND created_at BETWEEN %s AND %s",
            $user_id, $from, $to
        ));
        $avg_order_value = $avg_order_value ? '$' . number_format($avg_order_value / 100, 2) : '$0.00';
    } else {
        $total_orders = 0;
        $avg_order_value = '$0.00';
    }
    
    // Attempt to compute visits in same window for a crude conversion rate
    $interactions_table = $wpdb->prefix . 'vmo_contact_interactions';
    $visits = 0;
    if (vmo_table_exists($interactions_table)) {
        $visits = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$interactions_table} WHERE user_id=%d AND interaction_type='website_visit' AND created_at BETWEEN %s AND %s",
            $user_id, $from, $to
        ));
    }
    $conversion_rate = ($visits > 0 && $total_orders >= 0) ? round(($total_orders / max(1,$visits)) * 100, 2) : 0;
    $cart_abandon = 0;
    if (class_exists('VMO_Abandoned_Cart')) {
        $ac_stats = VMO_Abandoned_Cart::instance()->get_stats($user_id);
        $cart_abandon = $ac_stats['total_abandoned'] ?? 0;
    }
    
    // Build simple orders-per-day series within the window and conversion-rate series
    $labels = [];$chart_data = [];$rate_series = [];
    if ($table_exists) {
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT DATE(created_at) d, COUNT(*) c FROM {$orders_table}
             WHERE user_id=%d AND status='paid' AND created_at BETWEEN %s AND %s
             GROUP BY DATE(created_at) ORDER BY d ASC",
            $user_id, $from, $to
        ), ARRAY_A);
        foreach ($rows as $r) { $labels[] = date('M j', strtotime($r['d'])); $chart_data[] = (int)$r['c']; }
        // Build conversion rate per day using visits if available
        if (vmo_table_exists($interactions_table)) {
            foreach ($rows as $r) {
                $day = $r['d'];
                $vis_day = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$interactions_table} WHERE user_id=%d AND interaction_type='website_visit' AND DATE(created_at)=%s", $user_id, $day));
                $ord_day = (int) $r['c'];
                $rate_series[] = $vis_day > 0 ? round(($ord_day / max(1,$vis_day)) * 100, 2) : 0;
            }
        }
    }
    
    // Compute funnel steps from real interaction data
    $funnel_visitors  = $visits; // already computed above from vmo_contact_interactions
    $funnel_add_to_cart = 0;
    $funnel_checkout    = 0;
    if (vmo_table_exists($interactions_table)) {
        $funnel_add_to_cart = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$interactions_table} WHERE user_id=%d AND interaction_type='add_to_cart' AND created_at BETWEEN %s AND %s",
            $user_id, $from, $to
        ));
        $funnel_checkout = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$interactions_table} WHERE user_id=%d AND interaction_type IN ('checkout','checkout_started') AND created_at BETWEEN %s AND %s",
            $user_id, $from, $to
        ));
    }

    wp_send_json_success([
        'conversion_rate' => $conversion_rate,
        'total_conversions' => $total_orders,
        'avg_order_value' => $avg_order_value,
        'cart_abandon' => $cart_abandon,
        'funnel' => [
            'visitors' => $funnel_visitors,
            'add_to_cart' => $funnel_add_to_cart,
            'checkout' => $funnel_checkout,
            'purchase' => $total_orders
        ],
        'chart' => ['labels' => $labels, 'data' => $chart_data],
        'rate_series' => $rate_series
    ]);
}

add_action('wp_ajax_vmo_get_competitors_data', 'vmo_get_competitors_data');
// Alias: dashboard JS calls `vmo_get_competitors` (not `_data`)
add_action('wp_ajax_vmo_get_competitors', 'vmo_get_competitors_data');

// ── Real handlers for dashboard panels backed by existing DB tables ──
add_action('wp_ajax_vmo_get_market_scans', function () {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $table = $wpdb->prefix . 'vmo_phase_outputs';
    $has_table = vmo_table_exists($table);
    $scans = [];
    $total_scans = 0;
    if ($has_table) {
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT id, session_id, output_json, is_approved, created_at, updated_at
             FROM {$table}
             WHERE user_id = %d AND output_type = 'market_research' AND is_active = 1
             ORDER BY created_at DESC LIMIT 20",
            $user_id
        ), ARRAY_A);
        $total_scans = count($rows);
        foreach ($rows as $row) {
            $data = json_decode($row['output_json'], true);
            $scans[] = [
                'id' => (int) $row['id'],
                'session_id' => $row['session_id'],
                'competitors' => count($data['competitors'] ?? []),
                'threats' => count($data['threats'] ?? []),
                'opportunities' => count($data['opportunities'] ?? []),
                'summary' => $data['summary'] ?? '',
                'approved' => (bool) $row['is_approved'],
                'created_at' => $row['created_at'],
            ];
        }
    }
    wp_send_json_success([
        'scans' => $scans,
        'stats' => ['total_scans' => $total_scans],
    ]);
});

add_action('wp_ajax_vmo_get_business_goals', function () {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $table = $wpdb->prefix . 'vmo_business_roadmaps';
    $has_table = vmo_table_exists($table);
    $goals = [];
    $total_goals = 0;
    $total_progress = 0;
    if ($has_table) {
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT id, roadmap_id, business_vision, roadmap_data, total_milestones, total_activities,
                    roadmap_start_date, vision_target_date, current_day_number, status, created_at
             FROM {$table}
             WHERE user_id = %d AND is_active = 1
             ORDER BY created_at DESC LIMIT 20",
            $user_id
        ), ARRAY_A);
        $total_goals = count($rows);

        // Get progress data
        $progress_table = $wpdb->prefix . 'vmo_progress_tracking';
        $has_progress = vmo_table_exists($progress_table);

        foreach ($rows as $row) {
            $progress = 0;
            $health = 'on_track';
            if ($has_progress) {
                $latest = $wpdb->get_row($wpdb->prepare(
                    "SELECT progress_percentage, schedule_health FROM {$progress_table} WHERE roadmap_id = %s AND user_id = %d ORDER BY tracking_date DESC LIMIT 1",
                    $row['roadmap_id'], $user_id
                ));
                if ($latest) {
                    $progress = round((float) $latest->progress_percentage, 1);
                    $health = $latest->schedule_health ?: 'on_track';
                }
            }
            $total_progress += $progress;

            $goals[] = [
                'id' => (int) $row['id'],
                'roadmap_id' => $row['roadmap_id'],
                'vision' => $row['business_vision'],
                'milestones' => (int) $row['total_milestones'],
                'activities' => (int) $row['total_activities'],
                'start_date' => $row['roadmap_start_date'],
                'target_date' => $row['vision_target_date'],
                'current_day' => (int) $row['current_day_number'],
                'status' => $row['status'],
                'progress' => $progress,
                'health' => $health,
                'created_at' => $row['created_at'],
            ];
        }
    }
    $avg_progress = $total_goals > 0 ? round($total_progress / $total_goals, 1) : 0;
    wp_send_json_success([
        'goals' => $goals,
        'stats' => [
            'total_goals' => $total_goals,
            'average_progress' => $avg_progress,
        ],
    ]);
});

add_action('wp_ajax_vmo_get_roadmap_tasks', function () {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $table = $wpdb->prefix . 'vmo_daily_activities';
    $has_table = vmo_table_exists($table);
    $tasks = [];
    $stats = [
        'total' => 0,
        'pending' => 0,
        'in_progress' => 0,
        'completed' => 0,
        'overdue' => 0,
    ];
    if ($has_table) {
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT id, activity_id, roadmap_id, milestone_id, activity_title, activity_description,
                    activity_type, priority, status, activity_date, expected_complete_by,
                    actual_completed_at, ai_generated, recurring, recurrence_pattern
             FROM {$table}
             WHERE user_id = %d
             ORDER BY priority ASC, activity_date ASC
             LIMIT 50",
            $user_id
        ), ARRAY_A);

        $now = current_time('Y-m-d');
        foreach ($rows as $row) {
            $is_overdue = ($row['status'] !== 'completed' && $row['status'] !== 'skipped' && $row['activity_date'] < $now);
            $tasks[] = [
                'id' => (int) $row['id'],
                'activity_id' => $row['activity_id'],
                'roadmap_id' => $row['roadmap_id'],
                'milestone_id' => $row['milestone_id'],
                'title' => $row['activity_title'],
                'description' => $row['activity_description'],
                'type' => $row['activity_type'],
                'priority' => (int) $row['priority'],
                'status' => $row['status'],
                'date' => $row['activity_date'],
                'due' => $row['expected_complete_by'],
                'completed_at' => $row['actual_completed_at'],
                'ai_generated' => (bool) $row['ai_generated'],
                'recurring' => (bool) $row['recurring'],
                'recurrence' => $row['recurrence_pattern'],
                'overdue' => $is_overdue,
            ];
            $stats['total']++;
            if ($row['status'] === 'completed') $stats['completed']++;
            elseif ($row['status'] === 'in_progress') $stats['in_progress']++;
            elseif ($is_overdue) $stats['overdue']++;
            else $stats['pending']++;
        }
    }
    wp_send_json_success([
        'tasks' => $tasks,
        'stats' => $stats,
    ]);
});

/* ══════════════════════════════════════════════════════════
   TODAY'S TASKS — Full daily task list + End-of-Day Report
   + Vision velocity projection (faster / slower)
   ══════════════════════════════════════════════════════════ */

/**
 * AJAX: Get full today's task list with stats & velocity info
 */
add_action('wp_ajax_vmo_get_todays_tasks_full', 'vmo_get_todays_tasks_full');
function vmo_get_todays_tasks_full() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }

    global $wpdb;
    $user_id = get_current_user_id();
    $today   = current_time('Y-m-d');

    $activities_table = $wpdb->prefix . 'vmo_daily_activities';
    $reports_table    = $wpdb->prefix . 'vmo_daily_reports';
    $roadmaps_table   = $wpdb->prefix . 'vmo_business_roadmaps';
    $progress_table   = $wpdb->prefix . 'vmo_progress_tracking';

    // Verify tables exist
    if (!vmo_table_exists($activities_table)) {
        wp_send_json_error(['message' => 'Activities system not initialized']);
    }

    // Get active roadmap
    $roadmap = $wpdb->get_row($wpdb->prepare(
        "SELECT roadmap_id, roadmap_start_date, vision_target_date, total_duration_days, current_day_number
         FROM {$roadmaps_table} WHERE user_id = %d AND is_active = 1 ORDER BY created_at DESC LIMIT 1",
        $user_id
    ));

    if (!$roadmap) {
        wp_send_json_success([
            'has_roadmap' => false,
            'message'     => 'Complete Phase 4 to generate your roadmap and daily tasks.'
        ]);
    }

    // Fetch today's activities
    $tasks = $wpdb->get_results($wpdb->prepare(
        "SELECT id, activity_id, activity_title, activity_description, activity_type,
                estimated_duration_minutes, priority, status, expected_complete_by,
                actual_completed_at, completion_notes
         FROM {$activities_table}
         WHERE user_id = %d AND activity_date = %s AND roadmap_id = %s
         ORDER BY priority ASC, id ASC",
        $user_id, $today, $roadmap->roadmap_id
    ), ARRAY_A);

    // Also get overdue tasks from previous days
    $overdue = $wpdb->get_results($wpdb->prepare(
        "SELECT id, activity_id, activity_title, activity_description, activity_type,
                estimated_duration_minutes, priority, status, activity_date, expected_complete_by
         FROM {$activities_table}
         WHERE user_id = %d AND activity_date < %s AND roadmap_id = %s
               AND status NOT IN ('completed','skipped')
         ORDER BY activity_date ASC, priority ASC
         LIMIT 20",
        $user_id, $today, $roadmap->roadmap_id
    ), ARRAY_A);

    // Stats
    $total_planned   = count($tasks);
    $completed_today = 0;
    $in_progress     = 0;
    $total_est_mins  = 0;
    $done_est_mins   = 0;
    foreach ($tasks as $t) {
        $total_est_mins += (int) ($t['estimated_duration_minutes'] ?? 60);
        if ($t['status'] === 'completed') {
            $completed_today++;
            $done_est_mins += (int) ($t['estimated_duration_minutes'] ?? 60);
        } elseif ($t['status'] === 'in_progress') {
            $in_progress++;
        }
    }

    // Compute velocity & projection
    $start_date        = $roadmap->roadmap_start_date;
    $total_days        = (int) $roadmap->total_duration_days;
    $vision_target     = $roadmap->vision_target_date;
    $current_day       = (int) $roadmap->current_day_number;

    // Historical completion rate (last 14 days)
    $history = $wpdb->get_results($wpdb->prepare(
        "SELECT report_date, planned_activities_count, completed_activities_count, estimated_completion_adjustment
         FROM {$reports_table}
         WHERE user_id = %d AND roadmap_id = %s
         ORDER BY report_date DESC LIMIT 14",
        $user_id, $roadmap->roadmap_id
    ), ARRAY_A);

    $avg_completion_rate = 1.0;
    $cumulative_adj      = 0;
    $streak              = 0;
    if (!empty($history)) {
        $total_planned_h = 0;
        $total_done_h    = 0;
        foreach ($history as $h) {
            $total_planned_h += (int) $h['planned_activities_count'];
            $total_done_h    += (int) $h['completed_activities_count'];
        }
        $avg_completion_rate = $total_planned_h > 0 ? round($total_done_h / $total_planned_h, 3) : 1.0;
        $cumulative_adj = (int) ($history[0]['estimated_completion_adjustment'] ?? 0);

        // Streak: count consecutive 100% days
        foreach ($history as $h) {
            if ((int) $h['completed_activities_count'] >= (int) $h['planned_activities_count'] && (int) $h['planned_activities_count'] > 0) {
                $streak++;
            } else {
                break;
            }
        }
    }

    // Check if today's report already exists
    $existing_report = $wpdb->get_row($wpdb->prepare(
        "SELECT id, day_score, submitted_at FROM {$reports_table}
         WHERE user_id = %d AND roadmap_id = %s AND report_date = %s LIMIT 1",
        $user_id, $roadmap->roadmap_id, $today
    ), ARRAY_A);

    wp_send_json_success([
        'has_roadmap'       => true,
        'date'              => $today,
        'roadmap_id'        => $roadmap->roadmap_id,
        'current_day'       => $current_day,
        'total_days'        => $total_days,
        'vision_target'     => $vision_target,
        'tasks'             => $tasks,
        'overdue'           => $overdue,
        'stats'             => [
            'total_planned'     => $total_planned,
            'completed'         => $completed_today,
            'in_progress'       => $in_progress,
            'remaining'         => $total_planned - $completed_today - $in_progress,
            'total_est_minutes' => $total_est_mins,
            'done_est_minutes'  => $done_est_mins,
            'overdue_count'     => count($overdue),
        ],
        'velocity'          => [
            'avg_completion_rate' => $avg_completion_rate,
            'cumulative_adj'     => $cumulative_adj,
            'streak'             => $streak,
        ],
        'report_submitted'  => !empty($existing_report['submitted_at']),
        'report_score'      => (int) ($existing_report['day_score'] ?? 0),
    ]);
}

/**
 * AJAX: Submit end-of-day report — contrasts planned vs completed,
 *       calculates vision velocity adjustment
 */
add_action('wp_ajax_vmo_submit_daily_report', 'vmo_submit_daily_report');
function vmo_submit_daily_report() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }

    global $wpdb;
    $user_id    = get_current_user_id();
    $today      = current_time('Y-m-d');
    $roadmap_id = sanitize_text_field(wp_unslash($_POST['roadmap_id'] ?? ''));
    $user_notes = sanitize_textarea_field(wp_unslash($_POST['notes'] ?? ''));
    $blockers   = sanitize_textarea_field(wp_unslash($_POST['blockers'] ?? ''));
    $wins       = sanitize_textarea_field(wp_unslash($_POST['wins'] ?? ''));

    if (empty($roadmap_id)) {
        wp_send_json_error(['message' => 'Missing roadmap_id']);
    }

    $activities_table = $wpdb->prefix . 'vmo_daily_activities';
    $reports_table    = $wpdb->prefix . 'vmo_daily_reports';
    $roadmaps_table   = $wpdb->prefix . 'vmo_business_roadmaps';

    // Get roadmap
    $roadmap = $wpdb->get_row($wpdb->prepare(
        "SELECT roadmap_id, total_duration_days, current_day_number, vision_target_date, roadmap_start_date
         FROM {$roadmaps_table} WHERE roadmap_id = %s AND user_id = %d AND is_active = 1 LIMIT 1",
        $roadmap_id, $user_id
    ));
    if (!$roadmap) {
        wp_send_json_error(['message' => 'Roadmap not found']);
    }

    $current_day = (int) $roadmap->current_day_number;
    $total_days  = (int) $roadmap->total_duration_days;

    // Get today's tasks
    $tasks = $wpdb->get_results($wpdb->prepare(
        "SELECT activity_id, status, estimated_duration_minutes FROM {$activities_table}
         WHERE user_id = %d AND activity_date = %s AND roadmap_id = %s",
        $user_id, $today, $roadmap_id
    ), ARRAY_A);

    $planned   = count($tasks);
    $completed = 0;
    $completed_ids   = [];
    $uncompleted_ids = [];
    foreach ($tasks as $t) {
        if ($t['status'] === 'completed') {
            $completed++;
            $completed_ids[] = $t['activity_id'];
        } else {
            $uncompleted_ids[] = $t['activity_id'];
        }
    }

    // Calculate velocity adjustment
    // If completion > 100% (user did extra tasks), days saved = positive
    // If completion < 100%, days added = negative
    $completion_rate = $planned > 0 ? ($completed / $planned) : 1.0;
    $day_adjustment  = 0;
    if ($planned > 0) {
        // Each percentage point above/below 100% adjusts vision estimate
        // At 150%, saving ~0.5 day. At 50%, losing ~0.5 day.
        $delta = $completion_rate - 1.0;
        $day_adjustment = (int) round($delta * -1); // negative = days saved, positive = days added
    }

    // 100% completion = 100 score; scale linearly with extra bonus for streaks
    $day_score = min(100, (int) round($completion_rate * 100));

    // Get previous cumulative adjustment
    $prev_cum = (int) $wpdb->get_var($wpdb->prepare(
        "SELECT cumulative_adjustment FROM {$reports_table}
         WHERE user_id = %d AND roadmap_id = %s AND report_date < %s
         ORDER BY report_date DESC LIMIT 1",
        $user_id, $roadmap_id, $today
    ));
    $new_cumulative = $prev_cum + $day_adjustment;

    // Streak
    $prev_streak = (int) $wpdb->get_var($wpdb->prepare(
        "SELECT streak_count FROM {$reports_table}
         WHERE user_id = %d AND roadmap_id = %s AND report_date < %s
         ORDER BY report_date DESC LIMIT 1",
        $user_id, $roadmap_id, $today
    ));
    $new_streak = ($completion_rate >= 1.0 && $planned > 0) ? ($prev_streak + 1) : 0;

    // Bonus: 7-day streak saves an extra day
    if ($new_streak > 0 && $new_streak % 7 === 0) {
        $day_adjustment--;
        $new_cumulative--;
    }

    // Progress delta
    $progress_delta = $total_days > 0 ? round(($completed / max(1, $total_days)) * 100, 2) : 0;

    // Upsert report
    $report_id = 'dr_' . $roadmap_id . '_d' . $current_day;
    $existing = $wpdb->get_var($wpdb->prepare(
        "SELECT id FROM {$reports_table} WHERE report_id = %s LIMIT 1", $report_id
    ));

    $report_data = [
        'report_id'                    => $report_id,
        'roadmap_id'                   => $roadmap_id,
        'user_id'                      => $user_id,
        'day_number'                   => $current_day,
        'report_date'                  => $today,
        'planned_activities_count'     => $planned,
        'completed_activities_count'   => $completed,
        'completed_activity_ids'       => wp_json_encode($completed_ids),
        'uncompleted_activity_ids'     => wp_json_encode($uncompleted_ids),
        'user_notes'                   => $user_notes,
        'blockers'                     => $blockers,
        'wins'                         => $wins,
        'progress_delta'               => $progress_delta,
        'estimated_completion_adjustment' => $day_adjustment,
        'cumulative_adjustment'        => $new_cumulative,
        'day_score'                    => $day_score,
        'streak_count'                 => $new_streak,
        'submitted_at'                 => current_time('mysql'),
    ];

    if ($existing) {
        $wpdb->update($reports_table, $report_data, ['id' => $existing]);
    } else {
        $wpdb->insert($reports_table, $report_data);
    }

    // Update roadmap's vision_target_date based on adjustment
    if ($day_adjustment !== 0) {
        $new_target = date('Y-m-d', strtotime($roadmap->vision_target_date . " {$day_adjustment} days"));
        $wpdb->update($roadmaps_table, ['vision_target_date' => $new_target], [
            'roadmap_id' => $roadmap_id,
            'user_id'    => $user_id,
        ]);
    }

    // Build human-friendly assessment
    $assessment = '';
    if ($completion_rate >= 1.0) {
        $assessment = 'Outstanding day! You completed all planned tasks';
        if ($new_streak >= 3) $assessment .= " ({$new_streak}-day streak!)";
        if ($day_adjustment < 0) $assessment .= '. You are ' . abs($day_adjustment) . ' day(s) closer to achieving your business vision.';
        else $assessment .= '.';
    } elseif ($completion_rate >= 0.8) {
        $assessment = "Good progress — {$completed}/{$planned} tasks done ({$day_score}%). ";
        $assessment .= 'Keep pushing to maintain momentum.';
    } elseif ($completion_rate >= 0.5) {
        $assessment = "Moderate progress — {$completed}/{$planned} tasks done ({$day_score}%). ";
        if ($day_adjustment > 0) $assessment .= "This pushes your vision date out by {$day_adjustment} day(s). Try to catch up tomorrow.";
    } else {
        $assessment = "Low completion today — {$completed}/{$planned} tasks done ({$day_score}%). ";
        if ($day_adjustment > 0) $assessment .= "Your business vision is now {$day_adjustment} day(s) further away. Consider blocking dedicated time tomorrow.";
    }

    wp_send_json_success([
        'report_id'       => $report_id,
        'day_score'       => $day_score,
        'completion_rate'  => round($completion_rate * 100),
        'planned'         => $planned,
        'completed'       => $completed,
        'day_adjustment'  => $day_adjustment,
        'cumulative_adj'  => $new_cumulative,
        'streak'          => $new_streak,
        'assessment'      => $assessment,
        'new_vision_date' => $day_adjustment !== 0 ? $new_target : $roadmap->vision_target_date,
    ]);
}

/**
 * AJAX: Get historical daily reports for the user
 */
add_action('wp_ajax_vmo_get_daily_reports_history', 'vmo_get_daily_reports_history');
function vmo_get_daily_reports_history() {
    if (!current_user_can('read')) wp_send_json_error(['message' => 'Unauthorized'], 403);
    if (!vmo_verify_admin_security()) wp_send_json_error(['message' => 'Invalid nonce'], 403);

    global $wpdb;
    $user_id    = get_current_user_id();
    $roadmap_id = sanitize_text_field(wp_unslash($_POST['roadmap_id'] ?? ''));
    $limit      = min(30, max(1, (int) ($_POST['limit'] ?? 14)));

    if (empty($roadmap_id)) {
        wp_send_json_error(['message' => 'Missing roadmap_id']);
    }

    $reports_table = $wpdb->prefix . 'vmo_daily_reports';
    if (!vmo_table_exists($reports_table)) {
        wp_send_json_success(['reports' => []]);
    }

    $rows = $wpdb->get_results($wpdb->prepare(
        "SELECT report_date, day_number, planned_activities_count, completed_activities_count,
                day_score, streak_count, estimated_completion_adjustment, cumulative_adjustment,
                user_notes, wins, blockers, submitted_at
         FROM {$reports_table}
         WHERE user_id = %d AND roadmap_id = %s
         ORDER BY report_date DESC LIMIT %d",
        $user_id, $roadmap_id, $limit
    ), ARRAY_A);

    wp_send_json_success(['reports' => $rows ?: []]);
}

// SEO Analytics AJAX handlers
add_action('wp_ajax_vmo_get_seo_data', 'vmo_get_seo_data');
add_action('wp_ajax_vmo_get_seo_recommendations', 'vmo_get_seo_recommendations');

// Strategic Dashboard SEO wiring (integrations + approvals)
add_action('wp_ajax_vmo_get_seo_overview', 'vmo_get_seo_overview');
add_action('wp_ajax_vmo_get_seo_integrations', 'vmo_get_seo_integrations');
add_action('wp_ajax_vmo_save_seo_integrations', 'vmo_save_seo_integrations');
add_action('wp_ajax_vmo_set_seo_auto_execute', 'vmo_set_seo_auto_execute');
add_action('wp_ajax_vmo_refresh_seo_data', 'vmo_refresh_seo_data');
add_action('wp_ajax_vmo_get_seo_reports', 'vmo_get_seo_reports');
add_action('wp_ajax_vmo_regenerate_seo_recommendations', 'vmo_regenerate_seo_recommendations');
add_action('wp_ajax_vmo_update_seo_recommendation', 'vmo_update_seo_recommendation');
add_action('wp_ajax_vmo_approve_seo_recommendation', 'vmo_approve_seo_recommendation');
add_action('wp_ajax_vmo_execute_seo_recommendation', 'vmo_execute_seo_recommendation');

// Strategic Dashboard CRM wiring (contacts list + tags + import)
add_action('wp_ajax_vmo_get_contacts', 'vmo_get_contacts');
add_action('wp_ajax_vmo_list_tags', 'vmo_list_tags');
add_action('wp_ajax_vmo_mark_as_customer', 'vmo_mark_as_customer');
add_action('wp_ajax_vmo_get_crm_contact_details', 'vmo_get_crm_contact_details');
add_action('wp_ajax_vmo_import_contacts', 'vmo_import_contacts');

// Contact Communications History AJAX handlers
add_action('wp_ajax_vmo_get_contact_communications', 'vmo_get_contact_communications');
add_action('wp_ajax_vmo_get_communication_detail', 'vmo_get_communication_detail');
add_action('wp_ajax_vmo_generate_contact_recommendations', 'vmo_generate_contact_recommendations');

// Quarterly Business Insights AJAX handlers
add_action('wp_ajax_vmo_generate_quarterly_insights', 'vmo_generate_quarterly_insights');
add_action('wp_ajax_vmo_get_quarterly_history', 'vmo_get_quarterly_history');
add_action('wp_ajax_vmo_get_quarterly_report', 'vmo_get_quarterly_report');

// Strategic Dashboard Payments tax settings
add_action('wp_ajax_vmo_get_tax_settings', 'vmo_get_tax_settings');
add_action('wp_ajax_vmo_save_tax_settings', 'vmo_save_tax_settings');

// Email Marketing AJAX handlers
add_action('wp_ajax_vmo_get_email_metrics', 'vmo_get_email_metrics');
add_action('wp_ajax_vmo_get_email_campaigns', 'vmo_get_email_campaigns');
;

// Email Credentials Management (User-Provided IMAP/SMTP)
add_action('wp_ajax_vmo_save_email_credentials', 'vmo_save_email_credentials');
add_action('wp_ajax_vmo_test_and_save_email_credentials', 'vmo_test_and_save_email_credentials');
add_action('wp_ajax_vmo_verify_email_credentials', 'vmo_verify_email_credentials');
add_action('wp_ajax_vmo_get_email_accounts', 'vmo_get_email_accounts');
add_action('wp_ajax_vmo_delete_email_account', 'vmo_delete_email_account');

// Bulk Email Provider Integration (Brevo, MailerLite)
add_action('wp_ajax_vmo_save_bulk_email_provider', 'vmo_save_bulk_email_provider');
add_action('wp_ajax_vmo_verify_bulk_email_provider', 'vmo_verify_bulk_email_provider');

// Payment/Revenue AJAX handlers
add_action('wp_ajax_vmo_get_payment_data', 'vmo_get_payment_data');
add_action('wp_ajax_vmo_get_revenue_analytics', 'vmo_get_revenue_analytics');

// CRM AJAX handlers
add_action('wp_ajax_vmo_get_crm_data', 'vmo_get_crm_data');
add_action('wp_ajax_vmo_get_leads_data', 'vmo_get_leads_data');

// AI Recommendations AJAX handlers
add_action('wp_ajax_vmo_get_ai_recommendations', 'vmo_get_ai_recommendations');
add_action('wp_ajax_vmo_approve_ai_action', 'vmo_approve_ai_action');
add_action('wp_ajax_vmo_get_industry_insights', 'vmo_get_industry_insights');

// Detailed Analytics AJAX handlers
add_action('wp_ajax_vmo_get_detailed_analytics', 'vmo_get_detailed_analytics');
add_action('wp_ajax_vmo_get_geographical_data', 'vmo_get_geographical_data');

// Shipping & Fulfillment AJAX handlers
add_action('wp_ajax_vmo_get_active_orders', 'vmo_get_active_orders');
add_action('wp_ajax_vmo_approve_shipment', 'vmo_approve_shipment');
add_action('wp_ajax_vmo_get_shipping_settings', 'vmo_get_shipping_settings');
add_action('wp_ajax_vmo_save_shipping_settings', 'vmo_save_shipping_settings');
add_action('wp_ajax_vmo_test_shipping_integration', 'vmo_test_shipping_integration');
add_action('wp_ajax_vmo_get_shipping_analytics', 'vmo_get_shipping_analytics');
add_action('wp_ajax_vmo_get_shipping_ai_recommendations', 'vmo_get_shipping_ai_recommendations');
add_action('wp_ajax_vmo_generate_shipping_recommendations', 'vmo_generate_shipping_recommendations');
add_action('wp_ajax_vmo_approve_shipping_recommendation', 'vmo_approve_shipping_recommendation');
add_action('wp_ajax_vmo_apply_all_shipping_optimizations', 'vmo_apply_all_shipping_optimizations');
// Shipping utility endpoints used by the dashboard UI
add_action('wp_ajax_vmo_test_carrier_connection', 'vmo_test_carrier_connection');
add_action('wp_ajax_vmo_get_order_details', 'vmo_get_order_details');
add_action('wp_ajax_vmo_bulk_approve_shipments', 'vmo_bulk_approve_shipments');
add_action('wp_ajax_vmo_get_carrier_config', 'vmo_get_carrier_config');
add_action('wp_ajax_vmo_save_carrier_config', 'vmo_save_carrier_config');
add_action('wp_ajax_vmo_save_fulfillment_config', 'vmo_save_fulfillment_config');

// Strategic Dashboard KPI endpoint (was referenced by the UI but not registered)
add_action('wp_ajax_vmo_get_dashboard_kpis', 'vmo_get_dashboard_kpis');
function vmo_get_dashboard_kpis() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    // Require a valid nonce; return JSON error (avoid admin-ajax 400 noise).
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }

    $period = isset($_POST['period']) ? sanitize_text_field(wp_unslash($_POST['period'])) : '30d';
    $user_id = get_current_user_id();
    global $wpdb;
    $dashboard_context = vmo_dashboard_get_generation_context($user_id);
    $setup_snapshot = vmo_dashboard_build_setup_snapshot($dashboard_context);
    $seed_kpis = $dashboard_context['seed_kpis'];

    // Determine date window from period
    $days = 30;
    if ($period === '7d') $days = 7;
    elseif ($period === '90d') $days = 90;
    elseif ($period === '365d' || $period === '1y') $days = 365;
    $date_from = date('Y-m-d H:i:s', strtotime("-{$days} days"));

    // ── Contacts / Customers ──
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $contacts_exist = ($wpdb->get_var("SHOW TABLES LIKE '{$contacts_table}'") === $contacts_table);
    $total_customers = 0;
    $new_customers = 0;
    $total_leads = 0;
    if ($contacts_exist) {
        $total_customers = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$contacts_table} WHERE user_id = %d", $user_id
        ));
        $new_customers = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$contacts_table} WHERE user_id = %d AND created_at >= %s", $user_id, $date_from
        ));
        $total_leads = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$contacts_table} WHERE user_id = %d AND lifecycle_stage = 'lead'", $user_id
        ));
    }

    // ── Call metrics ──
    $call_sources = array(
        array(
            'table' => $wpdb->prefix . 'vmo_call_history',
            'timestamp_column' => 'created_at',
            'duration_column' => 'duration',
            'label' => 'call_history',
        ),
        array(
            'table' => $wpdb->prefix . 'vmo_phone_calls',
            'timestamp_column' => 'created_at',
            'duration_column' => 'duration',
            'label' => 'phone_calls',
        ),
        array(
            'table' => $wpdb->prefix . 'vmo_business_calls',
            'timestamp_column' => 'call_start_time',
            'duration_column' => 'call_duration',
            'label' => 'business_calls',
        ),
    );
    $total_calls = 0;
    $avg_duration = 0;
    $call_metrics_source = 'none';
    foreach ($call_sources as $source) {
        $table_name = $source['table'];
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        if ($wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") !== $table_name) {
            continue;
        }

        $count = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_name} WHERE user_id = %d AND {$source['timestamp_column']} >= %s",
            $user_id,
            $date_from
        ));
        $duration = (float) $wpdb->get_var($wpdb->prepare(
            "SELECT COALESCE(AVG({$source['duration_column']}), 0) FROM {$table_name} WHERE user_id = %d AND {$source['timestamp_column']} >= %s",
            $user_id,
            $date_from
        ));

        if ($count > $total_calls) {
            $total_calls = $count;
            $avg_duration = $duration;
            $call_metrics_source = $source['label'];
        }
    }

    // ── Revenue (from Stripe orders) ──
    $orders_table = $wpdb->prefix . 'vmo_store_orders';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $orders_exist = ($wpdb->get_var("SHOW TABLES LIKE '{$orders_table}'") === $orders_table);
    $total_revenue = 0;
    $avg_transaction = 0;
    if ($orders_exist) {
        $total_revenue_cents = (float) $wpdb->get_var($wpdb->prepare(
            "SELECT COALESCE(SUM(amount_total), 0) FROM {$orders_table} WHERE user_id = %d AND status = 'paid' AND created_at >= %s", $user_id, $date_from
        ));
        $avg_transaction_cents = (float) $wpdb->get_var($wpdb->prepare(
            "SELECT COALESCE(AVG(amount_total), 0) FROM {$orders_table} WHERE user_id = %d AND status = 'paid' AND created_at >= %s", $user_id, $date_from
        ));
        $total_revenue = round($total_revenue_cents / 100, 2);
        $avg_transaction = round($avg_transaction_cents / 100, 2);
    }

    // ── Roadmap / Tasks ──
    $tasks_table = $wpdb->prefix . 'vmo_daily_activities';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $tasks_exist = ($wpdb->get_var("SHOW TABLES LIKE '{$tasks_table}'") === $tasks_table);
    $total_tasks = 0;
    $tasks_completed = 0;
    $overdue_count = 0;
    if ($tasks_exist) {
        $total_tasks = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$tasks_table} WHERE user_id = %d", $user_id
        ));
        $tasks_completed = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$tasks_table} WHERE user_id = %d AND status = 'completed'", $user_id
        ));
        $overdue_count = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$tasks_table} WHERE user_id = %d AND status != 'completed' AND scheduled_date < %s", $user_id, current_time('Y-m-d')
        ));
    }
    $task_completion_rate = $total_tasks > 0 ? round(($tasks_completed / $total_tasks) * 100) : 0;

    // ── Marketing / Visitors ──
    $interactions_table = $wpdb->prefix . 'vmo_contact_interactions';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $interactions_exist = ($wpdb->get_var("SHOW TABLES LIKE '{$interactions_table}'") === $interactions_table);
    $total_visitors  = 0;
    $total_pageviews = 0;
    $bounce_rate     = 0;
    if ($interactions_exist) {
        $total_visitors = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(DISTINCT COALESCE(contact_id, id)) FROM {$interactions_table} WHERE user_id = %d AND interaction_type = 'website_visit' AND created_at >= %s",
            $user_id, $date_from
        ));
        $total_pageviews = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$interactions_table} WHERE user_id = %d AND interaction_type IN ('website_visit','page_view') AND created_at >= %s",
            $user_id, $date_from
        ));
        // Bounce = single-page sessions. Approximate: visitors with exactly 1 pageview / total visitors
        if ($total_visitors > 0) {
            $single_page_visitors = (int) $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM (
                    SELECT COALESCE(contact_id, id) as vid, COUNT(*) as pv
                    FROM {$interactions_table}
                    WHERE user_id = %d AND interaction_type IN ('website_visit','page_view') AND created_at >= %s
                    GROUP BY vid HAVING pv = 1
                ) bounce_sub",
                $user_id, $date_from
            ));
            $bounce_rate = round(($single_page_visitors / max(1, $total_visitors)) * 100, 1);
        }
    }

    // ── Goals (from roadmaps) ──
    $roadmaps_table = $wpdb->prefix . 'vmo_business_roadmaps';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $roadmaps_exist = ($wpdb->get_var("SHOW TABLES LIKE '{$roadmaps_table}'") === $roadmaps_table);
    $total_goals  = 0;
    $goals_completed = 0;
    $goals_avg_progress = 0;
    $short_term_count = 0;
    $long_term_count  = 0;
    if ($roadmaps_exist) {
        $roadmap_rows = $wpdb->get_results($wpdb->prepare(
            "SELECT roadmap_data, status, total_milestones, vision_target_date, roadmap_start_date FROM {$roadmaps_table} WHERE user_id = %d AND is_active = 1",
            $user_id
        ));
        $total_goals = count($roadmap_rows);
        foreach ($roadmap_rows as $rm) {
            if ($rm->status === 'completed') $goals_completed++;
            // short-term: target within 90 days; long-term: beyond
            $target = $rm->vision_target_date ? strtotime($rm->vision_target_date) : 0;
            if ($target > 0) {
                $diff_days = ($target - time()) / 86400;
                if ($diff_days <= 90) { $short_term_count++; } else { $long_term_count++; }
            } else {
                $long_term_count++;
            }
        }
        // Average progress from progress_tracking
        $progress_table = $wpdb->prefix . 'vmo_progress_tracking';
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        if ($wpdb->get_var("SHOW TABLES LIKE '{$progress_table}'") === $progress_table) {
            $avg_prog = (float) $wpdb->get_var($wpdb->prepare(
                "SELECT AVG(progress_percentage) FROM {$progress_table} WHERE user_id = %d", $user_id
            ));
            $goals_avg_progress = round($avg_prog, 1);
        }
    }
    $goals_completion_rate = $total_goals > 0 ? round(($goals_completed / $total_goals) * 100) : 0;

    // ── Market intelligence ──
    $phase_outputs_table = $wpdb->prefix . 'vmo_phase_outputs';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $phase_outputs_exist = ($wpdb->get_var("SHOW TABLES LIKE '{$phase_outputs_table}'") === $phase_outputs_table);
    $total_scans        = 0;
    $active_competitors = 0;
    $total_threats      = 0;
    $total_opportunities = 0;
    if ($phase_outputs_exist) {
        $total_scans = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$phase_outputs_table} WHERE user_id = %d AND output_type = 'market_research' AND is_active = 1",
            $user_id
        ));
        // Parse latest market research for competitor/threat/opportunity counts
        $latest_market = $wpdb->get_var($wpdb->prepare(
            "SELECT output_json FROM {$phase_outputs_table} WHERE user_id = %d AND output_type = 'market_research' AND is_active = 1 ORDER BY created_at DESC LIMIT 1",
            $user_id
        ));
        if ($latest_market) {
            $market_data = json_decode($latest_market, true);
            if (is_array($market_data)) {
                $active_competitors  = count($market_data['competitors'] ?? []);
                $total_threats       = count($market_data['threats'] ?? []);
                $total_opportunities = count($market_data['opportunities'] ?? []);
            }
        }
    }

    $seed_total_leads = (int) vmo_dashboard_seed_metric_value($seed_kpis, 'total_leads', 0);
    if ($total_leads <= 0 && $seed_total_leads > 0) {
        $total_leads = $seed_total_leads;
    }

    $seed_total_calls = (int) vmo_dashboard_seed_metric_value($seed_kpis, 'phone_calls_total', 0);
    if ($total_calls <= 0 && $seed_total_calls > 0) {
        $total_calls = $seed_total_calls;
    }

    $seed_visitors = (int) vmo_dashboard_seed_metric_value($seed_kpis, 'website_visitors', 0);
    if ($total_visitors <= 0 && $seed_visitors > 0) {
        $total_visitors = $seed_visitors;
    }

    $seed_pageviews = (int) vmo_dashboard_seed_metric_value($seed_kpis, 'page_views', 0);
    if ($total_pageviews <= 0 && $seed_pageviews > 0) {
        $total_pageviews = $seed_pageviews;
    }

    if ($total_goals <= 0 && (int) ($setup_snapshot['setup']['total'] ?? 0) > 0) {
        $total_goals = (int) $setup_snapshot['setup']['total'];
        $goals_completed = (int) $setup_snapshot['setup']['completed'];
        $goals_avg_progress = (float) ($setup_snapshot['setup']['progress'] ?? 0);
        $short_term_count = (int) ($setup_snapshot['website']['draft_ready'] ? 1 : 0);
        $long_term_count = (int) (($setup_snapshot['systems']['payments']['ready'] ?? false) || ($setup_snapshot['systems']['contact_management']['ready'] ?? false));
    }
    $goals_completion_rate = max((int) $goals_completion_rate, (int) ($setup_snapshot['setup']['progress'] ?? 0));

    if ($total_tasks <= 0 && !empty($setup_snapshot['tasks'])) {
        $total_tasks = count($setup_snapshot['tasks']);
        $overdue_count = max($overdue_count, (int) ($setup_snapshot['website']['qa_pending'] ?? 0));
    }
    $task_completion_rate = max((int) $task_completion_rate, min((int) ($setup_snapshot['setup']['progress'] ?? 0), 100));

    // ── Health Score ──
    $health = 50;
    if ($total_customers > 0) $health += 10;
    if ($total_calls > 0) $health += 5;
    if ($total_revenue > 0) $health += 15;
    if ($task_completion_rate > 50) $health += 10;
    if ($new_customers > 0) $health += 5;
    if ($total_visitors > 0) $health += 5;
    if ($total_goals > 0 && $goals_avg_progress > 25) $health += 5;
    if ($total_scans > 0) $health += 5;
    if ((int) ($setup_snapshot['setup']['progress'] ?? 0) > 0) {
        $health = max($health, min(100, 40 + (int) round(((int) $setup_snapshot['setup']['progress']) * 0.45)));
    }
    $health = min(100, $health);

    $conversion_rate = $total_leads > 0 ? round(($total_customers / max(1, $total_leads)) * 100, 1) : (float) vmo_dashboard_seed_metric_value($seed_kpis, 'conversion_rate', 0);
    $setup_progress_value = (int) ($setup_snapshot['setup']['progress'] ?? 0);
    $next_milestone = (string) ($setup_snapshot['setup']['next_milestone'] ?? 'Monitor live KPIs and manage operations');

    $kpis = [
        'overall' => [
            'health_score' => $health,
            'status' => $health >= 70 ? 'good' : ($health >= 40 ? 'fair' : 'needs_attention'),
        ],
        'revenue' => [
            'total_revenue' => round($total_revenue, 2),
            'net_revenue' => round($total_revenue * 0.971, 2), // ~2.9% Stripe fees
            'processor_fees' => round($total_revenue * 0.029, 2),
            'growth_rate' => 0,
            'avg_transaction_value' => round($avg_transaction, 2),
        ],
        'customers' => [
            'total_customers' => $total_customers,
            'new_customers' => $new_customers,
            'avg_lifetime_value' => $total_customers > 0 ? round($total_revenue / $total_customers, 2) : 0,
            'acquisition_rate' => $total_customers > 0 ? round(($new_customers / max(1, $total_customers)) * 100) : 0,
        ],
        'calls' => [
            'total_calls' => $total_calls,
            'avg_duration' => round($avg_duration),
            'source' => $call_metrics_source,
            'avg_sentiment_score' => 0,
            'avg_conversion_probability' => 0,
        ],
        'marketing' => [
            'total_visitors' => $total_visitors,
            'total_pageviews' => $total_pageviews,
            'bounce_rate' => $bounce_rate,
            'total_leads' => $total_leads,
            'conversion_rate' => $conversion_rate,
        ],
        'goals' => [
            'total_goals' => $total_goals,
            'completion_rate' => $goals_completion_rate,
            'avg_progress' => $goals_avg_progress,
            'short_term_count' => $short_term_count,
            'long_term_count' => $long_term_count,
        ],
        'tasks' => [
            'total_tasks' => $total_tasks,
            'completion_rate' => $task_completion_rate,
            'overdue_count' => $overdue_count,
            'immediate_count' => count($setup_snapshot['tasks'] ?? array()),
        ],
        'market' => [
            'total_scans' => $total_scans,
            'active_competitors' => $active_competitors,
            'total_threats' => $total_threats,
            'total_opportunities' => $total_opportunities,
        ],
        'website' => [
            'status' => (string) ($setup_snapshot['website']['status'] ?? 'pending'),
            'pages_count' => (int) ($setup_snapshot['website']['pages_count'] ?? 0),
            'publish_ready' => !empty($setup_snapshot['website']['publish_ready']) ? 1 : 0,
            'qa_pending_count' => (int) ($setup_snapshot['website']['qa_pending'] ?? 0),
        ],
        'setup' => [
            'setup_progress' => $setup_progress_value,
            'next_milestone' => $next_milestone,
            'payment_system_ready' => !empty($setup_snapshot['systems']['payments']['ready']) ? 1 : 0,
            'phone_system_ready' => !empty($setup_snapshot['systems']['phone']['ready']) ? 1 : 0,
            'email_marketing_ready' => !empty($setup_snapshot['systems']['email_marketing']['ready']) ? 1 : 0,
            'seo_system_ready' => !empty($setup_snapshot['systems']['seo']['ready']) ? 1 : 0,
            'contact_management_ready' => !empty($setup_snapshot['systems']['contact_management']['ready']) ? 1 : 0,
        ],
        'business' => [
            'business_name' => (string) ($setup_snapshot['business']['name'] ?? ''),
            'business_type' => (string) ($setup_snapshot['business']['type'] ?? ''),
            'industry' => (string) ($setup_snapshot['business']['industry'] ?? ''),
        ],
        'recommendations' => [
            'next_milestone' => $next_milestone,
            'active_modules' => (int) ($setup_snapshot['modules']['active'] ?? 0),
            'in_progress_modules' => (int) ($setup_snapshot['modules']['in_progress'] ?? 0),
            'critical_alerts' => max((int) $overdue_count, (int) ($setup_snapshot['website']['qa_pending'] ?? 0)),
        ],
    ];

    wp_send_json_success([
        'period' => $period,
        'kpis' => $kpis,
        'dashboard_modules' => $dashboard_context['dashboard_modules'],
        'kpi_model' => $dashboard_context['kpi_model'],
        'last_update' => (string) ($dashboard_context['operating_state']['generated_at'] ?? current_time('mysql')),
    ]);
}

// SEO task list endpoint (used by Strategic Dashboard loadTasks())
add_action('wp_ajax_vmo_list_seo_tasks', 'vmo_list_seo_tasks');
function vmo_list_seo_tasks() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }

    global $wpdb;
    $user_id = get_current_user_id();
    $table = $wpdb->prefix . 'vmo_approval_queue';
    $has_table = vmo_table_exists($table);
    if (!$has_table) {
        wp_send_json_success(['tasks' => []]);
    }

    $rows = $wpdb->get_results(
        $wpdb->prepare(
            "SELECT id, title, status, created_at, updated_at FROM {$table} WHERE user_id=%d AND type='seo' ORDER BY updated_at DESC LIMIT 50",
            $user_id
        ),
        ARRAY_A
    );
    wp_send_json_success([
        'tasks' => is_array($rows) ? $rows : [],
    ]);
}

/* --------------------------------------------------------------
   Strategic Dashboard: SEO + CRM + Tax endpoints
---------------------------------------------------------------- */

function vmo_get_seo_overview() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }

    $user_id = get_current_user_id();
    $consultation_profile = function_exists('vmo_get_consultation_profile') ? vmo_get_consultation_profile($user_id) : [];
    if (empty($consultation_profile)) {
        wp_send_json_success([
            'score' => '—',
            'pages_indexed' => '—',
            'errors' => '—',
            'warnings' => '—',
            'keyword_trend' => [],
            'levels' => [],
            'domain_authority' => '—',
            'keyword_opportunity' => '—',
            'status' => 'consultation_pending',
            'message' => 'SEO analysis available after consultation completion'
        ]);
    }

    $website_analysis = vmo_analyze_website_seo($user_id, $consultation_profile);
    $score = isset($website_analysis['overall_score']) ? (int) $website_analysis['overall_score'] : 0;
    $kw = isset($website_analysis['keyword_opportunity']) ? (int) $website_analysis['keyword_opportunity'] : 0;

    // Build keyword trend from real weekly content counts (last 10 weeks)
    $keyword_trend = [];
    global $wpdb;
    $cumulative = 0;
    for ($w = 9; $w >= 0; $w--) {
        $week_start = date('Y-m-d', strtotime("-{$w} weeks Monday"));
        $week_end   = date('Y-m-d', strtotime("-{$w} weeks Sunday"));
        $count = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->posts}
             WHERE post_author = %d AND post_status = 'publish'
             AND post_date BETWEEN %s AND %s",
            $user_id, $week_start . ' 00:00:00', $week_end . ' 23:59:59'
        ));
        $cumulative += $count;
        // Scale: each published page adds ~10 to keyword opportunity score (capped at 100)
        $keyword_trend[] = min(100, $cumulative * 10);
    }
    // If no content exists, fall back to the computed keyword opportunity score
    if (array_sum($keyword_trend) === 0) {
        $base = max(0, min(100, $kw));
        for ($i = 0; $i < 10; $i++) {
            $keyword_trend[$i] = $base;
        }
    }

    wp_send_json_success([
        'score' => $score,
        'pages_indexed' => (int) ($website_analysis['indexed_pages'] ?? 0),
        'errors' => (int) ($website_analysis['errors'] ?? 0),
        'warnings' => (int) ($website_analysis['warnings'] ?? 0),
        'keyword_trend' => $keyword_trend,
        'levels' => [
            'onpage' => (int) ($website_analysis['onpage_score'] ?? 0),
            'technical' => (int) ($website_analysis['technical_score'] ?? 0),
            'content' => (int) ($website_analysis['content_score'] ?? 0),
            'backlink' => (int) ($website_analysis['backlink_authority'] ?? 0),
            'local' => null,
        ],
        'domain_authority' => (int) ($website_analysis['domain_authority'] ?? 0),
        'keyword_opportunity' => (int) ($website_analysis['keyword_opportunity'] ?? 0),
        'status' => 'active'
    ]);
}

function vmo_get_seo_integrations() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    $user_id = get_current_user_id();
    $ah = (string) get_user_meta($user_id, 'vmo_ahrefs_api_key', true);
    $se = (string) get_user_meta($user_id, 'vmo_semrush_api_key', true);
    $auto = (int) get_user_meta($user_id, 'vmo_seo_auto_execute', true);
    $last = (string) get_user_meta($user_id, 'vmo_seo_last_refresh', true);

    $mask = function($k){
        $k = (string) $k;
        if ($k === '') return '';
        $len = strlen($k);
        if ($len <= 6) return str_repeat('•', max(0, $len));
        return substr($k, 0, 3) . str_repeat('•', max(0, $len - 6)) . substr($k, -3);
    };

    wp_send_json_success([
        'ahrefs_connected' => $ah !== '',
        'semrush_connected' => $se !== '',
        'ahrefs_key_masked' => $ah !== '' ? $mask($ah) : null,
        'semrush_key_masked' => $se !== '' ? $mask($se) : null,
        'auto_execute' => (bool) $auto,
        'last_refresh' => $last ?: '—',
    ]);
}

function vmo_save_seo_integrations() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    $user_id = get_current_user_id();
    $ah = isset($_POST['ahrefs_key']) ? sanitize_text_field(wp_unslash($_POST['ahrefs_key'])) : '';
    $se = isset($_POST['semrush_key']) ? sanitize_text_field(wp_unslash($_POST['semrush_key'])) : '';
    if ($ah !== '') update_user_meta($user_id, 'vmo_ahrefs_api_key', $ah);
    if ($se !== '') update_user_meta($user_id, 'vmo_semrush_api_key', $se);
    wp_send_json_success(['saved' => true]);
}

function vmo_set_seo_auto_execute() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    $user_id = get_current_user_id();
    $value = isset($_POST['value']) ? (int) $_POST['value'] : 0;
    update_user_meta($user_id, 'vmo_seo_auto_execute', $value ? 1 : 0);
    wp_send_json_success(['auto_execute' => (bool) $value]);
}

function vmo_refresh_seo_data() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    $user_id = get_current_user_id();
    $ts = current_time('mysql');
    update_user_meta($user_id, 'vmo_seo_last_refresh', $ts);
    wp_send_json_success(['message' => 'SEO data refresh recorded', 'last_refresh' => $ts]);
}

function vmo_get_seo_reports() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    // Minimal response to keep the UI stable (real reports can be added later).
    wp_send_json_success([
        'reports' => [],
        'core_trend' => [],
        'top_keywords' => [],
        'backlinks_trend' => [],
        'index_trend' => [],
        'issue_breakdown' => [],
    ]);
}

function vmo_seo_get_reco_state($user_id) {
    $state = get_user_meta($user_id, 'vmo_seo_recommendations_state', true);
    if (is_array($state) && isset($state['keywords'], $state['blog_topics'], $state['actions'])) {
        return $state;
    }
    $profile = function_exists('vmo_get_consultation_profile') ? vmo_get_consultation_profile($user_id) : [];
    $insights = vmo_get_cached_industry_insights($user_id);
    $recs = vmo_generate_ai_seo_recommendations($user_id, $profile, $insights);
    $keywords = [];
    foreach (($recs['keywords'] ?? []) as $k) {
        $kw = is_array($k) ? $k : ['keyword' => (string) $k];
        $kw['approved'] = false;
        $keywords[] = $kw;
    }
    $blogs = array_map(function($t){ return ['topic' => (string) $t, 'approved' => false]; }, (array) ($recs['blog_topics'] ?? []));
    $actions = array_map(function($t){ return ['text' => (string) $t, 'approved' => false]; }, (array) ($recs['technical_actions'] ?? []));
    $state = [
        'keywords' => $keywords,
        'blog_topics' => $blogs,
        'actions' => $actions,
        'generated_at' => current_time('mysql'),
    ];
    update_user_meta($user_id, 'vmo_seo_recommendations_state', $state);
    return $state;
}

function vmo_regenerate_seo_recommendations() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    $user_id = get_current_user_id();
    delete_user_meta($user_id, 'vmo_seo_recommendations_state');
    vmo_seo_get_reco_state($user_id);
    wp_send_json_success(['message' => 'Recommendations regenerated']);
}

function vmo_update_seo_recommendation() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    $user_id = get_current_user_id();
    $type = sanitize_key($_POST['type'] ?? '');
    $index = isset($_POST['index']) ? (int) $_POST['index'] : -1;
    $text = sanitize_text_field(wp_unslash($_POST['text'] ?? ''));
    if ($type === '' || $index < 0 || $text === '') {
        wp_send_json_error(['message' => 'Invalid input']);
    }
    $state = vmo_seo_get_reco_state($user_id);
    if (!isset($state[$type]) || !is_array($state[$type]) || !isset($state[$type][$index])) {
        wp_send_json_error(['message' => 'Recommendation not found']);
    }
    if ($type === 'keywords') {
        $state[$type][$index]['keyword'] = $text;
        $state[$type][$index]['approved'] = false;
    } elseif ($type === 'blog_topics') {
        $state[$type][$index]['topic'] = $text;
        $state[$type][$index]['approved'] = false;
    } else {
        $state[$type][$index]['text'] = $text;
        $state[$type][$index]['approved'] = false;
    }
    update_user_meta($user_id, 'vmo_seo_recommendations_state', $state);
    wp_send_json_success(['updated' => true]);
}

function vmo_approve_seo_recommendation() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $scope = sanitize_key($_POST['scope'] ?? '');
    $type = sanitize_key($_POST['type'] ?? '');
    $index = isset($_POST['index']) ? (int) $_POST['index'] : -1;
    $auto = (int) get_user_meta($user_id, 'vmo_seo_auto_execute', true);

    $state = vmo_seo_get_reco_state($user_id);
    if ($scope === 'all') {
        foreach (['keywords','blog_topics','actions'] as $k) {
            if (!isset($state[$k]) || !is_array($state[$k])) continue;
            foreach ($state[$k] as $i => $it) {
                $state[$k][$i]['approved'] = true;
            }
        }
        update_user_meta($user_id, 'vmo_seo_recommendations_state', $state);
        // Log approval
        $table = $wpdb->prefix . 'vmo_approval_queue';
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->insert($table, [
            'user_id' => $user_id,
            'type' => 'seo',
            'title' => 'SEO: Approved all recommendations',
            'payload' => wp_json_encode(['scope' => 'all']),
            'status' => 'approved',
            'notes' => $auto ? 'auto_execute=1' : 'auto_execute=0',
            'created_at' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
        ]);
        if ($auto) {
            // Auto execute “all” by enqueueing tasks/drafts best-effort
            foreach (['keywords','blog_topics','actions'] as $k) {
                if (!isset($state[$k]) || !is_array($state[$k])) continue;
                foreach ($state[$k] as $i => $it) {
                    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- intentionally setting POST data for internal dispatch
                    $_POST['type'] = $k;
                    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- intentionally setting POST data for internal dispatch
                    $_POST['index'] = $i;
                    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- intentionally setting POST data for internal dispatch
                    $_POST['text'] = ($k==='keywords') ? ($it['keyword'] ?? '') : (($k==='blog_topics') ? ($it['topic'] ?? '') : ($it['text'] ?? ''));
                    vmo_execute_seo_recommendation(false);
                }
            }
        }
        wp_send_json_success(['approved' => true]);
    }

    if ($type === '' || $index < 0) {
        wp_send_json_error(['message' => 'Invalid input']);
    }
    if (!isset($state[$type]) || !is_array($state[$type]) || !isset($state[$type][$index])) {
        wp_send_json_error(['message' => 'Recommendation not found']);
    }
    $state[$type][$index]['approved'] = true;
    update_user_meta($user_id, 'vmo_seo_recommendations_state', $state);

    // Log approval
    $txt = ($type==='keywords') ? ($state[$type][$index]['keyword'] ?? '') : (($type==='blog_topics') ? ($state[$type][$index]['topic'] ?? '') : ($state[$type][$index]['text'] ?? ''));
    $table = $wpdb->prefix . 'vmo_approval_queue';
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $wpdb->insert($table, [
        'user_id' => $user_id,
        'type' => 'seo',
        'title' => 'SEO: Approved',
        'payload' => wp_json_encode(['type' => $type, 'index' => $index, 'text' => $txt]),
        'status' => 'approved',
        'notes' => $auto ? 'auto_execute=1' : 'auto_execute=0',
        'created_at' => current_time('mysql'),
        'updated_at' => current_time('mysql'),
    ]);

    if ($auto) {
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- intentionally setting POST data for internal dispatch
        $_POST['text'] = $txt;
        vmo_execute_seo_recommendation(false);
    }
    wp_send_json_success(['approved' => true]);
}

function vmo_execute_seo_recommendation($emit_json = true) {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        if ($emit_json) wp_send_json_error(['message' => 'Unauthorized'], 403);
        return;
    }
    if (!vmo_verify_admin_security()) {
        if ($emit_json) wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
        return;
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $type = sanitize_key($_POST['type'] ?? '');
    $index = isset($_POST['index']) ? (int) $_POST['index'] : -1;
    $text = sanitize_text_field(wp_unslash($_POST['text'] ?? ''));
    if ($type === '' || $index < 0 || $text === '') {
        if ($emit_json) wp_send_json_error(['message' => 'Invalid input']);
        return;
    }

    $result = ['executed' => true];
    if ($type === 'keywords') {
        $list = get_user_meta($user_id, 'vmo_seo_target_keywords', true);
        if (!is_array($list)) $list = [];
        if (!in_array($text, $list, true)) $list[] = $text;
        update_user_meta($user_id, 'vmo_seo_target_keywords', $list);
        $result['message'] = 'Keyword saved to SEO targets';
    } elseif ($type === 'blog_topics') {
        // Create a draft post stub
        $post_id = wp_insert_post([
            'post_title' => $text,
            'post_content' => "(Draft)\n\nGenerated from SEO recommendations.\n\nAdd outline, key points, and publish when ready.",
            'post_status' => 'draft',
            'post_type' => 'post',
            'post_author' => $user_id,
        ], true);
        if (!is_wp_error($post_id)) {
            $result['draft_post_id'] = (int) $post_id;
            $result['message'] = 'Draft post created';
        } else {
            $result['message'] = 'Draft post creation failed';
        }
    } else {
        $tasks = get_user_meta($user_id, 'vmo_seo_technical_tasks', true);
        if (!is_array($tasks)) $tasks = [];
        $tasks[] = ['text' => $text, 'created_at' => current_time('mysql')];
        update_user_meta($user_id, 'vmo_seo_technical_tasks', $tasks);
        $result['message'] = 'Task saved';
    }

    // Log execution as a task row
    $table = $wpdb->prefix . 'vmo_approval_queue';
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $wpdb->insert($table, [
        'user_id' => $user_id,
        'type' => 'seo',
        'title' => 'SEO: Execute',
        'payload' => wp_json_encode(['type' => $type, 'index' => $index, 'text' => $text, 'result' => $result]),
        'status' => 'approved',
        'notes' => 'executed',
        'created_at' => current_time('mysql'),
        'updated_at' => current_time('mysql'),
    ]);

    if ($emit_json) wp_send_json_success($result);
}

function vmo_get_contacts() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $type = sanitize_key($_POST['type'] ?? 'lead');
    $limit = isset($_POST['limit']) ? max(1, min(500, (int) $_POST['limit'])) : 200;
    $table = $wpdb->prefix . 'vmo_contacts';
    $has_table = vmo_table_exists($table);
    if (!$has_table) {
        wp_send_json_success(['contacts' => []]);
    }
    $where = "user_id=%d";
    $params = [$user_id];
    if ($type === 'customer') {
        $where .= " AND lifecycle_stage='customer'";
    } else {
        $where .= " AND lifecycle_stage<>'customer'";
    }
    // ✅ ENHANCED: Now includes company, address, city, job_title for richer lead display
    $sql = $wpdb->prepare(
        "SELECT * FROM {$table} WHERE {$where} ORDER BY created_at DESC LIMIT %d",
        array_merge($params, [$limit])
    );
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $rows = $wpdb->get_results($sql, ARRAY_A);
    $contacts = [];
    foreach (($rows ?: []) as $r) {
        $tags = [];
        if (!empty($r['tags'])) {
            $tags = array_values(array_filter(array_map('trim', explode(',', (string) $r['tags']))));
        }
        // Build full address string for display
        $address_parts = array_filter([
            (string) ($r['address'] ?: ''),
            (string) ($r['city'] ?: ''),
            (string) ($r['state'] ?: ''),
            (string) ($r['zip'] ?: '')
        ]);
        $full_address = implode(', ', $address_parts);
        
        $contacts[] = [
            'id' => (int) $r['id'],
            'contact_id' => (string) $r['contact_id'],
            'name' => (string) ($r['full_name'] ?: 'Contact'),
            'email' => (string) ($r['email'] ?: ''),
            'phone' => (string) ($r['phone'] ?: ''),
            'company' => (string) ($r['company'] ?: ''),
            'job_title' => (string) ($r['job_title'] ?: ''),
            'address' => $full_address,
            'city' => (string) ($r['city'] ?: ''),
            'description' => (string) ($r['notes'] ?: ''),
            'tags' => $tags,
            'date_added' => (string) ($r['created_at'] ?: ''),
            'type' => ($r['lifecycle_stage'] === 'customer') ? 'customer' : 'lead',
            'lead_score' => (int) ($r['lead_score'] ?: 0),
            'source' => (string) ($r['source'] ?: 'manual_entry'),
        ];
    }
    wp_send_json_success(['contacts' => $contacts]);
}

function vmo_list_tags() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $table = $wpdb->prefix . 'vmo_contacts';
    $has_table = vmo_table_exists($table);
    if (!$has_table) {
        wp_send_json_success(['tags' => []]);
    }
    $rows = $wpdb->get_col($wpdb->prepare("SELECT tags FROM {$table} WHERE user_id=%d AND tags IS NOT NULL AND tags<>'' LIMIT 500", $user_id));
    $set = [];
    foreach (($rows ?: []) as $raw) {
        foreach (array_filter(array_map('trim', explode(',', (string) $raw))) as $t) {
            $k = strtolower($t);
            $set[$k] = $t;
        }
    }
    $tags = array_values($set);
    sort($tags, SORT_NATURAL | SORT_FLAG_CASE);
    wp_send_json_success(['tags' => array_map(function($t){ return ['name' => $t, 'label' => $t]; }, $tags)]);
}

function vmo_mark_as_customer() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $contact_id = isset($_POST['contact_id']) ? (int) $_POST['contact_id'] : 0;
    if (!$contact_id) {
        wp_send_json_error(['message' => 'Missing contact_id']);
    }
    $table = $wpdb->prefix . 'vmo_contacts';
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $ok = $wpdb->update($table, ['lifecycle_stage' => 'customer', 'updated_at' => current_time('mysql')], ['id' => $contact_id, 'user_id' => $user_id]);
    if ($ok === false) {
        wp_send_json_error(['message' => 'Failed to update']);
    }
    wp_send_json_success(['updated' => true]);
}

function vmo_get_crm_contact_details() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $id = isset($_POST['contact_id']) ? (int) $_POST['contact_id'] : 0;
    if (!$id) {
        wp_send_json_error(['message' => 'Missing contact_id']);
    }
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    $contact = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$contacts_table} WHERE id=%d AND user_id=%d", $id, $user_id), ARRAY_A);
    if (!$contact) {
        wp_send_json_error(['message' => 'Contact not found']);
    }
    $cid = (string) ($contact['contact_id'] ?? '');
    $interactions_table = $wpdb->prefix . 'vmo_contact_interactions';
    $purchases_table = $wpdb->prefix . 'vmo_contact_purchases';

    $numeric_id = (string) $contact['id']; // numeric auto-increment id

    $calls = [];
    if ($cid) {
        // Query by both VARCHAR contact_id (UUID) AND the numeric id cast to string,
        // because link_call_to_contact historically stored the numeric id.
        $calls = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT interaction_type, subject, summary, created_at FROM {$interactions_table} WHERE user_id=%d AND (contact_id=%s OR contact_id=%s) ORDER BY created_at DESC LIMIT 25",
                $user_id,
                $cid,
                $numeric_id
            ),
            ARRAY_A
        );
    }

    $purchases = [];
    $total_spent = 0.0;
    if ($cid) {
        $purchases = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT product_service, amount, currency, status, purchase_date FROM {$purchases_table} WHERE user_id=%d AND contact_id=%s ORDER BY purchase_date DESC LIMIT 25",
                $user_id,
                $cid
            ),
            ARRAY_A
        );
        foreach (($purchases ?: []) as $p) {
            $total_spent += (float) ($p['amount'] ?? 0);
        }
    }

    // Get direct phone call data (transcript, recording, summary) from vmo_phone_calls
    $phone_history = [];
    $contact_phone = $contact['phone'] ?? '';
    if (!empty($contact_phone)) {
        $calls_table = $wpdb->prefix . 'vmo_phone_calls';
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        if ($wpdb->get_var("SHOW TABLES LIKE '{$calls_table}'") === $calls_table) {
            $phone_history = $wpdb->get_results($wpdb->prepare(
                "SELECT id, caller_number, direction, status, duration, topic, summary, transcript, recording_url, created_at 
                FROM {$calls_table} 
                WHERE user_id = %d AND (caller_number LIKE %s OR caller_number LIKE %s)
                ORDER BY created_at DESC LIMIT 25",
                $user_id,
                '%' . $wpdb->esc_like(preg_replace('/[^0-9]/', '', $contact_phone)) . '%',
                '%' . $wpdb->esc_like($contact_phone) . '%'
            ), ARRAY_A) ?: [];
            // Convert timestamps so browser displays user's local time
            foreach ($phone_history as &$ph) {
                if (!empty($ph['created_at']) && method_exists('VMO_Twilio_Manager', 'to_iso8601')) {
                    $ph['created_at'] = VMO_Twilio_Manager::to_iso8601($ph['created_at']);
                }
            }
            unset($ph);
        }
    }

    // Fetch tasks (follow-ups) associated with this contact
    $contact_tasks = [];
    $followups_table = $wpdb->prefix . 'vmo_follow_ups';
    if (vmo_table_exists($followups_table)) {
        $contact_tasks = $wpdb->get_results($wpdb->prepare(
            "SELECT id, notes AS title, scheduled_date AS due_date, status FROM {$followups_table} WHERE user_id=%d AND customer_id=%d ORDER BY scheduled_date DESC LIMIT 50",
            $user_id, $id
        ), ARRAY_A) ?: [];
        foreach ($contact_tasks as &$ct) {
            $ct['id'] = (int) $ct['id'];
            $ct['completed'] = ($ct['status'] === 'completed');
        }
        unset($ct);
    }

    $payload = [
        'contact' => [
            'id' => (int) $contact['id'],
            'name' => (string) ($contact['full_name'] ?: trim(($contact['first_name'] ?? '') . ' ' . ($contact['last_name'] ?? ''))),
            'email' => (string) ($contact['email'] ?? ''),
            'phone' => (string) ($contact['phone'] ?? ''),
            'company' => (string) ($contact['company'] ?? ''),
            'notes' => (string) ($contact['notes'] ?? ''),
            'tags' => (string) ($contact['tags'] ?? ''),
            'created_at' => (string) ($contact['created_at'] ?? ''),
            'lifecycle_stage' => (string) ($contact['lifecycle_stage'] ?? ''),
        ],
        'calls' => is_array($calls) ? $calls : [],
        'phone_history' => $phone_history,
        'email_history' => vmo_get_contact_email_history($user_id, $contact['email'] ?? ''),
        'purchases' => is_array($purchases) ? $purchases : [],
        'total_spent' => $total_spent,
        'tasks' => $contact_tasks,
        'ai_recommendation' => 'Follow up with a short, helpful message and offer a next step (call or checkout link).'
    ];
    wp_send_json_success($payload);
}

/**
 * Set tags on a CRM contact.
 */
add_action('wp_ajax_vmo_set_contact_tags', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $contact_id = isset($_POST['contact_id']) ? (int) $_POST['contact_id'] : 0;
    if (!$contact_id) {
        wp_send_json_error(['message' => 'Missing contact_id']);
    }
    // Accept either comma-separated string or JSON array
    $tags = '';
    if (!empty($_POST['tags_json'])) {
        $arr = json_decode(wp_unslash($_POST['tags_json']), true);
        if (is_array($arr)) {
            $tags = implode(',', array_map('sanitize_text_field', $arr));
        }
    } elseif (isset($_POST['tags'])) {
        $tags = sanitize_text_field(wp_unslash($_POST['tags']));
    }
    $table = $wpdb->prefix . 'vmo_contacts';
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $ok = $wpdb->update($table, ['tags' => $tags, 'updated_at' => current_time('mysql')], ['id' => $contact_id, 'user_id' => $user_id]);
    if ($ok === false) {
        wp_send_json_error(['message' => 'Failed to update tags']);
    }
    wp_send_json_success(['message' => 'Tags saved']);
});

/**
 * Add a task (follow-up) for a CRM contact.
 */
add_action('wp_ajax_vmo_add_task', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $contact_id = isset($_POST['contact_id']) ? (int) $_POST['contact_id'] : 0;
    $title = sanitize_text_field($_POST['title'] ?? '');
    $due_date = sanitize_text_field($_POST['due_date'] ?? '');
    if (!$title) {
        wp_send_json_error(['message' => 'Title is required']);
    }
    $table = $wpdb->prefix . 'vmo_follow_ups';
    $exists = vmo_table_exists($table);
    if (!$exists) {
        wp_send_json_error(['message' => 'Follow-ups table not available']);
    }
    $scheduled = $due_date ? date('Y-m-d H:i:s', strtotime($due_date)) : current_time('mysql');
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $res = $wpdb->insert($table, [
        'user_id' => $user_id,
        'customer_id' => $contact_id,
        'call_id' => null,
        'follow_up_type' => 'manual_task',
        'scheduled_date' => $scheduled,
        'status' => 'pending',
        'notes' => $title,
        'created_at' => current_time('mysql'),
        'updated_at' => current_time('mysql'),
    ]);
    if (!$res) {
        wp_send_json_error(['message' => 'Failed to add task']);
    }
    wp_send_json_success(['message' => 'Task added', 'id' => $wpdb->insert_id]);
});

/**
 * Complete a CRM contact task (follow-up).
 */
add_action('wp_ajax_vmo_complete_task', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    global $wpdb;
    $id = isset($_POST['id']) ? (int) $_POST['id'] : 0;
    if ($id <= 0) {
        wp_send_json_error(['message' => 'Invalid task ID']);
    }
    $table = $wpdb->prefix . 'vmo_follow_ups';
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $res = $wpdb->update($table, ['status' => 'completed', 'updated_at' => current_time('mysql')], ['id' => $id, 'user_id' => get_current_user_id()]);
    if ($res === false) {
        wp_send_json_error(['message' => 'Failed to complete task']);
    }
    wp_send_json_success(['message' => 'Task completed']);
});

/**
 * Delete a CRM contact task (follow-up).
 */
add_action('wp_ajax_vmo_delete_task', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    global $wpdb;
    $id = isset($_POST['id']) ? (int) $_POST['id'] : 0;
    if ($id <= 0) {
        wp_send_json_error(['message' => 'Invalid task ID']);
    }
    $table = $wpdb->prefix . 'vmo_follow_ups';
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $res = $wpdb->delete($table, ['id' => $id, 'user_id' => get_current_user_id()]);
    if ($res === false) {
        wp_send_json_error(['message' => 'Failed to delete task']);
    }
    wp_send_json_success(['message' => 'Task deleted']);
});

/**
 * Refresh document recommendations using AI context.
 */
add_action('wp_ajax_vmo_ai_refresh_document_recs', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    // Nonce check — this action uses _ajax_nonce with the builder nonce
    $nonce = sanitize_text_field($_POST['_ajax_nonce'] ?? $_POST['security'] ?? $_POST['nonce'] ?? '');
    if ($nonce && !wp_verify_nonce($nonce, 'vmo_builder_nonce') && !wp_verify_nonce($nonce, 'vmo_admin_nonce')) {
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    // Return recommendation adjustments based on consultation context
    $user_id = get_current_user_id();
    $recs = [];
    // Check for consultation data to inform priority
    $consultation_table = $GLOBALS['wpdb']->prefix . 'vmo_consultations';
    $has_consultation = (bool) $GLOBALS['wpdb']->get_var($GLOBALS['wpdb']->prepare("SHOW TABLES LIKE %s", $GLOBALS['wpdb']->esc_like($consultation_table)));
    $completed = false;
    if ($has_consultation) {
        $completed = (bool) $GLOBALS['wpdb']->get_var($GLOBALS['wpdb']->prepare(
            "SELECT COUNT(*) FROM {$consultation_table} WHERE user_id=%d AND status='completed'", $user_id
        ));
    }
    if ($completed) {
        // Prioritize business-critical documents
        $critical_docs = ['privacy_policy', 'terms_of_service', 'business_plan'];
        foreach ($critical_docs as $doc_id) {
            $recs[] = ['id' => $doc_id, 'priority' => 'critical', 'selected' => true, 'summary' => 'Essential for your launched business.'];
        }
    }
    wp_send_json_success($recs);
});

/**
 * Get email history for a contact by email address.
 */
function vmo_get_contact_email_history($user_id, $email) {
    if (empty($email)) return [];
    global $wpdb;
    $all_emails = [];

    // Inbound / general emails
    $emails_table = $wpdb->prefix . 'vmo_emails';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$emails_table}'") === $emails_table) {
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT id, subject, body_preview, direction, from_email, to_email, created_at 
            FROM {$emails_table} 
            WHERE user_id = %d AND (from_email = %s OR to_email = %s)
            ORDER BY created_at DESC LIMIT 50",
            $user_id, $email, $email
        ), ARRAY_A) ?: [];
        foreach ($rows as $em) {
            $all_emails[] = [
                'id' => (int) $em['id'],
                'subject' => $em['subject'] ?: 'Email',
                'preview' => $em['body_preview'] ?: '',
                'direction' => $em['direction'] ?: 'inbound',
                'from' => $em['from_email'] ?? '',
                'to' => $em['to_email'] ?? '',
                'created_at' => $em['created_at'],
                'source' => 'inbox',
            ];
        }
    }

    // Sent emails
    $sent_table = $wpdb->prefix . 'vmo_sent_emails';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$sent_table}'") === $sent_table) {
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT id, subject, body, to_email, created_at 
            FROM {$sent_table} 
            WHERE user_id = %d AND to_email = %s
            ORDER BY created_at DESC LIMIT 50",
            $user_id, $email
        ), ARRAY_A) ?: [];
        foreach ($rows as $em) {
            $all_emails[] = [
                'id' => (int) $em['id'],
                'subject' => $em['subject'] ?: 'Sent Email',
                'preview' => wp_strip_all_tags(substr($em['body'] ?? '', 0, 120)),
                'direction' => 'outbound',
                'from' => '',
                'to' => $em['to_email'] ?? '',
                'created_at' => $em['created_at'],
                'source' => 'sent',
            ];
        }
    }

    // Sort by date descending
    usort($all_emails, function($a, $b) {
        return strtotime($b['created_at'] ?? '0') - strtotime($a['created_at'] ?? '0');
    });

    return array_slice($all_emails, 0, 50);
}

/**
 * Get communications history for a contact
 * Includes emails, phone calls, and chat messages
 */
function vmo_get_contact_communications() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $contact_id = isset($_POST['contact_id']) ? (int) $_POST['contact_id'] : 0;
    $type = isset($_POST['type']) ? sanitize_key($_POST['type']) : 'all';
    
    if (!$contact_id) {
        wp_send_json_error(['message' => 'Missing contact_id']);
    }
    
    // Get contact info to find phone/email
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    $contact = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$contacts_table} WHERE id=%d AND user_id=%d", $contact_id, $user_id), ARRAY_A);
    if (!$contact) {
        wp_send_json_error(['message' => 'Contact not found']);
    }
    
    $communications = [];
    $email = $contact['email'] ?? '';
    $phone = $contact['phone'] ?? '';
    $cid = $contact['contact_id'] ?? '';
    
    // Get Phone Calls
    if ($type === 'all' || $type === 'phone') {
        $calls_table = $wpdb->prefix . 'vmo_phone_calls';
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        if ($wpdb->get_var("SHOW TABLES LIKE '{$calls_table}'") === $calls_table && $phone) {
            $calls = $wpdb->get_results($wpdb->prepare(
                "SELECT id, call_sid, caller_number, status, duration, topic, summary, transcript, recording_url, created_at 
                FROM {$calls_table} 
                WHERE user_id = %d AND (caller_number LIKE %s OR caller_number LIKE %s)
                ORDER BY created_at DESC LIMIT 50",
                $user_id, 
                '%' . $wpdb->esc_like(preg_replace('/[^0-9]/', '', $phone)) . '%',
                '%' . $wpdb->esc_like($phone) . '%'
            ), ARRAY_A);
            
            foreach ($calls ?: [] as $call) {
                $communications[] = [
                    'type' => 'phone',
                    'id' => (int) $call['id'],
                    'subject' => $call['topic'] ?: 'Phone Call',
                    'preview' => $call['summary'] ?: 'Call with ' . $call['caller_number'],
                    'transcript' => $call['transcript'] ?? '',
                    'recording_url' => $call['recording_url'] ?? '',
                    'datetime' => $call['created_at'],
                    'direction' => 'inbound',
                    'duration' => (int) $call['duration'],
                    'status' => $call['status'],
                ];
            }
        }
    }
    
    // Get Emails
    if ($type === 'all' || $type === 'email') {
        $emails_table = $wpdb->prefix . 'vmo_emails';
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        if ($wpdb->get_var("SHOW TABLES LIKE '{$emails_table}'") === $emails_table && $email) {
            $emails = $wpdb->get_results($wpdb->prepare(
                "SELECT id, subject, body_preview, direction, created_at 
                FROM {$emails_table} 
                WHERE user_id = %d AND (from_email = %s OR to_email = %s)
                ORDER BY created_at DESC LIMIT 50",
                $user_id, $email, $email
            ), ARRAY_A);
            
            foreach ($emails ?: [] as $em) {
                $communications[] = [
                    'type' => 'email',
                    'id' => (int) $em['id'],
                    'subject' => $em['subject'] ?: 'Email',
                    'preview' => $em['body_preview'] ?: '',
                    'datetime' => $em['created_at'],
                    'direction' => $em['direction'] ?: 'outbound',
                ];
            }
        }
        
        // Also check sent emails table
        $sent_table = $wpdb->prefix . 'vmo_sent_emails';
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        if ($wpdb->get_var("SHOW TABLES LIKE '{$sent_table}'") === $sent_table && $email) {
            $sent_emails = $wpdb->get_results($wpdb->prepare(
                "SELECT id, subject, body, created_at 
                FROM {$sent_table} 
                WHERE user_id = %d AND to_email = %s
                ORDER BY created_at DESC LIMIT 50",
                $user_id, $email
            ), ARRAY_A);
            
            foreach ($sent_emails ?: [] as $em) {
                $communications[] = [
                    'type' => 'email',
                    'id' => (int) $em['id'],
                    'subject' => $em['subject'] ?: 'Sent Email',
                    'preview' => wp_strip_all_tags(substr($em['body'] ?? '', 0, 100)),
                    'datetime' => $em['created_at'],
                    'direction' => 'outbound',
                ];
            }
        }
    }
    
    // Get Chat messages
    if ($type === 'all' || $type === 'chat') {
        $chat_table = $wpdb->prefix . 'vmo_chat_history';
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        if ($wpdb->get_var("SHOW TABLES LIKE '{$chat_table}'") === $chat_table && $cid) {
            $chats = $wpdb->get_results($wpdb->prepare(
                "SELECT id, message, message_role, created_at 
                FROM {$chat_table} 
                WHERE user_id = %d AND contact_id = %s
                ORDER BY created_at DESC LIMIT 50",
                $user_id, $cid
            ), ARRAY_A);
            
            // Group chats by conversation (same date)
            $chat_groups = [];
            foreach ($chats ?: [] as $chat) {
                $date = date('Y-m-d', strtotime($chat['created_at']));
                if (!isset($chat_groups[$date])) {
                    $chat_groups[$date] = [
                        'type' => 'chat',
                        'id' => (int) $chat['id'],
                        'subject' => 'Chat Conversation',
                        'preview' => $chat['message'],
                        'datetime' => $chat['created_at'],
                        'direction' => '',
                    ];
                }
            }
            foreach ($chat_groups as $cg) {
                $communications[] = $cg;
            }
        }
    }
    
    // Sort by datetime descending
    usort($communications, function($a, $b) {
        return strtotime($b['datetime']) - strtotime($a['datetime']);
    });
    
    wp_send_json_success(['communications' => array_slice($communications, 0, 100)]);
}

/**
 * Get detailed communication content
 */
function vmo_get_communication_detail() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $type = isset($_POST['type']) ? sanitize_key($_POST['type']) : '';
    $id = isset($_POST['id']) ? (int) $_POST['id'] : 0;
    
    if (!$type || !$id) {
        wp_send_json_error(['message' => 'Missing type or id']);
    }
    
    $data = [];
    
    if ($type === 'phone') {
        $calls_table = $wpdb->prefix . 'vmo_phone_calls';
        $call = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$calls_table} WHERE id = %d AND user_id = %d",
            $id, $user_id
        ), ARRAY_A);
        
        if ($call) {
            $data = [
                'direction' => $call['direction'] ?? 'inbound',
                'duration' => (int) ($call['duration'] ?? 0),
                'datetime' => $call['created_at'],
                'status' => $call['status'],
                'recording_url' => $call['recording_url'] ?? $call['recording_local_path'] ?? '',
                'summary' => $call['summary'] ?? '',
                'transcript' => $call['transcript'] ?? '',
            ];
        }
    } elseif ($type === 'email') {
        // Check both emails tables
        $emails_table = $wpdb->prefix . 'vmo_emails';
        $email = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$emails_table} WHERE id = %d AND user_id = %d",
            $id, $user_id
        ), ARRAY_A);
        
        if ($email) {
            $data = [
                'subject' => $email['subject'] ?? '',
                'from' => $email['from_email'] ?? '',
                'to' => $email['to_email'] ?? '',
                'datetime' => $email['created_at'],
                'body' => $email['body'] ?? '',
                'body_html' => $email['body_html'] ?? $email['body'] ?? '',
            ];
        } else {
            // Check sent emails table
            $sent_table = $wpdb->prefix . 'vmo_sent_emails';
            $sent = $wpdb->get_row($wpdb->prepare(
                "SELECT * FROM {$sent_table} WHERE id = %d AND user_id = %d",
                $id, $user_id
            ), ARRAY_A);
            
            if ($sent) {
                $data = [
                    'subject' => $sent['subject'] ?? '',
                    'from' => get_user_meta($user_id, 'business_email', true) ?: 'You',
                    'to' => $sent['to_email'] ?? '',
                    'datetime' => $sent['created_at'],
                    'body' => $sent['body'] ?? '',
                    'body_html' => $sent['body_html'] ?? $sent['body'] ?? '',
                ];
            }
        }
    } elseif ($type === 'chat') {
        $chat_table = $wpdb->prefix . 'vmo_chat_history';
        // Get the conversation from that day
        $chat = $wpdb->get_row($wpdb->prepare(
            "SELECT created_at FROM {$chat_table} WHERE id = %d AND user_id = %d",
            $id, $user_id
        ), ARRAY_A);
        
        if ($chat) {
            $date = date('Y-m-d', strtotime($chat['created_at']));
            $messages = $wpdb->get_results($wpdb->prepare(
                "SELECT message, message_role, created_at 
                FROM {$chat_table} 
                WHERE user_id = %d AND DATE(created_at) = %s
                ORDER BY created_at ASC LIMIT 100",
                $user_id, $date
            ), ARRAY_A);
            
            $data = [
                'datetime' => $chat['created_at'],
                'messages' => array_map(function($m) {
                    return [
                        'role' => $m['message_role'],
                        'content' => $m['message'],
                        'time' => $m['created_at'],
                    ];
                }, $messages ?: []),
            ];
        }
    }
    
    if (empty($data)) {
        wp_send_json_error(['message' => 'Communication not found']);
    }
    
    wp_send_json_success($data);
}

/**
 * Generate AI recommendations for a contact based on all communications
 */
function vmo_generate_contact_recommendations() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    // Rate limit AI calls
    $rate_check = vmo_check_ai_rate_limit(get_current_user_id(), 'contact_rec');
    if (is_wp_error($rate_check)) { wp_send_json_error(['message' => $rate_check->get_error_message()], 429); }
    global $wpdb;
    $user_id = get_current_user_id();
    $contact_id = isset($_POST['contact_id']) ? (int) $_POST['contact_id'] : 0;
    
    if (!$contact_id) {
        wp_send_json_error(['message' => 'Missing contact_id']);
    }

    // ✅ TOKEN SECURITY CHECK v8.1.0: Prevent negative arbitrage
    $current_balance = (int) get_user_meta($user_id, 'vmo_token_balance', true);
    if ($current_balance < 100) {
        wp_send_json_error(['message' => 'Insufficient tokens. Minimum balance of 100 tokens required for this analysis.']);
    }
    
    // Get contact info
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    $contact = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$contacts_table} WHERE id=%d AND user_id=%d", $contact_id, $user_id), ARRAY_A);
    if (!$contact) {
        wp_send_json_error(['message' => 'Contact not found']);
    }
    
    $name = $contact['full_name'] ?: trim(($contact['first_name'] ?? '') . ' ' . ($contact['last_name'] ?? ''));
    $email = $contact['email'] ?? '';
    $phone = $contact['phone'] ?? '';
    $stage = $contact['lifecycle_stage'] ?? 'lead';
    
    // Gather communication summary
    $communications_summary = "Contact: {$name}\nEmail: {$email}\nPhone: {$phone}\nLifecycle Stage: {$stage}\n\n";
    
    // Get recent calls (include full transcripts so AI has maximum context)
    $calls_table = $wpdb->prefix . 'vmo_phone_calls';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$calls_table}'") === $calls_table && $phone) {
        $calls = $wpdb->get_results($wpdb->prepare(
            "SELECT topic, summary, transcript, duration, created_at FROM {$calls_table} 
            WHERE user_id = %d AND caller_number LIKE %s 
            ORDER BY created_at DESC LIMIT 5",
            $user_id, '%' . $wpdb->esc_like(preg_replace('/[^0-9]/', '', $phone)) . '%'
        ), ARRAY_A);
        
        if ($calls) {
            $communications_summary .= "Recent Phone Calls:\n";
            foreach ($calls as $c) {
                $communications_summary .= "- {$c['created_at']}: {$c['topic']} ({$c['duration']}s)\n";
                if ($c['summary']) $communications_summary .= "  Summary: {$c['summary']}\n";
                if ($c['transcript']) $communications_summary .= "  Full Transcript:\n{$c['transcript']}\n";
            }
            $communications_summary .= "\n";
        }
    }
    
    // Get purchases
    $purchases_table = $wpdb->prefix . 'vmo_contact_purchases';
    $cid = $contact['contact_id'] ?? '';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$purchases_table}'") === $purchases_table && $cid) {
        $purchases = $wpdb->get_results($wpdb->prepare(
            "SELECT product_service, amount, purchase_date FROM {$purchases_table} 
            WHERE user_id = %d AND contact_id = %s 
            ORDER BY purchase_date DESC LIMIT 5",
            $user_id, $cid
        ), ARRAY_A);
        
        if ($purchases) {
            $communications_summary .= "Purchase History:\n";
            foreach ($purchases as $p) {
                $communications_summary .= "- {$p['purchase_date']}: {$p['product_service']} (\${$p['amount']})\n";
            }
            $communications_summary .= "\n";
        }
    }
    
    // Get business context
    $business_name = get_user_meta($user_id, 'business_name', true) ?: 'the business';
    $business_type = get_user_meta($user_id, 'business_type', true) ?: 'business';
    
    // Generate AI recommendations
    $prompt = "You are an expert business consultant analyzing a contact for {$business_name} ({$business_type}).

Based on the following contact information and communication history, provide specific, actionable recommendations to convert or retain this contact.

{$communications_summary}

Provide 3-5 specific recommendations. Be concise and actionable. Focus on:
1. Best next action to take
2. Timing suggestions
3. Communication channel recommendations
4. Potential upsell/cross-sell opportunities if applicable
5. Relationship strengthening tactics

Format as a bulleted list with clear, actionable items.";

    $api_key = get_option('vmo_openai_api_key') ?: VMO_OPENAI_API_KEY;
    if (!$api_key) {
        wp_send_json_error(['message' => 'AI not configured']);
    }
    
    // ✅ FIX: GPT-5.2 requires max_completion_tokens, not max_tokens
    $_crm_model = defined('VMO_OPENAI_MODEL') ? VMO_OPENAI_MODEL : 'gpt-4o';
    $_crm_body = array_merge([
        'model' => $_crm_model,
        'messages' => [
            ['role' => 'system', 'content' => 'You are a helpful business consultant providing actionable CRM recommendations.'],
            ['role' => 'user', 'content' => $prompt],
        ],
        'temperature' => 0.7,
    ], function_exists('vmo_get_token_param') ? vmo_get_token_param($_crm_model, 500) : ['max_tokens' => 500]);

    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
        'timeout' => 60,
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type' => 'application/json',
        ],
        'body' => json_encode($_crm_body),
    ]);
    
    if (is_wp_error($response)) {
        wp_send_json_error(['message' => 'AI request failed: ' . $response->get_error_message()]);
    }
    
    $body = json_decode(wp_remote_retrieve_body($response), true);
    $recommendations = $body['choices'][0]['message']['content'] ?? 'Unable to generate recommendations at this time.';
    
    // ✅ TOKEN LEAK FIX v8.2.1: Use centralized deduction with FIFO, admin bypass & audit trail
    $cost = vmo_get_token_cost('contact_insights');
    vmo_deduct_tokens($user_id, $cost, 'contact_insights', 'AI contact recommendations for contact #' . $contact_id);
    
    wp_send_json_success(['recommendations' => $recommendations]);
}

// ========================================
// QUARTERLY BUSINESS INSIGHTS FUNCTIONS
// ========================================

/**
 * Generate quarterly business insights from all communications
 */
function vmo_generate_quarterly_insights() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    // Rate limit AI calls
    $rate_check = vmo_check_ai_rate_limit(get_current_user_id(), 'quarterly_insights', 2, 10);
    if (is_wp_error($rate_check)) { wp_send_json_error(['message' => $rate_check->get_error_message()], 429); }
    
    global $wpdb;
    $user_id = get_current_user_id();

    // ✅ TOKEN SECURITY CHECK v8.1.0: Prevent negative arbitrage
    $current_balance = (int) get_user_meta($user_id, 'vmo_token_balance', true);
    if ($current_balance < 200) {
        wp_send_json_error(['message' => 'Insufficient tokens. Minimum balance of 200 tokens required for quarterly insights.']);
    }
    
    // Calculate current quarter
    $current_month = (int) date('n');
    $current_year = (int) date('Y');
    $quarter = ceil($current_month / 3);
    $period = "Q{$quarter} {$current_year}";
    
    // Quarter date range
    $quarter_start_month = (($quarter - 1) * 3) + 1;
    $quarter_start = date('Y-m-d', mktime(0, 0, 0, $quarter_start_month, 1, $current_year));
    $quarter_end = date('Y-m-d', mktime(23, 59, 59, $quarter_start_month + 2, date('t', mktime(0, 0, 0, $quarter_start_month + 2, 1, $current_year)), $current_year));
    
    $metrics = [
        'emails' => 0,
        'calls' => 0,
        'chats' => 0,
    ];
    
    $communications_summary = "Business Communications Summary for {$period}\n\n";
    
    // Get email stats
    $emails_table = $wpdb->prefix . 'vmo_email_sends';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$emails_table}'") === $emails_table) {
        $email_count = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$emails_table} WHERE user_id = %d AND created_at BETWEEN %s AND %s",
            $user_id, $quarter_start, $quarter_end . ' 23:59:59'
        ));
        $metrics['emails'] = $email_count;
        
        // Get top email subjects/campaigns
        $top_emails = $wpdb->get_results($wpdb->prepare(
            "SELECT subject, COUNT(*) as cnt FROM {$emails_table} 
            WHERE user_id = %d AND created_at BETWEEN %s AND %s 
            GROUP BY subject ORDER BY cnt DESC LIMIT 5",
            $user_id, $quarter_start, $quarter_end . ' 23:59:59'
        ), ARRAY_A);
        
        if ($top_emails) {
            $communications_summary .= "EMAIL ACTIVITY ({$email_count} total):\n";
            foreach ($top_emails as $e) {
                $communications_summary .= "- \"{$e['subject']}\" ({$e['cnt']} sends)\n";
            }
            $communications_summary .= "\n";
        }
    }
    
    // Get phone call stats
    $calls_table = $wpdb->prefix . 'vmo_phone_calls';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$calls_table}'") === $calls_table) {
        $call_count = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$calls_table} WHERE user_id = %d AND created_at BETWEEN %s AND %s",
            $user_id, $quarter_start, $quarter_end . ' 23:59:59'
        ));
        $metrics['calls'] = $call_count;
        
        // Get call summaries
        $call_summaries = $wpdb->get_results($wpdb->prepare(
            "SELECT topic, summary, duration, direction FROM {$calls_table} 
            WHERE user_id = %d AND created_at BETWEEN %s AND %s AND summary IS NOT NULL AND summary != ''
            ORDER BY created_at DESC LIMIT 10",
            $user_id, $quarter_start, $quarter_end . ' 23:59:59'
        ), ARRAY_A);
        
        $inbound = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$calls_table} WHERE user_id = %d AND created_at BETWEEN %s AND %s AND direction = 'inbound'",
            $user_id, $quarter_start, $quarter_end . ' 23:59:59'
        ));
        $outbound = $call_count - $inbound;
        
        $communications_summary .= "PHONE CALLS ({$call_count} total - {$inbound} inbound, {$outbound} outbound):\n";
        if ($call_summaries) {
            $communications_summary .= "Recent call topics:\n";
            foreach ($call_summaries as $c) {
                $dir = ucfirst($c['direction'] ?? 'unknown');
                $duration = round(($c['duration'] ?? 0) / 60, 1);
                $communications_summary .= "- [{$dir}] {$c['topic']} ({$duration} min)\n";
            }
        }
        $communications_summary .= "\n";
    }
    
    // Get chat stats
    $chat_table = $wpdb->prefix . 'vmo_chat_sessions';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$chat_table}'") === $chat_table) {
        $chat_count = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$chat_table} WHERE user_id = %d AND created_at BETWEEN %s AND %s",
            $user_id, $quarter_start, $quarter_end . ' 23:59:59'
        ));
        $metrics['chats'] = $chat_count;
        
        // Get recent chat summaries if available
        $chat_summaries = $wpdb->get_results($wpdb->prepare(
            "SELECT summary, visitor_name FROM {$chat_table} 
            WHERE user_id = %d AND created_at BETWEEN %s AND %s AND summary IS NOT NULL AND summary != ''
            ORDER BY created_at DESC LIMIT 5",
            $user_id, $quarter_start, $quarter_end . ' 23:59:59'
        ), ARRAY_A);
        
        $communications_summary .= "CHAT SESSIONS ({$chat_count} total):\n";
        if ($chat_summaries) {
            foreach ($chat_summaries as $c) {
                $visitor = $c['visitor_name'] ?: 'Visitor';
                $summary = substr($c['summary'], 0, 100) . (strlen($c['summary']) > 100 ? '...' : '');
                $communications_summary .= "- {$visitor}: {$summary}\n";
            }
        }
        $communications_summary .= "\n";
    }
    
    // Get contact and conversion stats
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$contacts_table}'") === $contacts_table) {
        $new_contacts = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$contacts_table} WHERE user_id = %d AND created_at BETWEEN %s AND %s",
            $user_id, $quarter_start, $quarter_end . ' 23:59:59'
        ));
        $conversions = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$contacts_table} 
            WHERE user_id = %d AND created_at BETWEEN %s AND %s AND lifecycle_stage = 'customer'",
            $user_id, $quarter_start, $quarter_end . ' 23:59:59'
        ));
        
        $communications_summary .= "CONTACTS & CONVERSIONS:\n";
        $communications_summary .= "- New contacts added: {$new_contacts}\n";
        $communications_summary .= "- Conversions to customer: {$conversions}\n\n";
    }
    
    // Get business context
    $business_name = get_user_meta($user_id, 'business_name', true) ?: 'the business';
    $business_type = get_user_meta($user_id, 'business_type', true) ?: 'business';
    
    // Generate AI insights
    $prompt = "You are an expert business consultant analyzing quarterly performance for {$business_name} ({$business_type}).

Based on the following quarterly communications data, provide:
1. A brief summary (2-3 sentences) of overall communication performance
2. 4-5 specific, actionable recommendations to improve business outcomes next quarter

{$communications_summary}

Be specific and actionable. Focus on patterns you notice and opportunities for improvement.

Format your response as:
SUMMARY:
[Your 2-3 sentence summary here]

RECOMMENDATIONS:
1. [First recommendation]
2. [Second recommendation]
3. [Third recommendation]
4. [Fourth recommendation]
5. [Fifth recommendation - optional]";

    $api_key = get_option('vmo_openai_api_key') ?: VMO_OPENAI_API_KEY;
    if (!$api_key) {
        wp_send_json_error(['message' => 'AI not configured']);
    }
    
    // ✅ FIX: GPT-5.2 requires max_completion_tokens, not max_tokens
    $_qi_model = defined('VMO_OPENAI_MODEL') ? VMO_OPENAI_MODEL : 'gpt-4o';
    $_qi_body = array_merge([
        'model' => $_qi_model,
        'messages' => [
            ['role' => 'system', 'content' => 'You are a helpful business consultant providing quarterly insights and recommendations.'],
            ['role' => 'user', 'content' => $prompt],
        ],
        'temperature' => 0.7,
    ], function_exists('vmo_get_token_param') ? vmo_get_token_param($_qi_model, 800) : ['max_tokens' => 800]);

    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
        'timeout' => 55, // Capped: Kinsta 60s PHP limit
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type' => 'application/json',
        ],
        'body' => json_encode($_qi_body),
    ]);

    if (is_wp_error($response)) {
        wp_send_json_error(['message' => 'AI request failed: ' . $response->get_error_message()]);
    }
    
    $body = json_decode(wp_remote_retrieve_body($response), true);
    $ai_response = $body['choices'][0]['message']['content'] ?? '';
    
    // Parse the response
    $summary = '';
    $recommendations = [];
    
    if (preg_match('/SUMMARY:\s*(.+?)(?=RECOMMENDATIONS:|$)/is', $ai_response, $match)) {
        $summary = trim($match[1]);
    }
    
    if (preg_match('/RECOMMENDATIONS:\s*(.+)/is', $ai_response, $match)) {
        $rec_text = trim($match[1]);
        preg_match_all('/\d+\.\s*(.+?)(?=\d+\.|$)/s', $rec_text, $rec_matches);
        if (!empty($rec_matches[1])) {
            foreach ($rec_matches[1] as $r) {
                $clean = trim($r);
                if ($clean) $recommendations[] = $clean;
            }
        }
    }
    
    // Fallback if parsing failed
    if (!$summary && !$recommendations) {
        $summary = $ai_response;
    }
    
    // Save report to database
    $reports_table = $wpdb->prefix . 'vmo_quarterly_reports';
    vmo_ensure_quarterly_reports_table();
    
    $report_data = [
        'user_id' => $user_id,
        'period' => $period,
        'quarter' => $quarter,
        'year' => $current_year,
        'summary' => $summary,
        'recommendations' => json_encode($recommendations),
        'metrics' => json_encode($metrics),
        'raw_response' => $ai_response,
        'generated_at' => current_time('mysql'),
    ];
    
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $wpdb->insert($reports_table, $report_data);
    
    // ✅ TOKEN LEAK FIX v8.2.1: Use centralized deduction with FIFO, admin bypass & audit trail
    $cost = vmo_get_token_cost('quarterly_insights');
    vmo_deduct_tokens($user_id, $cost, 'quarterly_insights', 'Quarterly business insights for ' . $period);
    
    wp_send_json_success([
        'period' => $period,
        'summary' => $summary,
        'recommendations' => $recommendations,
        'metrics' => $metrics,
        'generated_at' => date('M j, Y g:i A'),
    ]);
}

/**
 * Get quarterly reports history
 */
function vmo_get_quarterly_history() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    
    global $wpdb;
    $user_id = get_current_user_id();
    
    $reports_table = $wpdb->prefix . 'vmo_quarterly_reports';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$reports_table}'") !== $reports_table) {
        wp_send_json_success(['reports' => []]);
    }
    
    $reports = $wpdb->get_results($wpdb->prepare(
        "SELECT id, period, quarter, year, generated_at, metrics FROM {$reports_table} 
        WHERE user_id = %d ORDER BY year DESC, quarter DESC LIMIT 12",
        $user_id
    ), ARRAY_A);
    
    $formatted = [];
    foreach ($reports as $r) {
        $metrics = json_decode($r['metrics'] ?: '{}', true);
        $formatted[] = [
            'id' => (int) $r['id'],
            'period' => $r['period'],
            'generated_at' => date('M j, Y', strtotime($r['generated_at'])),
            'emails' => $metrics['emails'] ?? 0,
            'calls' => $metrics['calls'] ?? 0,
            'chats' => $metrics['chats'] ?? 0,
        ];
    }
    
    wp_send_json_success(['reports' => $formatted]);
}

/**
 * Get a specific quarterly report
 */
function vmo_get_quarterly_report() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    
    global $wpdb;
    $user_id = get_current_user_id();
    $report_id = isset($_POST['report_id']) ? (int) $_POST['report_id'] : 0;
    
    if (!$report_id) {
        wp_send_json_error(['message' => 'Missing report_id']);
    }
    
    $reports_table = $wpdb->prefix . 'vmo_quarterly_reports';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$reports_table}'") !== $reports_table) {
        wp_send_json_error(['message' => 'No reports found']);
    }
    
    $report = $wpdb->get_row($wpdb->prepare(
        "SELECT * FROM {$reports_table} WHERE id = %d AND user_id = %d",
        $report_id, $user_id
    ), ARRAY_A);
    
    if (!$report) {
        wp_send_json_error(['message' => 'Report not found']);
    }
    
    $recommendations = json_decode($report['recommendations'] ?: '[]', true);
    $metrics = json_decode($report['metrics'] ?: '{}', true);
    
    wp_send_json_success([
        'period' => $report['period'],
        'summary' => $report['summary'],
        'recommendations' => is_array($recommendations) ? $recommendations : [],
        'metrics' => $metrics,
        'generated_at' => date('M j, Y g:i A', strtotime($report['generated_at'])),
    ]);
}

/**
 * Ensure quarterly reports table exists
 */
function vmo_ensure_quarterly_reports_table() {
    global $wpdb;
    $table = $wpdb->prefix . 'vmo_quarterly_reports';
    
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$table}'") === $table) {
        return;
    }
    
    $charset_collate = $wpdb->get_charset_collate();
    
    $sql = "CREATE TABLE {$table} (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        user_id BIGINT UNSIGNED NOT NULL,
        period VARCHAR(20) NOT NULL,
        quarter TINYINT NOT NULL,
        year SMALLINT NOT NULL,
        summary TEXT,
        recommendations LONGTEXT,
        metrics TEXT,
        raw_response LONGTEXT,
        generated_at DATETIME NOT NULL,
        PRIMARY KEY (id),
        KEY user_quarter (user_id, year, quarter)
    ) {$charset_collate};";
    
    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta($sql);
}

/**
 * Generate quarterly insights for a specific user (used by cron)
 * Does not require nonce verification - for internal use only
 */
function vmo_generate_quarterly_insights_for_user($user_id) {
    global $wpdb;

    // ✅ TOKEN SECURITY CHECK v8.1.0: Prevent negative arbitrage
    $current_balance = (int) get_user_meta($user_id, 'vmo_token_balance', true);
    if ($current_balance < 200) {
        // If this runs via cron, we just abort silently (or with log) rather than sending JSON
        return false;
    }
    
    // Calculate current quarter (generate for PREVIOUS quarter)
    $current_month = (int) date('n');
    $current_year = (int) date('Y');
    
    // We generate for the previous quarter
    $prev_quarter = ceil($current_month / 3) - 1;
    $prev_year = $current_year;
    if ($prev_quarter < 1) {
        $prev_quarter = 4;
        $prev_year--;
    }
    
    $period = "Q{$prev_quarter} {$prev_year}";
    
    // Quarter date range
    $quarter_start_month = (($prev_quarter - 1) * 3) + 1;
    $quarter_start = date('Y-m-d', mktime(0, 0, 0, $quarter_start_month, 1, $prev_year));
    $quarter_end = date('Y-m-d', mktime(23, 59, 59, $quarter_start_month + 2, date('t', mktime(0, 0, 0, $quarter_start_month + 2, 1, $prev_year)), $prev_year));
    
    $metrics = [
        'emails' => 0,
        'calls' => 0,
        'chats' => 0,
    ];
    
    $communications_summary = "Business Communications Summary for {$period}\n\n";
    
    // Get email stats
    $emails_table = $wpdb->prefix . 'vmo_email_sends';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$emails_table}'") === $emails_table) {
        $email_count = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$emails_table} WHERE user_id = %d AND created_at BETWEEN %s AND %s",
            $user_id, $quarter_start, $quarter_end . ' 23:59:59'
        ));
        $metrics['emails'] = $email_count;
        $communications_summary .= "EMAILS: {$email_count} sent\n\n";
    }
    
    // Get phone call stats
    $calls_table = $wpdb->prefix . 'vmo_phone_calls';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$calls_table}'") === $calls_table) {
        $call_count = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$calls_table} WHERE user_id = %d AND created_at BETWEEN %s AND %s",
            $user_id, $quarter_start, $quarter_end . ' 23:59:59'
        ));
        $metrics['calls'] = $call_count;
        $communications_summary .= "PHONE CALLS: {$call_count} total\n\n";
    }
    
    // Get chat stats
    $chat_table = $wpdb->prefix . 'vmo_chat_sessions';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$chat_table}'") === $chat_table) {
        $chat_count = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$chat_table} WHERE user_id = %d AND created_at BETWEEN %s AND %s",
            $user_id, $quarter_start, $quarter_end . ' 23:59:59'
        ));
        $metrics['chats'] = $chat_count;
        $communications_summary .= "CHAT SESSIONS: {$chat_count} total\n\n";
    }
    
    // Check if there's any activity worth reporting
    $total_activity = $metrics['emails'] + $metrics['calls'] + $metrics['chats'];
    if ($total_activity < 5) {
        // Not enough activity to generate meaningful insights
        return false;
    }
    
    // Get business context
    $business_name = get_user_meta($user_id, 'business_name', true) ?: 'the business';
    $business_type = get_user_meta($user_id, 'business_type', true) ?: 'business';
    
    // Generate AI insights
    $prompt = "You are an expert business consultant analyzing quarterly performance for {$business_name} ({$business_type}).

Based on the following quarterly communications data, provide:
1. A brief summary (2-3 sentences) of overall communication performance
2. 4-5 specific, actionable recommendations to improve business outcomes next quarter

{$communications_summary}

Format your response as:
SUMMARY:
[Your 2-3 sentence summary here]

RECOMMENDATIONS:
1. [First recommendation]
2. [Second recommendation]
3. [Third recommendation]
4. [Fourth recommendation]";

    $api_key = get_option('vmo_openai_api_key') ?: VMO_OPENAI_API_KEY;
    if (!$api_key) {
        return false;
    }
    
    // ✅ FIX: GPT-5.2 requires max_completion_tokens, not max_tokens
    $_qi2_model = defined('VMO_OPENAI_MODEL') ? VMO_OPENAI_MODEL : 'gpt-4o';
    $_qi2_body = array_merge([
        'model' => $_qi2_model,
        'messages' => [
            ['role' => 'system', 'content' => 'You are a helpful business consultant providing quarterly insights and recommendations.'],
            ['role' => 'user', 'content' => $prompt],
        ],
        'temperature' => 0.7,
    ], function_exists('vmo_get_token_param') ? vmo_get_token_param($_qi2_model, 600) : ['max_tokens' => 600]);

    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
        'timeout' => 55, // Capped: Kinsta 60s PHP limit
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type' => 'application/json',
        ],
        'body' => json_encode($_qi2_body),
    ]);
    
    if (is_wp_error($response)) {
        return false;
    }
    
    $body = json_decode(wp_remote_retrieve_body($response), true);
    $ai_response = $body['choices'][0]['message']['content'] ?? '';
    
    // Parse the response
    $summary = '';
    $recommendations = [];
    
    if (preg_match('/SUMMARY:\s*(.+?)(?=RECOMMENDATIONS:|$)/is', $ai_response, $match)) {
        $summary = trim($match[1]);
    }
    
    if (preg_match('/RECOMMENDATIONS:\s*(.+)/is', $ai_response, $match)) {
        $rec_text = trim($match[1]);
        preg_match_all('/\d+\.\s*(.+?)(?=\d+\.|$)/s', $rec_text, $rec_matches);
        if (!empty($rec_matches[1])) {
            foreach ($rec_matches[1] as $r) {
                $clean = trim($r);
                if ($clean) $recommendations[] = $clean;
            }
        }
    }
    
    if (!$summary) {
        $summary = $ai_response;
    }
    
    // Save report to database
    $reports_table = $wpdb->prefix . 'vmo_quarterly_reports';
    vmo_ensure_quarterly_reports_table();
    
    $report_data = [
        'user_id' => $user_id,
        'period' => $period,
        'quarter' => $prev_quarter,
        'year' => $prev_year,
        'summary' => $summary,
        'recommendations' => json_encode($recommendations),
        'metrics' => json_encode($metrics),
        'raw_response' => $ai_response,
        'generated_at' => current_time('mysql'),
    ];
    
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $wpdb->insert($reports_table, $report_data);
    
    // ✅ TOKEN LEAK FIX v8.2.1: Use centralized deduction with FIFO, admin bypass & audit trail
    $cost = vmo_get_token_cost('quarterly_insights');
    vmo_deduct_tokens($user_id, $cost, 'quarterly_insights', 'Automated quarterly insights for ' . $period);
    
    return true;
}

// ========================================
// END QUARTERLY BUSINESS INSIGHTS
// ========================================

function vmo_import_contacts() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    global $wpdb;
    $user_id = get_current_user_id();
    $rows_json = wp_unslash($_POST['rows'] ?? '');
    $type = sanitize_key($_POST['type'] ?? 'lead');
    $rows = json_decode($rows_json, true);
    if (!is_array($rows) || !count($rows)) {
        wp_send_json_error(['message' => 'No rows provided']);
    }
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    $has_table = vmo_table_exists($contacts_table);
    if (!$has_table) {
        wp_send_json_error(['message' => 'Contacts table missing']);
    }


    $inserted = 0;
    $skipped = 0;
    foreach ($rows as $r) {
        if (!is_array($r)) { $skipped++; continue; }
        $email = sanitize_email($r['email'] ?? '');
        $phone = sanitize_text_field($r['phone'] ?? '');
        $name = sanitize_text_field($r['name'] ?? '');
        $notes = sanitize_textarea_field($r['description'] ?? ($r['notes'] ?? ''));
        $tags = sanitize_text_field($r['tags'] ?? '');
        $company = sanitize_text_field($r['company'] ?? '');
        if ($name === '' && $email === '' && $phone === '') { $skipped++; continue; }
        // Deduplicate by email (best effort)
        if ($email) {
            $exists = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$contacts_table} WHERE user_id=%d AND email=%s", $user_id, $email));
            if ($exists) { $skipped++; continue; }
        }

        $contact_uuid = function_exists('wp_generate_uuid4') ? wp_generate_uuid4() : ('c_' . $user_id . '_' . time() . '_' . wp_rand(1000, 9999));
        $lifecycle = ($type === 'customer') ? 'customer' : 'new';
        $source = 'manual_entry';

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $ok = $wpdb->insert($contacts_table, [
            'user_id' => $user_id,
            'contact_id' => $contact_uuid,
            'source' => $source,
            'primary_category' => ($type === 'customer') ? 'customer' : 'lead',
            'secondary_category' => null,
            'confidence_score' => 0,
            'priority_level' => 'medium',
            'lifecycle_stage' => $lifecycle,
            'first_name' => null,
            'last_name' => null,
            'full_name' => $name ?: null,
            'email' => $email ?: null,
            'phone' => $phone ?: null,
            'company' => $company ?: null,
            'tags' => $tags ?: null,
            'notes' => $notes ?: null,
            'created_at' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
        ]);
        if ($ok) $inserted++; else $skipped++;
    }

    wp_send_json_success(['inserted' => $inserted, 'skipped' => $skipped]);
}

function vmo_get_tax_settings() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    $user_id = get_current_user_id();
    $settings = get_user_meta($user_id, 'vmo_tax_settings', true);
    if (!is_array($settings)) $settings = [];
    wp_send_json_success(['settings' => $settings]);
}

function vmo_save_tax_settings() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    $user_id = get_current_user_id();
    $country = sanitize_text_field(wp_unslash($_POST['country'] ?? ''));
    $state = sanitize_text_field(wp_unslash($_POST['state'] ?? ''));

    // Merge with existing settings to avoid overwriting REST-saved fields
    $existing = get_user_meta($user_id, 'vmo_tax_settings', true);
    if (!is_array($existing)) $existing = [];

    $settings = array_merge($existing, [
        'country' => $country,
        'state' => $state,
        'updated_at' => current_time('mysql'),
    ]);

    // Only set legacy fields if explicitly provided
    if (isset($_POST['profit_margin'])) {
        $settings['profit_margin'] = max(0.0, min(100.0, floatval($_POST['profit_margin'])));
    }
    if (isset($_POST['sales_tax_rate'])) {
        $settings['sales_tax_rate'] = max(0.0, min(100.0, floatval($_POST['sales_tax_rate'])));
    }
    if (isset($_POST['income_tax_rate'])) {
        $settings['income_tax_rate'] = max(0.0, min(100.0, floatval($_POST['income_tax_rate'])));
    }

    update_user_meta($user_id, 'vmo_tax_settings', $settings);
    wp_send_json_success(['settings' => $settings]);
}

function vmo_get_competitors_data() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        // Soften invalid nonce to avoid HTTP 400 noise in console
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }
    // With no external data, approximate "market share" as your orders vs top competitors from connected accounts (if any)
    global $wpdb;
    $user_id = get_current_user_id();
    $from = isset($_POST['from']) ? sanitize_text_field(wp_unslash($_POST['from'])) : '';
    $to   = isset($_POST['to'])   ? sanitize_text_field(wp_unslash($_POST['to']))   : '';
    $days = isset($_POST['days']) ? (int) $_POST['days'] : 30;
    $now  = current_time('mysql');
    if (!$to) $to = $now;
    if (!$from) $from = date('Y-m-d H:i:s', strtotime('-' . max(1,$days) . ' days', strtotime($to)));
    $orders_table = $wpdb->prefix . 'vmo_store_orders';
    $connected_accounts_table = $wpdb->prefix . 'vmo_connected_accounts';
    $has_orders = vmo_table_exists($orders_table);
    $has_accounts = vmo_table_exists($connected_accounts_table);

    $your_orders = $has_orders ? (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$orders_table} WHERE user_id=%d AND status='paid' AND created_at BETWEEN %s AND %s", $user_id, $from, $to)) : 0;
    $competitors = [];
    if ($has_accounts) {
        // take up to 5 competitor accounts as placeholders
        $rows = $wpdb->get_results($wpdb->prepare("SELECT account_name FROM {$connected_accounts_table} WHERE user_id=%d AND provider LIKE '%%competitor%%' LIMIT 5", $user_id));
        foreach (($rows?:[]) as $r) {
            $competitors[] = ['name' => (string) ($r->account_name ?: 'Competitor'), 'share' => 0];
        }
    }
    // naive distribution: assume each competitor has a similar order volume proxy
    $comp_count = count($competitors);
    $total_pool = $your_orders + ($comp_count * ($your_orders ? max(1, (int) round($your_orders*0.8)) : 0));
    $your_share = $total_pool > 0 ? round(($your_orders / $total_pool) * 100, 1) : 0;
    $leader_share = max($your_share, $comp_count ? round(((max(1, (int) round($your_orders*0.8))) / $total_pool) * 100, 1) : 0);
    $your_rank = ($leader_share > $your_share) ? 2 : 1;
    $monitored = $comp_count;
    // chart: simple trend of your share over last 30 days buckets (weekly)
    $labels = [];$chart_data = [];
    for ($i=4; $i>=0; $i--) {
        $labels[] = 'W'.(5-$i);
        $chart_data[] = $your_share; // keep flat until real data exists
    }
    wp_send_json_success([
        'your_share' => $your_share,
        'leader_share' => $leader_share,
        'your_rank' => $your_rank,
        'monitored' => $monitored,
        'competitors' => $competitors,
        'chart' => ['labels' => $labels, 'data' => $chart_data]
    ]);
}

/* --------------------------------------------------------------
   Enqueue Scripts & Styles  (Admin only – VMO screens) - UNIFIED VERSION
---------------------------------------------------------------- */
add_action( 'admin_enqueue_scripts', function ( $hook ) {
    // ⚡ PERFORMANCE: Only load VMO assets on VMO admin pages
    // Non-VMO pages (Posts, Media, Users, Plugins, etc.) skip this entirely.
    $hook = (string) ($hook ?? '');
    if ( strpos( $hook, 'vmo' ) === false ) {
        return;
    }

    /* 1️⃣  make absolutely sure jQuery ships first */
    wp_enqueue_script( 'jquery' );   // WP-bundled copy

    /* 2️⃣  core stylesheet + template styles */
    wp_enqueue_style(
        'vmo-builder-css',
        VMO_AI_BUILDER_URL . 'assets/builder.css',
        [],
        '8.0.0-' . time() // Force cache bust with timestamp - UNIFIED BUSINESS PLATFORM THEME
    );
    // Provide a datepicker fallback for Safari/older browsers
    wp_enqueue_script('jquery-ui-datepicker');
    // Minimal datepicker CSS to ensure visibility (WordPress may not ship UI theme by default)
    $inline_css = '.ui-datepicker{z-index:999999 !important;background:#0b0f1a;border:1px solid rgba(255,255,255,.15);color:#e6f3ff;border-radius:8px;padding:6px}.ui-datepicker .ui-datepicker-header{background:rgba(255,255,255,.06);border:0;border-bottom:1px solid rgba(255,255,255,.1);border-radius:8px 8px 0 0}.ui-datepicker td a{color:#e6f3ff}.ui-datepicker td a.ui-state-active{background:#0fe1ff;color:#04121a;border-radius:4px}';
    wp_add_inline_style('vmo-builder-css', $inline_css);
    
    wp_enqueue_style(
        'vmo-templates-css',
        VMO_AI_BUILDER_URL . 'assets/vmo-templates.css',
        ['vmo-builder-css'],
        '1.0.0-' . time()
    );

    // Business Wizard UI fixes (text overflow, button sizing, spacing)
    wp_enqueue_style(
        'vmo-wizard-ui-fixes',
        VMO_AI_BUILDER_URL . 'assets/vmo-wizard-ui-fixes.css',
        ['vmo-builder-css'],
        '1.0.0-' . time()
    );

    // Jarvis-style floating voice panel (text chat only, ready for OpenAI TTS HD)
    wp_enqueue_style(
        'vmo-voice-panel-css',
        VMO_AI_BUILDER_URL . 'assets/vmo-voice-panel.css',
        ['vmo-builder-css'],
        '1.0.0-' . time()
    );

    /* 3️⃣  base state (everything else hangs off this) */
    wp_enqueue_script(
        'vmo-state',
        VMO_AI_BUILDER_URL . 'assets/vmo-state.js',
        [ 'jquery' ],
        vmo_safe_filemtime( VMO_AI_BUILDER_DIR . 'assets/vmo-state.js' ),
        true
    );

    /* 4️⃣  functional modules (order matters only via dependencies) */

    // Pre-register OpenAI TTS HD client so other scripts can depend on it
    $tts_hd_js_path = VMO_AI_BUILDER_DIR . 'assets/vmo-tts-hd.js';
    if (file_exists($tts_hd_js_path)) {
        $tts_version = '9.0.1-FIXED-' . time() . '-' . filemtime($tts_hd_js_path);
        // Ensure the audio coordinator loads before the TTS client
        wp_register_script('vmo-tts-hd', VMO_AI_BUILDER_URL . 'assets/vmo-tts-hd.js', ['vmo-audio-coordinator'], $tts_version, true);
        // Localize REST API URL and nonce
        wp_localize_script('vmo-tts-hd', 'wpApiSettings', [
            'root' => esc_url_raw(rest_url()),
            'nonce' => wp_create_nonce('wp_rest')
        ]);
    }

    $modules = [
        'vmo-audio-coordinator' => [], // NEW: One-mic guard across modules
        'vmo-init'     => [],              // v8.0.0 initialization
        'vmo-ui'       => [],
        'vmo-tokens'   => [],
        'vmo-undo'     => [],
        'vmo-grapes-adapter' => [], // Day-1 GrapesJS adapter (agent mode, no UI changes)
        'vmo-render'   => ['vmo-grapes-adapter'],
        'vmo-prompts'  => [],
        'vmo-menu'     => [],
        'vmo-media'    => [],
        'vmo-media-generator' => [], // NEW: Auto-generate missing media
        'vmo-media-integration' => [], // NEW: AI-powered backend media integration
        'vmo-iframe-wrapper' => [], // NEW: High-quality iframe wrapper with GSAP & modern libraries
        'vmo-iframe-placeholder' => [], // NEW: Professional placeholder before consultation
        'vmo-refine'   => [],
        'vmo-element-refiner' => ['vmo-refine'], // NEW: Element-specific refinement system
        'vmo-ai-agents' => [],         // NEW: Dedicated AI agents for different functions
        'vmo-projects' => [],
        'vmo-publish'  => ['vmo-ui'], // Needs showToast function
        'vmo-theme' => ['vmo-main'], // Replace button system - needs vmo-main for vmoOpenMediaPicker
        // 'vmo-voice'    => [],          // DISABLED: Legacy voice system (replaced by v8)
        // ✅ v9.0.0 CLEAN ARCHITECTURE: Two dedicated AI files only
        'vmo-tts-hd' => [], // ✅ OpenAI TTS Client (Must be loaded before dependents)
        'vmo-voice-panel' => ['vmo-tts-hd'], // ✅ JARVIS AI depends on TTS client
        'vmo-intelligent-consultation' => ['vmo-tts-hd'], // ✅ V2 CONSULTATION SYSTEM with persistent memory (uses new V2 file)
        'vmo-refinement-agents' => ['vmo-ai-agents'], // Multi-agent refinement system
        // 'vmo-conversational' => [], // ❌ DISABLED: Using vmo-intelligent-consultation instead
        // 'vmo-enhanced-business-wizard' => [], // REMOVED: Duplicate Business Consultant handler
        'vmo-forms'    => [],          // NEW: Enhanced forms for business wizard
        'vmo-templates' => [],         // NEW: Template system
        'vmo-template-integration' => ['vmo-templates'], // NEW: Template integration
        // 'vmo-business-dashboard' => [], // Removed frontend overlay; use WP Admin dashboard page instead
        'vmo-seo'      => [],          // NEW: SEO optimization features
        'vmo-main'     => ['media-upload', 'media-views'], // Add WordPress media dependencies
        'vmo-plans'    => [],          // plan picker / Stripe
        'vmo-payments' => [],          // product buy buttons (Stripe Connect)
        'vmo-queue'    => [],          // approval queue admin UI
        'vmo-video-generator' => [],   // NEW: Sora video generation UI
        'vmo-360-spin-viewer' => [],   // NEW: 360° product spin viewer
        'vmo-animated-overlays' => [], // NEW: Animated overlay effects (clouds, smoke, particles)
    ];

    foreach ( $modules as $handle => $extraDeps ) {
        $file_path = VMO_AI_BUILDER_DIR . "assets/{$handle}.js";
        
        // Special case for media integration - use backend version
        if ($handle === 'vmo-media-integration') {
            $file_path = VMO_AI_BUILDER_DIR . "assets/vmo-media-integration-backend.js";
        }
        
        if (file_exists($file_path)) {
            // ✅ FORCE CACHE BUST: Multiple strategies to force new files to load
            // Strategy 1: Manual version + timestamp
            // Strategy 2: Use current time for critical files
            // Strategy 3: Add unique query string
            $base_version = filemtime($file_path);
            
            // For critical fixed files, add current timestamp to force reload
            $version = ($handle === 'vmo-voice-panel' || $handle === 'vmo-intelligent-consultation' || $handle === 'vmo-tts-hd') 
                ? '9.0.1-FIXED-' . time() . '-' . $base_version  // FORCE RELOAD - V2 consultation system
                : $base_version;
            
            wp_enqueue_script(
                $handle,
                VMO_AI_BUILDER_URL . ($handle === 'vmo-media-integration' ? "assets/vmo-media-integration-backend.js" : "assets/{$handle}.js"),
                array_merge( [ 'jquery', 'vmo-state' ], $extraDeps ),
                $version,
                true
            );

            // Localize TTS settings immediately
            if ($handle === 'vmo-tts-hd') {
                wp_localize_script('vmo-tts-hd', 'wpApiSettings', [
                    'root' => esc_url_raw(rest_url()),
                    'nonce' => wp_create_nonce('wp_rest')
                ]);
            }
        }
    }

    if ( wp_script_is( 'vmo-grapes-adapter', 'enqueued' ) ) {
        $grapes_api_key = trim((string) get_option('vmo_grapesjs_api_key', ''));
        $grapes_active = $grapes_api_key !== '';

        wp_localize_script( 'vmo-grapes-adapter', 'vmoGrapesConfig', [
            'activation_required' => true,
            'activation_status' => $grapes_active,
            'api_key_last4' => $grapes_active ? substr($grapes_api_key, -4) : '',
            'agent_mode' => true,
            'cdn_js' => esc_url_raw( apply_filters( 'vmo_grapesjs_cdn_js', 'https://unpkg.com/grapesjs@0.21.10/dist/grapes.min.js' ) ),
            'cdn_css' => esc_url_raw( apply_filters( 'vmo_grapesjs_cdn_css', 'https://unpkg.com/grapesjs@0.21.10/dist/css/grapes.min.css' ) ),
            'debug' => (bool) ( defined( 'WP_DEBUG' ) && WP_DEBUG ),
        ] );
    }


    /* 6️⃣  GSAP and ScrollTrigger for advanced animations (bundled locally) */
    wp_enqueue_script( 'gsap-core', VMO_AI_BUILDER_URL . 'assets/vendor/gsap.min.js', [], '3.12.2', true );
    wp_enqueue_script( 'gsap-scrolltrigger', VMO_AI_BUILDER_URL . 'assets/vendor/ScrollTrigger.min.js', ['gsap-core'], '3.12.2', true );
    
    /* 7️⃣  AOS animation library (bundled locally) */
    wp_enqueue_style(  'aos-css', VMO_AI_BUILDER_URL . 'assets/vendor/aos.css', [], '2.3.1' );
    wp_enqueue_script( 'aos-js',  VMO_AI_BUILDER_URL . 'assets/vendor/aos.min.js',  [], '2.3.1', true );

    /* 8️⃣  WordPress media uploader (image / video uploads) */
    wp_enqueue_media();

    /* 9️⃣  localisation for AJAX + user state */
        wp_localize_script( 'vmo-main', 'vmoAjax', [
        'ajax_url' => admin_url( 'admin-ajax.php' ),
        'nonce' => wp_create_nonce( 'vmo_nonce' ),
    'pluginUrl' => VMO_AI_BUILDER_URL,  // Add plugin URL for templates
    // Used by Jarvis-style greeting logic in assets/vmo-main.js
    'has_published_site' => vmo_user_has_published_site(get_current_user_id()),
        'builder_reset_mode' => function_exists('vmo_builder_reset_mode_active') ? (bool) vmo_builder_reset_mode_active(get_current_user_id()) : false,
    ]);

    // 🔧 Compatibility: Also localize as vmo_ajax for legacy JavaScript files
    $consultation_profile = function_exists('vmo_get_consultation_profile') ? vmo_get_consultation_profile(get_current_user_id()) : [];
    $profile_system = !empty($consultation_profile) ? "BUSINESS CONSULTATION PROFILE (authoritative context):\n" . substr(wp_json_encode($consultation_profile, JSON_PRETTY_PRINT), 0, 6000) : '';
    
    // Build upgrade URL with user context (used by credit enforcement system)
    $installation_id_early = get_option('vmo_installation_id', '');
    $upgrade_url_early = 'https://jerremejosephson.com/vmo-upgrade/' . (!empty($installation_id_early) ? '?installation=' . urlencode($installation_id_early) : '');
    $topup_url_early = 'https://jerremejosephson.com/vmo-topup/' . (!empty($installation_id_early) ? '?installation=' . urlencode($installation_id_early) : '');
    
    wp_localize_script( 'vmo-main', 'vmo_ajax', [
        'ajaxurl'       => admin_url( 'admin-ajax.php' ),
        'nonce'         => wp_create_nonce( 'vmo_builder_nonce' ),
        // CLIENT VERSION: Require an active subscription from the master server
        'current_plan'  => get_user_meta( get_current_user_id(), 'vmo_subscription_status', true ),
        'token_balance' => (int) get_user_meta( get_current_user_id(), 'vmo_token_balance', true ),
        'upgradeUrl'    => $upgrade_url_early,
        'topupUrl'      => $topup_url_early,
        // Welcome bonus notification for new Starter plan users
        'pending_notification' => get_user_meta( get_current_user_id(), 'vmo_pending_notification', true ),
        // Background ops upgrade prompts (Tester plan missed operations)
        'upgrade_prompts' => get_user_meta( get_current_user_id(), 'vmo_upgrade_prompts', true ) ?: [],
    'payments_enabled' => (bool) VMO_PAYMENTS_ENABLED,
        'stripe' => [
            'pk' => get_option('vmo_stripe_publishable_key', ''),
            'platform_fee_bps' => (int) get_option('vmo_stripe_platform_fee_bps', 50)
        ],
        // OpenAI TTS HD Voice Settings
        'voice' => [
            'enabled' => true,
            'provider' => 'openai',
            'voice_id' => get_option('vmo_voice_id', 'alloy'),
            'speed' => (float) get_option('vmo_voice_speed', 1.0),
            'hd' => true
        ],
        'business_wizard' => [
            'enabled' => true,
            'cost_per_session' => VMO_TOKENS_BUSINESS_WIZARD,
            'generation_cost' => VMO_TOKENS_GENERATE_WEBSITE
        ],
        'pluginUrl' => plugin_dir_url( __FILE__ ),
        'userId' => get_current_user_id(),
        // Builder gating flags and niceties
        'generation_unlocked' => function_exists('vmo_is_consultation_ready') ? (bool) vmo_is_consultation_ready(get_current_user_id()) : false,
        'user_display_name' => vmo_get_greeting_display_name(),
        'builder_reset_mode' => function_exists('vmo_builder_reset_mode_active') ? (bool) vmo_builder_reset_mode_active(get_current_user_id()) : false
    ] );

    // Expose consultation system text globally for prompts
    wp_add_inline_script('vmo-main', 'window.vmoConsultationProfileSystem = '. wp_json_encode($profile_system) .';', 'before');

    // ✅ v9.0.0: Localize AJAX data for both AI systems
    // Build upgrade URL with user context
    $installation_id = get_option('vmo_installation_id', '');
    $upgrade_url = 'https://jerremejosephson.com/vmo-upgrade/' . (!empty($installation_id) ? '?installation=' . urlencode($installation_id) : '');
    $topup_url = 'https://jerremejosephson.com/vmo-topup/' . (!empty($installation_id) ? '?installation=' . urlencode($installation_id) : '');
    
    $ajax_data = [
        'ajaxurl'       => admin_url('admin-ajax.php'),
        'nonce'         => wp_create_nonce('vmo_builder_nonce'),
        'security'      => wp_create_nonce('vmo_builder_nonce'),
        // CLIENT VERSION: Require an active subscription from the master server
        'current_plan'  => get_user_meta(get_current_user_id(), 'vmo_subscription_status', true),
        'token_balance' => (int) get_user_meta(get_current_user_id(), 'vmo_token_balance', true),
        'upgradeUrl'    => $upgrade_url,
        'topupUrl'      => $topup_url,
        'business_wizard' => [
            'enabled' => true,
            'cost_per_session' => defined('VMO_TOKENS_BUSINESS_WIZARD') ? VMO_TOKENS_BUSINESS_WIZARD : 1500,
            'generation_cost' => defined('VMO_TOKENS_GENERATE_WEBSITE') ? VMO_TOKENS_GENERATE_WEBSITE : 2500
        ],
        'pluginUrl' => plugin_dir_url(__FILE__),
        'userId' => get_current_user_id(),
        'user_display_name' => vmo_get_greeting_display_name(),
        'builder_reset_mode' => function_exists('vmo_builder_reset_mode_active') ? (bool) vmo_builder_reset_mode_active(get_current_user_id()) : false,
        // Background ops upgrade prompts (Tester plan missed operations)
        'upgrade_prompts' => get_user_meta(get_current_user_id(), 'vmo_upgrade_prompts', true) ?: [],
    ];
    
    // Token system (needs vmo_ajax early)
    if (wp_script_is('vmo-tokens', 'enqueued')) {
        wp_localize_script('vmo-tokens', 'vmo_ajax', $ajax_data);
    }
    
    // Jarvis AI (Voice Panel)
    if (wp_script_is('vmo-voice-panel', 'enqueued')) {
        wp_localize_script('vmo-voice-panel', 'vmoAjax', $ajax_data);
        wp_localize_script('vmo-voice-panel', 'vmo_ajax', $ajax_data);
    }
    
    // Business Consultant AI - V2 (Persistent Memory System)
    if (wp_script_is('vmo-intelligent-consultation', 'enqueued')) {
        wp_localize_script('vmo-intelligent-consultation', 'vmoAjax', $ajax_data);
        wp_localize_script('vmo-intelligent-consultation', 'vmo_ajax', $ajax_data);

        // Belt-and-suspenders: ensure wpApiSettings is also available via consultation script
        // (normally provided by vmo-tts-hd dependency, but adding here for resilience)
        wp_add_inline_script('vmo-intelligent-consultation',
            'if(typeof window.wpApiSettings==="undefined"){window.wpApiSettings=' . wp_json_encode([
                'root'  => esc_url_raw(rest_url()),
                'nonce' => wp_create_nonce('wp_rest'),
            ]) . ';}',
            'before'
        );

        $greeting_text = function_exists('vmo_get_consultation_greeting_text') ? vmo_get_consultation_greeting_text() : '';
        $greeting_user_name = vmo_get_greeting_display_name();
        $greeting_bootstrap = [
            'text' => $greeting_text,
            'userName' => $greeting_user_name,
            'audioUrl' => '',
            'generatedAt' => null,
            'expiresAt' => null,
            'voice' => apply_filters('vmo_consultation_greeting_voice', 'ash'),
            'restEndpoint' => esc_url_raw(rest_url('vmo/v1/greeting-audio')),
        ];

        if (class_exists('VMO_Greeting_Audio_Manager')) {
            $greeting_manager = VMO_Greeting_Audio_Manager::get_instance();
            $audio_meta = $greeting_manager->get_audio_metadata(true);
            if (!is_wp_error($audio_meta) && !empty($audio_meta['url'])) {
                $greeting_bootstrap['audioUrl'] = esc_url_raw($audio_meta['url']);
                $greeting_bootstrap['generatedAt'] = isset($audio_meta['generated_at']) ? (int) $audio_meta['generated_at'] : null;
                $greeting_bootstrap['expiresAt'] = isset($audio_meta['expires_at']) ? (int) $audio_meta['expires_at'] : null;
                if (!empty($audio_meta['voice'])) {
                    $greeting_bootstrap['voice'] = $audio_meta['voice'];
                }
            }
        }

        $inline_greeting_bootstrap = 'window.vmoGreetingBootstrap = ' . wp_json_encode($greeting_bootstrap) . ';'
            . 'window.vmoGreetingText = window.vmoGreetingBootstrap?.text || window.vmoGreetingText;'
            . 'window.vmoGreetingAudioUrl = window.vmoGreetingBootstrap?.audioUrl || window.vmoGreetingAudioUrl;';
        wp_add_inline_script('vmo-intelligent-consultation', $inline_greeting_bootstrap, 'before');
    }

    // VOICE SYSTEM REMOVED: Voice system v8.0 localization removed for No Voice version
    // Voice system localization was here - removed because vmo-voice-system-v8 script doesn't exist

    // Enhanced Business Phone Admin assets
    $screen = get_current_screen();
    if ($screen && isset($screen->id) && strpos($screen->id, 'vmo-phone') !== false) {
        // CSS
        if (file_exists(VMO_AI_BUILDER_DIR . 'assets/vmo-phone.css')) {
            wp_enqueue_style('vmo-phone', VMO_AI_BUILDER_URL . 'assets/vmo-phone.css', [], vmo_safe_filemtime(VMO_AI_BUILDER_DIR . 'assets/vmo-phone.css'));
        }
        // Enhanced Business Phone Admin JS
        if (file_exists(VMO_AI_BUILDER_DIR . 'assets/vmo-business-phone-admin.js')) {
            wp_enqueue_script('vmo-business-phone-admin', VMO_AI_BUILDER_URL . 'assets/vmo-business-phone-admin.js', ['jquery'], vmo_safe_filemtime(VMO_AI_BUILDER_DIR . 'assets/vmo-business-phone-admin.js'), true);
            wp_localize_script('vmo-business-phone-admin', 'vmo_admin_ajax', [
                'ajaxurl' => admin_url('admin-ajax.php'),
                'nonce'   => wp_create_nonce('vmo_admin_nonce'),
                'costs'   => [
                    'twilio_per_minute' => 0.10, // Actual AI voice cost (not basic PSTN)
                    'vmo_tokens_per_minute' => 300, // 2x markup for profitability
                    'openai_per_1k_tokens' => 0.03
                ],
                'markup'  => get_option('vmo_phone_markup_multiplier', 2), // 2x markup (was 9x)
                'strings' => [
                    'activating' => 'Activating...',
                    'deactivating' => 'Deactivating...',
                    'processing' => 'Processing...',
                    'confirmDeactivate' => 'Are you sure you want to deactivate your phone service?',
                    'networkError' => 'Network error occurred'
                ]
            ]);
        }
    }
} );

// Ensure business dashboard JS is loaded on our Admin Dashboard page
add_action('admin_enqueue_scripts', function() {
    $page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
    // Allow dashboard JS on both the main dashboard AND the master admin
    if ($page !== 'vmo-ai-dashboard' && $page !== 'vmo-master-admin') return;
    $path = VMO_AI_BUILDER_DIR . 'assets/vmo-business-dashboard.js';
    if (file_exists($path)) {
        wp_enqueue_script('vmo-business-dashboard', VMO_AI_BUILDER_URL . 'assets/vmo-business-dashboard.js', ['jquery', 'vmo-state'], filemtime($path), true);
        
        // Build robust AJAX data for dashboard (must include plan status for gatekeeper)
        $installation_id = get_option('vmo_installation_id', '');
        $upgrade_url = 'https://jerremejosephson.com/vmo-upgrade/' . (!empty($installation_id) ? '?installation=' . urlencode($installation_id) : '');
        $topup_url = 'https://jerremejosephson.com/vmo-topup/' . (!empty($installation_id) ? '?installation=' . urlencode($installation_id) : '');
        
        $dashboard_ajax_data = [
            'ajaxurl'       => admin_url('admin-ajax.php'),
            'rest_url'      => get_rest_url(null, 'vmo/v1'),
            'nonce'         => wp_create_nonce('vmo_builder_nonce'),
            'vmo_nonce'     => wp_create_nonce('vmo_nonce'), // Added standard nonce for Twilio/Phone operations
            'security'      => wp_create_nonce('vmo_builder_nonce'),
            'current_plan'  => get_user_meta(get_current_user_id(), 'vmo_subscription_status', true),
            'has_pack'      => get_user_meta(get_current_user_id(), 'vmo_has_phone_pack', true) === 'true',
            'token_balance' => (int) get_user_meta(get_current_user_id(), 'vmo_token_balance', true),
            'payments_enabled' => (bool) defined('VMO_PAYMENTS_ENABLED') ? VMO_PAYMENTS_ENABLED : true,
            'upgradeUrl'    => $upgrade_url,
            'topupUrl'      => $topup_url,
            'stripe' => [
                'pk' => get_option('vmo_stripe_publishable_key', ''),
            ],
            'userId' => get_current_user_id(),
            'user_display_name' => vmo_get_greeting_display_name(),
            'plugin_url' => VMO_AI_BUILDER_URL
        ];
        
        wp_localize_script('vmo-business-dashboard', 'vmo_ajax', $dashboard_ajax_data);
    }
    
    // ═══════════════════════════════════════════════════════════════════════════════
    // ✅ JARVIS AI VOICE PANEL - Same panel as in the builder, with same chat history
    // This allows users to continue Jarvis conversations inside the dashboard
    // ═══════════════════════════════════════════════════════════════════════════════
    
    // Audio Coordinator (must load first)
    $audio_coord_path = VMO_AI_BUILDER_DIR . 'assets/vmo-audio-coordinator.js';
    if (file_exists($audio_coord_path)) {
        wp_enqueue_script('vmo-audio-coordinator', VMO_AI_BUILDER_URL . 'assets/vmo-audio-coordinator.js', ['jquery'], filemtime($audio_coord_path), true);
    }
    
    // Voice Panel CSS
    $voice_panel_css_path = VMO_AI_BUILDER_DIR . 'assets/vmo-voice-panel.css';
    if (file_exists($voice_panel_css_path)) {
        wp_enqueue_style('vmo-voice-panel-css', VMO_AI_BUILDER_URL . 'assets/vmo-voice-panel.css', [], filemtime($voice_panel_css_path));
    }
    
    // OpenAI TTS HD Voice Selector
    $voice_selector_js_path = VMO_AI_BUILDER_DIR . 'assets/vmo-voice-selector.js';
    $voice_selector_css_path = VMO_AI_BUILDER_DIR . 'assets/vmo-voice-selector.css';
    
    if (file_exists($voice_selector_js_path)) {
        wp_enqueue_script('vmo-voice-selector', VMO_AI_BUILDER_URL . 'assets/vmo-voice-selector.js', ['jquery'], filemtime($voice_selector_js_path), true);
    }
    
    if (file_exists($voice_selector_css_path)) {
        wp_enqueue_style('vmo-voice-selector', VMO_AI_BUILDER_URL . 'assets/vmo-voice-selector.css', [], filemtime($voice_selector_css_path));
    }
    
    // OpenAI TTS HD REST API Client (No API key exposure on frontend)
    $tts_hd_js_path = VMO_AI_BUILDER_DIR . 'assets/vmo-tts-hd.js';
    if (file_exists($tts_hd_js_path)) {
        // FORCE CACHE BUST - Oct 30 2025 critical fix
        $tts_version = '9.0.1-FIXED-' . time() . '-' . filemtime($tts_hd_js_path);
        wp_enqueue_script('vmo-tts-hd', VMO_AI_BUILDER_URL . 'assets/vmo-tts-hd.js', ['vmo-audio-coordinator'], $tts_version, true);
        // Localize REST API URL and nonce
        wp_localize_script('vmo-tts-hd', 'wpApiSettings', [
            'root' => esc_url_raw(rest_url()),
            'nonce' => wp_create_nonce('wp_rest')
        ]);
        if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: TTS HD client enqueued successfully with version ' . $tts_version);
    } else {
        if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: TTS HD client file missing - voice features will not work');
    }
    
    // ✅ JARVIS AI VOICE PANEL - Main voice panel script
    $voice_panel_js_path = VMO_AI_BUILDER_DIR . 'assets/vmo-voice-panel.js';
    if (file_exists($voice_panel_js_path)) {
        $voice_panel_version = '9.0.0-DASHBOARD-' . time() . '-' . filemtime($voice_panel_js_path);
        wp_enqueue_script('vmo-voice-panel', VMO_AI_BUILDER_URL . 'assets/vmo-voice-panel.js', ['jquery', 'vmo-tts-hd', 'vmo-audio-coordinator'], $voice_panel_version, true);
        // Localize AJAX globals for Jarvis
        wp_localize_script('vmo-voice-panel', 'vmo_ajax', [
            'ajaxurl' => admin_url('admin-ajax.php'),
            'nonce'   => wp_create_nonce('vmo_builder_nonce'),
        ]);
        wp_localize_script('vmo-voice-panel', 'vmoAjax', [
            'ajaxurl' => admin_url('admin-ajax.php'),
            'nonce'   => wp_create_nonce('vmo_builder_nonce'),
        ]);
        if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: Voice Panel enqueued for dashboard with version ' . $voice_panel_version);
    }
    
    // ✅ NEW V2 CONSULTATION SYSTEM - Clean, persistent, works like MVP
    $v2_js_path = VMO_AI_BUILDER_DIR . 'assets/vmo-business-consultation-v2.js';
    if (file_exists($v2_js_path)) {
        $v2_version = '2.0.0-PERSISTENT-' . time() . '-' . filemtime($v2_js_path);
        wp_enqueue_script('vmo-consultation-v2', VMO_AI_BUILDER_URL . 'assets/vmo-business-consultation-v2.js', ['jquery'], $v2_version, true);
        if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: Consultation V2 enqueued with version ' . $v2_version);
    } else {
        if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO: Consultation V2 file missing');
    }
});

// === Admin AJAX: DB status check and repair (for dashboard badge) ===
function vmo_db_required_tables() {
    global $wpdb;
    return [
        $wpdb->prefix . 'vmo_projects',
        $wpdb->prefix . 'vmo_project_history',
        $wpdb->prefix . 'vmo_consultations',
        $wpdb->prefix . 'vmo_approval_queue',
        $wpdb->prefix . 'vmo_contacts',
        $wpdb->prefix . 'vmo_contact_interactions',
        $wpdb->prefix . 'vmo_contact_purchases',
        $wpdb->prefix . 'vmo_contact_preferences',
        $wpdb->prefix . 'vmo_voice_conversations',
        $wpdb->prefix . 'vmo_voice_conversation_turns',
    ];
}

function vmo_db_tables_status() {
    global $wpdb;
    $tables = vmo_db_required_tables();
    $status = [];
    foreach ($tables as $t) {
        $exists = (bool) $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($t)));
        $status[$t] = $exists;
    }
    return $status;
}

add_action('wp_ajax_vmo_db_status_check', function() {
    // flexible nonce: accept 'security' or 'nonce'
    $nonce = isset($_POST['security']) ? $_POST['security'] : (isset($_POST['nonce']) ? $_POST['nonce'] : '');
    if (!wp_verify_nonce($nonce, 'vmo_builder_nonce')) {
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    $status = vmo_db_tables_status();
    $ok = !in_array(false, array_values($status), true);
    $last_scan = (int) get_option('vmo_last_deep_scan_ts', 0);
    $last_update = (int) get_option('vmo_dashboard_last_update_ts', 0);
    wp_send_json_success([
        'ok' => $ok,
        'status' => $status,
        'missing' => array_keys(array_filter($status, function($v){ return !$v; })),
        'last_scan_ts' => $last_scan,
        'last_update_ts' => $last_update,
    ]);
});

add_action('wp_ajax_vmo_db_status_repair', function() {
    $nonce = isset($_POST['security']) ? $_POST['security'] : (isset($_POST['nonce']) ? $_POST['nonce'] : '');
    if (!wp_verify_nonce($nonce, 'vmo_builder_nonce')) {
        wp_send_json_error(['message' => 'Invalid nonce'], 403);
    }
    try {
        vmo_create_plugin_tables();
    } catch (Throwable $e) {
        // continue to re-check status; if still missing, report error
    }
    $status = vmo_db_tables_status();
    $ok = !in_array(false, array_values($status), true);
    wp_send_json_success([
        'ok' => $ok,
        'status' => $status,
        'missing' => array_keys(array_filter($status, function($v){ return !$v; })),
    ]);
});

// === Subscription Checkout Handler ===
// MOVED TO: includes/modules/subscription/class-vmo-subscription-manager.php
// add_action('wp_ajax_vmo_subscription_checkout', function() {
//     check_ajax_referer('vmo_builder_nonce', 'security');
//     ...
// });

// === Token Top-Up Checkout Handler ===
add_action('wp_ajax_vmo_topup_checkout', function() {
    check_ajax_referer('vmo_builder_nonce', 'security');

    $topup = sanitize_text_field($_POST['topup'] ?? '');
    $user_id = get_current_user_id();
    $user = get_userdata($user_id);

    if (!$user || empty($user->user_email)) {
        wp_send_json_error(['message' => 'User email is missing.']);
    }

    // ── Client mode: route top-up through master checkout proxy ──
    if (defined('VMO_CLIENT_MODE') && VMO_CLIENT_MODE === true) {
        // Map top-up tiers to plan-style names the master recognizes
        $topup_to_plan = [
            'phone_m' => 'phone_m', 'phone_l' => 'phone_l',
            'power_dialer' => 'power_dialer', 'phone_xl' => 'phone_xl',
        ];
        $proxy_plan = $topup_to_plan[$topup] ?? '';
        if (!$proxy_plan) {
            // General top-ups: send raw topup tier + amount to master
            $proxy_plan = 'topup_' . $topup;
        }

        $proxy_base = defined('VMO_MASTER_PROXY_URL')
            ? preg_replace('#/proxy$#', '', VMO_MASTER_PROXY_URL)
            : rtrim(get_option('vmo_master_proxy_url', 'https://getvmo.com/wp-json/vmo-builder/v1/proxy'), '/proxy');

        $response = wp_remote_post($proxy_base . '/stripe-checkout/create', [
            'headers' => [
                'Content-Type'       => 'application/json',
                'X-VMO-Installation' => get_option('vmo_installation_id', ''),
                'X-VMO-License'      => get_option('vmo_license_key', ''),
                'X-VMO-Plan'         => get_user_meta($user_id, 'vmo_subscription_status', true),
            ],
            'body'    => wp_json_encode([
                'plan'           => $proxy_plan,
                'billing_cycle'  => 'monthly',
                'customer_email' => $user->user_email,
                'success_url'    => admin_url('admin.php?page=vmo-my-plan&payment=success&purchase=addon&session_id={CHECKOUT_SESSION_ID}'),
                'cancel_url'     => admin_url('admin.php?page=vmo-my-plan'),
            ]),
            'timeout' => 30,
        ]);

        if (is_wp_error($response)) {
            wp_send_json_error(['message' => 'Could not connect to payment server.']);
            return;
        }
        $result = json_decode(wp_remote_retrieve_body($response), true);
        if (!empty($result['checkout_url'])) {
            wp_send_json_success(['checkout_url' => $result['checkout_url']]);
        } else {
            wp_send_json_error(['message' => $result['message'] ?? 'Payment initialization failed.']);
        }
        return;
    }
    
    // Phone Packs require an active subscription plan (bypassed in master version)
    $phone_packs = ['phone_s', 'phone_m', 'phone_l', 'power_dialer'];
    if (in_array($topup, $phone_packs) && !defined('VMO_MASTER_VERSION') || (defined('VMO_MASTER_VERSION') && !VMO_MASTER_VERSION)) {
        $plan = get_user_meta($user_id, 'vmo_subscription_status', true);
        $active_plans = ['tester', 'starter', 'pro', 'elite', 'enterprise', 'starter_pack', 'pro_pack', 'business_pack', 'enterprise_pack', 'business'];
        
        if (empty($plan) || !in_array(strtolower($plan), $active_plans)) {
            wp_send_json_error([
                'message' => 'Phone Packs require an active subscription plan. Please upgrade your plan first.',
                'requires_plan' => true
            ]);
        }
    }

    // Validate top-up tier and map to configured Stripe Price IDs
    $topup_price_option_map = [
        // General Add-Ons
        'small' => 'vmo_price_topup_small',
        'medium' => 'vmo_price_topup_medium',
        'large' => 'vmo_price_topup_large',
        // Phone Booster Packs
        'phone_s' => 'vmo_manager_stripe_phone_pack_s_price_id',
        'phone_m' => 'vmo_manager_stripe_phone_pack_m_price_id',
        'phone_l' => 'vmo_manager_stripe_phone_pack_l_price_id',
        'power_dialer' => 'vmo_manager_stripe_power_dialer_pack_price_id',
    ];

    if (!isset($topup_price_option_map[$topup])) {
        wp_send_json_error(['message' => 'Invalid selection.']);
    }

    $price_id = trim((string) get_option($topup_price_option_map[$topup]));
    if (!$price_id) {
        wp_send_json_error(['message' => 'This add-on is not configured. Please contact support.']);
    }

    $secret = get_option('vmo_stripe_secret_key');
    if (!$secret) {
        wp_send_json_error(['message' => 'Stripe is not configured. Please contact support.']);
    }

    // Load Stripe SDK
    $stripe_init_file = VMO_AI_BUILDER_DIR . 'stripe-sdk/init.php';
    if (!file_exists($stripe_init_file) || !is_readable($stripe_init_file)) {
        wp_send_json_error(['message' => 'Payment system not available.']);
    }

    try {
        require_once $stripe_init_file;
        \Stripe\Stripe::setApiKey($secret);

        // Create one-time payment checkout session
        $session = \Stripe\Checkout\Session::create([
            'payment_method_types' => ['card'],
            'mode' => 'payment',  // One-time payment, not subscription
            'client_reference_id' => (string) $user_id,
            'customer_email' => $user->user_email,
            'line_items' => [[
                'price' => $price_id,
                'quantity' => 1
            ]],
            'success_url' => admin_url('admin.php?page=vmo-my-plan&payment=success&purchase=addon&session_id={CHECKOUT_SESSION_ID}'),
            'cancel_url' => admin_url('admin.php?page=vmo-my-plan'),
            'metadata' => [
                'type' => 'token_topup',
                'topup_tier' => $topup,
                'user_id' => $user_id
            ]
        ]);

        wp_send_json_success(['checkout_url' => $session->url]);
    } catch (Exception $e) {
        if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO Top-Up Checkout Error: ' . $e->getMessage());
        wp_send_json_error(['message' => 'Unable to create checkout session. Please try again.']);
    }
});

// === Register Settings ===
add_action('admin_init', function () {
    foreach ([
        'vmo_openai_api_key',
        'vmo_grapesjs_api_key',
        'vmo_stripe_secret_key',
    'vmo_stripe_publishable_key',
        'vmo_stripe_webhook_secret',
    'vmo_payments_enabled',
    'vmo_stripe_platform_fee_bps',
    // Legacy price IDs (kept for back-compat; not shown in UI)
    'vmo_price_starter',
    'vmo_price_pro',
    'vmo_price_elite',
    'vmo_price_topup',
    // Top-up (one-time add-on) Stripe Price IDs
    'vmo_price_topup_small',
    'vmo_price_topup_medium',
    'vmo_price_topup_large',
    // Current plan product/price IDs (5 tiers)
    'vmo_plan_tester_product',      // $49.99/month - Tester plan
    'vmo_plan_starter_product',     // $99.99/month - Starter plan
    'vmo_plan_pro_product',         // $199.99/month - Pro plan
    'vmo_plan_elite_product',       // $499.99/month - Elite plan (formerly business)
    'vmo_plan_business_product',    // Legacy: maps to elite
    'vmo_plan_enterprise_product',  // $999.99/month - Enterprise plan
    'vmo_price_tester',             // Stripe price ID for Tester
    'vmo_price_enterprise',         // Stripe price ID for Enterprise
    // Annual Price IDs
    'vmo_price_tester_annual',
    'vmo_price_starter_annual',
    'vmo_price_pro_annual',
    'vmo_price_elite_annual',
    'vmo_price_enterprise_annual',
        'vmo_enable_whisper',
        'vmo_enable_voice_chat',
        // VOICE SYSTEM REMOVED: Voice settings removed for No Voice version
        // Twilio integration
        'vmo_twilio_account_sid',
        'vmo_twilio_auth_token',
        'vmo_twilio_api_key_sid',
        'vmo_twilio_api_key_secret',
        'vmo_twilio_voice_webhook_url',
        'vmo_twilio_status_callback_url',
        'vmo_twilio_ai_stream_url',
        'vmo_twilio_phone_number',
    'vmo_enable_phone_assistant',
    // Node voice service + integration
    'vmo_node_service_url',
    'vmo_node_service_token',
    // Owner/alert preferences used by realtime manager
    'vmo_owner_phone_number',
    'vmo_auto_dial_owner',
    'vmo_owner_dial_delay',
    'vmo_enable_sms_alerts',
    'vmo_enable_email_alerts',
    'vmo_receptionist_language',
    // QuickLaunch hosting (Vultr + MXroute + Cloudflare)
    'vmo_vultr_api_key',
    'vmo_mxroute_server',
    'vmo_mxroute_username',
    'vmo_mxroute_api_key',
    'vmo_cloudflare_api_token',
    'vmo_namecheap_api_user',
    'vmo_namecheap_username',
    'vmo_namecheap_api_key',
    'vmo_namecheap_client_ip',
    'vmo_namecheap_use_sandbox',
    'vmo_namecheap_default_years',
    'vmo_namecheap_enable_whoisguard',
    'vmo_namecheap_contact_first_name',
    'vmo_namecheap_contact_last_name',
    'vmo_namecheap_contact_org',
    'vmo_namecheap_contact_job_title',
    'vmo_namecheap_contact_address1',
    'vmo_namecheap_contact_address2',
    'vmo_namecheap_contact_city',
    'vmo_namecheap_contact_state',
    'vmo_namecheap_contact_postal_code',
    'vmo_namecheap_contact_country',
    'vmo_namecheap_contact_phone',
    'vmo_namecheap_contact_email',
    'vmo_stripe_webhook_secret'
    ] as $key) {
        register_setting('vmo_settings_group', $key);
    }
});

// === Frontend: Enqueue buy button handler when payments enabled ===
add_action('wp_enqueue_scripts', function() {
    if (!defined('VMO_PAYMENTS_ENABLED') || !VMO_PAYMENTS_ENABLED) return;
    $file = VMO_AI_BUILDER_DIR . 'assets/vmo-payments.js';
    if (file_exists($file)) {
        wp_enqueue_script('vmo-payments', VMO_AI_BUILDER_URL . 'assets/vmo-payments.js', ['jquery'], filemtime($file), true);
        wp_localize_script('vmo-payments', 'vmo_ajax', [
            'payments_enabled' => (bool) VMO_PAYMENTS_ENABLED,
            'pluginUrl' => plugin_dir_url(__FILE__),
            'root' => esc_url_raw(rest_url())
        ]);
    }
});

// Shortcode: [vmo_buy product_id="123" label="Buy Now"]
// Legacy still supported: [vmo_buy amount="1999" user="123" name="Product" desc="Description" currency="usd" label="Buy Now"]
add_shortcode('vmo_buy', function($atts) {
    $a = shortcode_atts([
        'product_id' => '',
        'amount' => '',
        'user' => '',
        'name' => 'Product',
        'desc' => '',
        'currency' => 'usd',
        'label' => 'Buy Now'
    ], $atts, 'vmo_buy');
    // Preferred: product_id
    if (!empty($a['product_id'])) {
        $attrs = sprintf('data-vmo-buy data-product-id="%d"', intval($a['product_id']));
        return '<button class="button vmo-buy-btn" '.$attrs.'>'.esc_html($a['label']).'</button>';
    }
    // Legacy fallback
    if (empty($a['amount']) || empty($a['user'])) return '';
    $attrs = sprintf('data-vmo-buy data-merchant="%d" data-amount="%d" data-name="%s" data-desc="%s" data-currency="%s"',
        intval($a['user']), intval($a['amount']), esc_attr($a['name']), esc_attr($a['desc']), esc_attr($a['currency'])
    );
    return '<button class="button vmo-buy-btn" '.$attrs.'>'.esc_html($a['label']).'</button>';
});

// AJAX: phone status for dashboard
add_action('wp_ajax_vmo_get_phone_status', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }

    // Ensure Twilio Manager is available
    if (!class_exists('VMO_Twilio_Manager')) {
        $path = plugin_dir_path(__FILE__) . 'includes/class-vmo-twilio-manager.php';
        if (file_exists($path)) require_once $path;
    }

    $user_id = get_current_user_id();
    $phone_number = '';
    if (class_exists('VMO_Twilio_Manager')) {
        try {
            $manager = VMO_Twilio_Manager::get_instance();
            $phone_number = (string) ($manager->get_user_phone($user_id) ?: '');

            // Auto-sync webhook URLs (throttled to once per 12 hours).
            // Fixes inbound "application error" caused by stale webhook URLs
            // after code updates, option changes, or domain migrations.
            $manager->sync_phone_webhooks($user_id);
        } catch (Throwable $e) {
            if (defined('WP_DEBUG') && WP_DEBUG) error_log('[VMO Phone Status] Error: ' . $e->getMessage());
            $phone_number = (string) (get_user_meta($user_id, 'vmo_phone_number', true) ?: '');
        }
    } else {
        $phone_number = (string) (get_user_meta($user_id, 'vmo_phone_number', true) ?: '');
    }

    wp_send_json_success(array(
        'phone_number' => $phone_number,
        'ai_greeting' => (string) (get_user_meta($user_id, 'vmo_ai_greeting', true) ?: ''),
        'external_phone' => (string) (get_user_meta($user_id, 'vmo_external_phone', true) ?: ''),
        'call_recording_enabled' => (int) get_user_meta($user_id, 'call_recording_enabled', true),
        'ring_url' => (string) (get_user_meta($user_id, 'vmo_ring_url', true) ?: ''),
        'ring_attachment_id' => (int) get_user_meta($user_id, 'vmo_ring_attachment_id', true),
        'ring_enabled' => (int) (get_user_meta($user_id, 'vmo_ring_enabled', true) === '' ? 1 : get_user_meta($user_id, 'vmo_ring_enabled', true)),
        'ring_volume' => (float) (get_user_meta($user_id, 'vmo_ring_volume', true) === '' ? 0.6 : get_user_meta($user_id, 'vmo_ring_volume', true)),
        'ring_preset' => (string) (get_user_meta($user_id, 'vmo_ring_preset', true) ?: 'classic'),
    ));
});

// AJAX: Phone system health check — returns comprehensive diagnostics.
// Called by the frontend on phone tab open to surface configuration issues.
add_action('wp_ajax_vmo_phone_system_check', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }

    $user_id = get_current_user_id();
    $checks = array();

    // 1. Twilio credentials
    $has_sid   = !empty(defined('VMO_TWILIO_ACCOUNT_SID') ? VMO_TWILIO_ACCOUNT_SID : get_option('vmo_twilio_account_sid'));
    $has_token = !empty(defined('VMO_TWILIO_AUTH_TOKEN') ? VMO_TWILIO_AUTH_TOKEN : get_option('vmo_twilio_auth_token'));
    $checks['twilio_credentials'] = ($has_sid && $has_token) ? 'ok' : 'missing';

    // 2. Twilio Manager class
    $checks['twilio_manager'] = class_exists('VMO_Twilio_Manager') ? 'ok' : 'missing';

    // 3. Phone number in database
    global $wpdb;
    $table = $wpdb->prefix . 'vmo_phone_numbers';
    $table_exists = vmo_table_exists($table);
    $checks['phone_table'] = $table_exists ? 'ok' : 'missing';

    $phone_number = '';
    $twilio_sid = '';
    if ($table_exists) {
        $phone_col = 'phone_number';
        if (class_exists('VMO_Twilio_Manager')) {
            try {
                $mgr = VMO_Twilio_Manager::get_instance();
                $phone_number = (string) ($mgr->get_user_phone($user_id) ?: '');
            } catch (Throwable $e) {}
        }
        $row = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$table} WHERE user_id = %d AND status IN ('active','pending_port') ORDER BY created_at DESC LIMIT 1",
            $user_id
        ));
        if ($row) {
            $twilio_sid = isset($row->twilio_sid) ? (string) $row->twilio_sid : '';
            if (empty($phone_number)) {
                // Try common column names
                foreach (array('phone_number', 'phone', 'number', 'twilio_number') as $col) {
                    if (isset($row->{$col}) && !empty($row->{$col})) {
                        $phone_number = (string) $row->{$col};
                        break;
                    }
                }
            }
        }
    }
    if (empty($phone_number)) {
        $phone_number = (string) (get_user_meta($user_id, 'vmo_phone_number', true) ?: '');
    }
    $checks['phone_number'] = !empty($phone_number) ? $phone_number : 'none';
    $checks['twilio_sid'] = !empty($twilio_sid) ? substr($twilio_sid, 0, 8) . '...' : 'none';

    // 4. Webhook key
    $webhook_key = (string) get_option('vmo_phone_webhook_key', '');
    $checks['webhook_key'] = !empty($webhook_key) ? 'set (' . strlen($webhook_key) . ' chars)' : 'missing';

    // 5. TwiML App
    $twiml_app = (string) get_option('vmo_twilio_twiml_app_sid', '');
    $checks['twiml_app'] = !empty($twiml_app) ? 'ok' : 'missing';

    // 6. API Key
    $api_key = (string) get_option('vmo_twilio_api_key_sid', '');
    $checks['api_key'] = !empty($api_key) ? 'ok' : 'missing';

    // 7. OpenAI key (for TTS/AI) — check both option names
    $oai_key = get_option('vmo_openai_api_key', '');
    if (empty($oai_key)) {
        $oai_key = get_option('vmo_manager_openai_api_key', '');
    }
    $checks['openai_key'] = !empty($oai_key) ? 'ok' : 'missing';

    // 8. Gateway URL test
    if (class_exists('VMO_Twilio_Manager')) {
        try {
            $mgr = VMO_Twilio_Manager::get_instance();
            $voice_url = $mgr->phone_gateway_url('voice');
            $checks['voice_webhook_url'] = $voice_url;
        } catch (Throwable $e) {
            $checks['voice_webhook_url'] = 'error: ' . $e->getMessage();
        }
    }

    // 9. Admin guidance — tell the frontend EXACTLY what to do
    $actions_needed = array();
    if (!$has_sid || !$has_token) {
        $actions_needed[] = 'Phone provisioning is managed by the VMO master service for client installs.';
    }
    if (empty($oai_key)) {
        $actions_needed[] = 'AI and billing credentials are managed by the VMO master service for client installs.';
    }
    if (!empty($actions_needed)) {
        $checks['actions_needed'] = $actions_needed;
    }

    // 10. Settings page URLs for convenience
    $checks['settings_urls'] = array(
        'dashboard' => admin_url('admin.php?page=vmo-ai-dashboard'),
        'billing' => admin_url('admin.php?page=vmo-my-plan'),
    );

    wp_send_json_success(array('checks' => $checks));
});

// AJAX: outbound call from dashboard dialer
add_action('wp_ajax_vmo_outbound_call', function() {
    // Start a scoped output buffer to capture any stray PHP notices/warnings
    // from the Twilio API call or DB operations. We intentionally do NOT kill
    // existing buffers (the sd_ajax gateway proxy relies on its own OB wrapper).
    ob_start();

    if (!current_user_can('read')) {
        ob_end_clean();
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    // Accept either admin or builder nonces (compat with dashboard helpers)
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        ob_end_clean();
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }
    $to = sanitize_text_field($_POST['to'] ?? '');
    if (!$to) {
        ob_end_clean();
        wp_send_json_error(array('message' => 'Missing destination'));
    }

    // Auto-load class if missing
    if (!class_exists('VMO_Twilio_Enhanced_Client')) {
        $path = plugin_dir_path(__FILE__) . 'includes/class-vmo-twilio-enhanced-client.php';
        if (file_exists($path)) require_once $path;
    }

    if (!class_exists('VMO_Twilio_Enhanced_Client')) {
        ob_end_clean();
        wp_send_json_error(array('message' => 'Twilio unavailable'));
    }
    try {
        $client = new VMO_Twilio_Enhanced_Client();
        $result = $client->make_outbound_call($to);
        // Discard any stray output captured during the API/DB calls
        ob_end_clean();
        if (is_wp_error($result)) wp_send_json_error(array('message' => $result->get_error_message()));
        wp_send_json_success($result);
    } catch (Throwable $e) {
        ob_end_clean();
        if (defined('WP_DEBUG') && WP_DEBUG) error_log('[VMO Outbound Call] Exception: ' . $e->getMessage());
        wp_send_json_error(array('message' => 'Call error: ' . $e->getMessage()));
    }
});

// AJAX: quick contacts for dialer picker (name + phone)
add_action('wp_ajax_vmo_get_quick_contacts', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }

    global $wpdb;
    $user_id = get_current_user_id();
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    $limit = isset($_POST['limit']) ? max(1, min(200, intval($_POST['limit']))) : 100;
    $search = isset($_POST['search']) ? sanitize_text_field(wp_unslash($_POST['search'])) : '';

    // Ensure table exists
    $exists = vmo_table_exists($contacts_table);
    if (!$exists) {
        wp_send_json_success(array('contacts' => array()));
    }

    if (!empty($search)) {
        $pattern = '%' . $wpdb->esc_like($search) . '%';
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$contacts_table}
             WHERE user_id = %d AND phone IS NOT NULL AND phone <> ''
             AND (COALESCE(full_name, CONCAT(COALESCE(first_name,''),' ',COALESCE(last_name,'')), '') LIKE %s OR email LIKE %s OR phone LIKE %s OR company LIKE %s)
             ORDER BY COALESCE(full_name, CONCAT(COALESCE(first_name,''),' ',COALESCE(last_name,'')), '') ASC LIMIT %d",
            $user_id, $pattern, $pattern, $pattern, $pattern, $limit
        ), ARRAY_A);
    } else {
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$contacts_table}
             WHERE user_id = %d AND phone IS NOT NULL AND phone <> ''
             ORDER BY updated_at DESC, created_at DESC LIMIT %d",
            $user_id, $limit
        ), ARRAY_A);
    }

    $contacts = array();
    foreach (($rows ?: array()) as $r) {
        $phone = preg_replace('/[^+0-9]/', '', $r['phone'] ?? '');
        if (empty($phone)) continue;
        $r_name = $r['full_name'] ?? '';
        if (empty($r_name)) { $r_name = trim(($r['first_name'] ?? '') . ' ' . ($r['last_name'] ?? '')); }
        $contacts[] = array(
            'id' => intval($r['id']),
            'name' => (string) $r_name,
            'phone' => $phone,
            'company' => (string) ($r['company'] ?: ''),
        );
    }

    wp_send_json_success(array('contacts' => $contacts));
});

// AJAX: Get live calls (active/ringing) for dashboard monitoring
add_action('wp_ajax_vmo_get_live_calls', function() {
    try {
        // Clean any buffered output (wpdb errors etc.) before sending JSON
        if (ob_get_level()) ob_clean();

        if (!current_user_can('read')) {
            wp_send_json_error(array('message' => 'Forbidden'), 200);
        }
        if (!vmo_verify_admin_security()) {
            wp_send_json_error(array('message' => 'Security check failed'), 200);
        }

        // Use Twilio Manager to get active calls
        if (class_exists('VMO_Twilio_Manager')) {
            $manager = VMO_Twilio_Manager::get_instance();
            ob_start(); // Capture any stray wpdb output
            $result = $manager->get_live_calls(get_current_user_id());
            ob_end_clean(); // Discard captured output
            if (is_wp_error($result)) {
                wp_send_json_error(array('message' => $result->get_error_message()), 200);
            }
            wp_send_json_success($result);
        }

        wp_send_json_error(array('message' => 'Twilio manager unavailable'), 200);
    } catch (Throwable $t) {
        // Never let background polling produce a 500 flood.
        if (ob_get_level()) ob_end_clean();
        wp_send_json_error(array('message' => 'Live calls unavailable'), 200);
    }
});

// AJAX: Call action (join, listen, whisper, barge)
add_action('wp_ajax_vmo_call_action', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }
    
    $action_type = sanitize_text_field($_POST['action_type'] ?? '');
    $call_sid = sanitize_text_field($_POST['call_sid'] ?? '');
    $conference_sid = sanitize_text_field($_POST['conference_sid'] ?? '');
    
    if (!in_array($action_type, array('join', 'listen', 'whisper', 'barge', 'resume_ai'))) {
        wp_send_json_error(array('message' => 'Invalid action type'));
    }
    
    if (empty($call_sid)) {
        wp_send_json_error(array('message' => 'Missing call SID'));
    }
    
    if (class_exists('VMO_Twilio_Manager')) {
        $manager = VMO_Twilio_Manager::get_instance();
        $via = sanitize_text_field($_POST['via'] ?? 'auto');
        $result = $manager->call_action($action_type, $call_sid, $conference_sid, get_current_user_id(), $via);
        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
        }
        wp_send_json_success($result);
    }
    
    wp_send_json_error(array('message' => 'Twilio manager unavailable'));
});

// AJAX: Generate Twilio browser-calling token
add_action('wp_ajax_vmo_twilio_browser_token', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }
    if (class_exists('VMO_Twilio_Manager')) {
        $manager = VMO_Twilio_Manager::get_instance();
        $token = $manager->generate_browser_token(get_current_user_id());
        if (!empty($token)) {
            wp_send_json_success(array('token' => $token, 'identity' => 'vmo_user_' . get_current_user_id()));
        }
        wp_send_json_error(array('message' => 'Could not generate browser token'));
    }
    wp_send_json_error(array('message' => 'Twilio manager unavailable'));
});

// AJAX: End call
add_action('wp_ajax_vmo_end_call', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }
    
    $call_sid = sanitize_text_field($_POST['call_sid'] ?? '');
    if (empty($call_sid)) {
        wp_send_json_error(array('message' => 'Missing call SID'));
    }
    
    if (class_exists('VMO_Twilio_Manager')) {
        $manager = VMO_Twilio_Manager::get_instance();
        // Pass extra info so end_call can INSERT a record if the webhook never created one
        $extra = array(
            'user_id'       => get_current_user_id(),
            'direction'     => sanitize_text_field($_POST['direction'] ?? 'outbound'),
            'caller_number' => sanitize_text_field($_POST['caller_number'] ?? ''),
        );
        $result = $manager->end_call($call_sid, $extra);
        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
        }
        wp_send_json_success($result);
    }
    
    wp_send_json_error(array('message' => 'Twilio manager unavailable'));
});

// AJAX: Get live AI transcript for a call
add_action('wp_ajax_vmo_get_call_transcript', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }
    
    $call_sid = sanitize_text_field($_POST['call_sid'] ?? '');
    if (empty($call_sid)) {
        wp_send_json_error(array('message' => 'Missing call SID'));
    }
    
    if (class_exists('VMO_Twilio_Manager')) {
        $manager = VMO_Twilio_Manager::get_instance();
        $result = $manager->get_call_transcript($call_sid, get_current_user_id());
        wp_send_json_success($result);
    }
    
    wp_send_json_error(array('message' => 'Twilio manager unavailable'));
});

// AJAX: Save phone preferences (ring settings, greeting, external phone)
add_action('wp_ajax_vmo_save_phone_prefs', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }
    
    $user_id = get_current_user_id();
    $greeting = sanitize_text_field($_POST['greeting'] ?? '');
    $ring_url = esc_url_raw($_POST['ring_url'] ?? '');
    $ring_attachment_id = intval($_POST['ring_attachment_id'] ?? 0);
    $external_phone = sanitize_text_field($_POST['external_phone'] ?? '');

    $call_recording_enabled = isset($_POST['call_recording_enabled']) ? intval($_POST['call_recording_enabled']) : 0;
    $email_incoming_alerts = isset($_POST['email_incoming_alerts']) ? intval($_POST['email_incoming_alerts']) : 0;
    $email_call_summaries = isset($_POST['email_call_summaries']) ? intval($_POST['email_call_summaries']) : 1;

    $ring_enabled = isset($_POST['ring_enabled']) ? intval($_POST['ring_enabled']) : 1;
    $ring_volume = isset($_POST['ring_volume']) ? floatval($_POST['ring_volume']) : 0.6;
    $ring_preset = sanitize_key($_POST['ring_preset'] ?? 'classic');
    $extensions_raw = wp_unslash($_POST['extensions'] ?? '');
    $extensions = array();
    if (!empty($extensions_raw)) {
        $decoded = json_decode($extensions_raw, true);
        if (is_array($decoded)) {
            foreach ($decoded as $k => $v) {
                $kk = preg_replace('/[^1-7]/', '', (string) $k);
                if ($kk === '') continue;
                $vv = sanitize_text_field((string) $v);
                if ($vv !== '') {
                    $extensions[$kk] = $vv;
                }
            }
        }
    }

    // Parse extension labels
    $ext_labels_raw = wp_unslash($_POST['extension_labels'] ?? '');
    $extension_labels = array();
    if (!empty($ext_labels_raw)) {
        $decoded_labels = json_decode($ext_labels_raw, true);
        if (is_array($decoded_labels)) {
            foreach ($decoded_labels as $k => $v) {
                $kk = preg_replace('/[^1-7]/', '', (string) $k);
                if ($kk === '') continue;
                $vv = sanitize_text_field((string) $v);
                if ($vv !== '') {
                    $extension_labels[$kk] = $vv;
                }
            }
        }
    }
    
    // Save to user meta
    update_user_meta($user_id, 'vmo_ai_greeting', $greeting);
    update_user_meta($user_id, 'vmo_ring_url', $ring_url);
    update_user_meta($user_id, 'vmo_ring_attachment_id', $ring_attachment_id);
    update_user_meta($user_id, 'vmo_external_phone', $external_phone);

    update_user_meta($user_id, 'call_recording_enabled', $call_recording_enabled ? 1 : 0);
    update_user_meta($user_id, 'vmo_email_incoming_alerts', $email_incoming_alerts ? 1 : 0);
    update_user_meta($user_id, 'vmo_email_call_summaries', $email_call_summaries ? 1 : 0);

    update_user_meta($user_id, 'vmo_ring_enabled', $ring_enabled ? 1 : 0);
    update_user_meta($user_id, 'vmo_ring_volume', max(0, min(1, $ring_volume)));
    update_user_meta($user_id, 'vmo_ring_preset', in_array($ring_preset, array('classic', 'pulse', 'chime', 'soft'), true) ? $ring_preset : 'classic');
    update_user_meta($user_id, 'vmo_phone_extensions', $extensions);
    update_user_meta($user_id, 'vmo_phone_extension_labels', $extension_labels);
    
    wp_send_json_success(array('message' => 'Preferences saved successfully'));
});

// AJAX: Get phone preferences
add_action('wp_ajax_vmo_get_phone_prefs', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }
    
    $user_id = get_current_user_id();
    
    $enabled_meta = get_user_meta($user_id, 'vmo_ring_enabled', true);
    $volume_meta = get_user_meta($user_id, 'vmo_ring_volume', true);
    $preset_meta = get_user_meta($user_id, 'vmo_ring_preset', true);
    $ext_meta = get_user_meta($user_id, 'vmo_phone_extensions', true);
    $ext_labels_meta = get_user_meta($user_id, 'vmo_phone_extension_labels', true);

    wp_send_json_success(array(
        'greeting' => get_user_meta($user_id, 'vmo_ai_greeting', true),
        'ring_url' => get_user_meta($user_id, 'vmo_ring_url', true),
        'ring_attachment_id' => get_user_meta($user_id, 'vmo_ring_attachment_id', true),
        'external_phone' => get_user_meta($user_id, 'vmo_external_phone', true),
        'call_recording_enabled' => (int) get_user_meta($user_id, 'call_recording_enabled', true),
        'email_incoming_alerts' => (($v = get_user_meta($user_id, 'vmo_email_incoming_alerts', true)) !== '' ? (int) $v : 0),
        'email_call_summaries' => (($v = get_user_meta($user_id, 'vmo_email_call_summaries', true)) !== '' ? (int) $v : 1),
        'ring_enabled' => ($enabled_meta === '' ? 1 : (int) $enabled_meta),
        'ring_volume' => ($volume_meta === '' ? 0.6 : (float) $volume_meta),
        'ring_preset' => ($preset_meta ? $preset_meta : 'classic'),
        'extensions' => (is_array($ext_meta) ? $ext_meta : array()),
        'extension_labels' => (is_array($ext_labels_meta) ? $ext_labels_meta : array()),
    ));
});

if (!function_exists('vmo_sms_normalize_phone')) {
    function vmo_sms_normalize_phone($phone) {
        $digits = preg_replace('/\D+/', '', (string) $phone);
        if ($digits === '') {
            return '';
        }
        if (strlen($digits) === 11 && strpos($digits, '1') === 0) {
            return substr($digits, 1);
        }
        return $digits;
    }
}

if (!function_exists('vmo_sms_get_business_number')) {
    function vmo_sms_get_business_number($user_id) {
        if (class_exists('VMO_Twilio_Manager')) {
            try {
                $phone = VMO_Twilio_Manager::get_instance()->get_user_phone($user_id);
                if (is_string($phone)) {
                    return $phone;
                }
                if (is_array($phone)) {
                    return (string) ($phone['number'] ?? $phone['phone_number'] ?? '');
                }
            } catch (Throwable $e) {
            }
        }

        $meta_phone = get_user_meta($user_id, 'vmo_phone_number', true);
        if (is_array($meta_phone)) {
            return (string) ($meta_phone['number'] ?? $meta_phone['phone_number'] ?? '');
        }

        return (string) $meta_phone;
    }
}

if (!function_exists('vmo_sms_collect_conversations')) {
    function vmo_sms_collect_conversations(array $rows) {
        $conversations = array();

        foreach ($rows as $row) {
            $direction = (string) ($row['direction'] ?? '');
            if ($direction !== 'inbound' && $direction !== 'outbound') {
                continue;
            }

            $contact_number = ($direction === 'inbound')
                ? (string) ($row['from_number'] ?? '')
                : (string) ($row['to_number'] ?? '');
            $contact_key = vmo_sms_normalize_phone($contact_number);
            if ($contact_key === '') {
                continue;
            }

            if (!isset($conversations[$contact_key])) {
                $preview = trim((string) preg_replace('/\s+/', ' ', (string) ($row['body'] ?? '')));
                if (strlen($preview) > 120) {
                    $preview = substr($preview, 0, 117) . '...';
                }

                $conversations[$contact_key] = array(
                    'contact_number' => $contact_number,
                    'contact_key' => $contact_key,
                    'last_body' => (string) ($row['body'] ?? ''),
                    'last_preview' => $preview,
                    'last_direction' => $direction,
                    'last_status' => (string) ($row['status'] ?? ''),
                    'last_at' => (string) ($row['created_at'] ?? ''),
                    'message_count' => 0,
                    'inbound_count' => 0,
                    'outbound_count' => 0,
                );
            }

            $conversations[$contact_key]['message_count']++;
            if ($direction === 'inbound') {
                $conversations[$contact_key]['inbound_count']++;
            } else {
                $conversations[$contact_key]['outbound_count']++;
            }
        }

        return array_values($conversations);
    }
}

if (!function_exists('vmo_get_sms_activation_snapshot')) {
    function vmo_get_sms_activation_snapshot($user_id, $refresh = true) {
        $user_id = (int) $user_id;
        $brand_sid = (string) get_user_meta($user_id, 'vmo_a2p_brand_sid', true);
        $campaign_sid = (string) get_user_meta($user_id, 'vmo_a2p_campaign_sid', true);
        $messaging_service_sid = (string) get_user_meta($user_id, 'vmo_a2p_messaging_service_sid', true);
        $sms_status = (string) get_user_meta($user_id, 'vmo_sms_activated', true);
        $has_business_number = (function_exists('vmo_sms_get_business_number') && vmo_sms_get_business_number($user_id) !== '');

        $snapshot = array(
            'brand_status' => (string) (get_user_meta($user_id, 'vmo_a2p_brand_status', true) ?: 'not_started'),
            'campaign_status' => (string) (get_user_meta($user_id, 'vmo_a2p_campaign_status', true) ?: 'not_started'),
            'sms_status' => $sms_status ?: 'inactive',
            'sms_active' => ($sms_status === '1' || $sms_status === 'active'),
            'sms_activated_at' => (string) get_user_meta($user_id, 'vmo_sms_activated_date', true),
            'tracked_in_plugin' => (!empty($brand_sid) || !empty($campaign_sid) || !empty($messaging_service_sid)),
            'checked_at' => current_time('mysql'),
        );

        if ($refresh && class_exists('VMO_Twilio_A2P_Manager') && ($snapshot['tracked_in_plugin'] || $has_business_number)) {
            try {
                $manager = VMO_Twilio_A2P_Manager::get_instance();
                if ($manager && method_exists($manager, 'check_status')) {
                    $live = $manager->check_status($user_id);
                    if (is_array($live)) {
                        $snapshot = array_merge($snapshot, $live);
                    }
                }
            } catch (Throwable $e) {
                if (function_exists('vmo_log')) {
                    vmo_log('SMS activation status refresh failed for user ' . $user_id . ': ' . $e->getMessage());
                }
            }
        }

        $snapshot['sms_active'] = !empty($snapshot['sms_active']);
        $snapshot['sms_status'] = ($snapshot['sms_active'] ? 'active' : ((string) ($snapshot['sms_status'] ?? 'inactive')));
        $snapshot['brand_status'] = (string) ($snapshot['brand_status'] ?? 'not_started');
        $snapshot['campaign_status'] = (string) ($snapshot['campaign_status'] ?? 'not_started');
        $snapshot['sms_activated_at'] = (string) ($snapshot['sms_activated_at'] ?? '');
        $snapshot['tracked_in_plugin'] = !empty($snapshot['tracked_in_plugin']);
        $snapshot['checked_at'] = (string) ($snapshot['checked_at'] ?? current_time('mysql'));

        return $snapshot;
    }
}

// AJAX: SMS workspace summary and conversation list
add_action('wp_ajax_vmo_get_sms_workspace', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }

    global $wpdb;
    $user_id = get_current_user_id();
    $sms_table = $wpdb->prefix . 'vmo_sms_logs';
    $table_exists = function_exists('vmo_table_exists') ? vmo_table_exists($sms_table) : ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $sms_table)) === $sms_table);
    if (!$table_exists && function_exists('vmo_create_twilio_tables')) {
        vmo_create_twilio_tables();
        $table_exists = function_exists('vmo_table_exists') ? vmo_table_exists($sms_table) : ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $sms_table)) === $sms_table);
    }

    $rows = array();
    if ($table_exists) {
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT id, twilio_message_sid, from_number, to_number, direction, body, status, created_at
             FROM {$sms_table}
             WHERE user_id = %d
             ORDER BY created_at DESC, id DESC
             LIMIT 250",
            $user_id
        ), ARRAY_A);
    }

    $conversations = vmo_sms_collect_conversations($rows ?: array());
    $activation = function_exists('vmo_get_sms_activation_snapshot')
        ? vmo_get_sms_activation_snapshot($user_id, true)
        : array(
            'sms_status' => (string) get_user_meta($user_id, 'vmo_sms_activated', true),
            'sms_active' => ((string) get_user_meta($user_id, 'vmo_sms_activated', true) === 'active'),
            'brand_status' => (string) get_user_meta($user_id, 'vmo_a2p_brand_status', true),
            'campaign_status' => (string) get_user_meta($user_id, 'vmo_a2p_campaign_status', true),
            'sms_activated_at' => (string) get_user_meta($user_id, 'vmo_sms_activated_date', true),
            'tracked_in_plugin' => false,
            'checked_at' => current_time('mysql'),
        );
    $sms_status = (string) ($activation['sms_status'] ?? 'inactive');
    $token_balance = function_exists('vmo_get_token_balance') ? (int) vmo_get_token_balance($user_id) : (int) get_user_meta($user_id, 'vmo_token_balance', true);
    $brand_cost = class_exists('VMO_Twilio_Token_Economics') ? VMO_Twilio_Token_Economics::A2P_BRAND() : 15000;
    $campaign_cost = class_exists('VMO_Twilio_Token_Economics') ? VMO_Twilio_Token_Economics::A2P_CAMPAIGN_MONTHLY() : 56250;
    $per_reply_cost = class_exists('VMO_Twilio_Token_Economics')
        ? (VMO_Twilio_Token_Economics::SMS_OUTBOUND() + VMO_Twilio_Token_Economics::SMS_A2P_SURCHARGE())
        : 50;
    $recommended_campaign_balance = $campaign_cost + (class_exists('VMO_Twilio_Token_Economics') ? VMO_Twilio_Token_Economics::PHONE_MONTHLY_RENTAL() : 3750);

    $inbound_total = 0;
    $outbound_total = 0;
    foreach (($rows ?: array()) as $row) {
        $dir = (string) ($row['direction'] ?? '');
        if ($dir === 'inbound') {
            $inbound_total++;
        } elseif ($dir === 'outbound') {
            $outbound_total++;
        }
    }

    wp_send_json_success(array(
        'business_phone' => vmo_sms_get_business_number($user_id),
        'external_phone' => (string) get_user_meta($user_id, 'vmo_external_phone', true),
        'sms_status' => $sms_status,
        'sms_active' => !empty($activation['sms_active']),
        'brand_status' => (string) ($activation['brand_status'] ?? 'not_started'),
        'campaign_status' => (string) ($activation['campaign_status'] ?? 'not_started'),
        'sms_activated_at' => (string) ($activation['sms_activated_at'] ?? ''),
        'sms_checked_at' => (string) ($activation['checked_at'] ?? ''),
        'tracked_in_plugin' => !empty($activation['tracked_in_plugin']),
        'token_balance' => $token_balance,
        'latest_contact' => (string) get_user_meta($user_id, 'vmo_sms_last_contact', true),
        'counts' => array(
            'conversations' => count($conversations),
            'messages' => $inbound_total + $outbound_total,
            'inbound' => $inbound_total,
            'outbound' => $outbound_total,
        ),
        'costs' => array(
            'brand_tokens' => $brand_cost,
            'campaign_tokens' => $campaign_cost,
            'per_reply_tokens' => $per_reply_cost,
            'recommended_campaign_balance' => $recommended_campaign_balance,
        ),
        'conversations' => $conversations,
    ));
});

// AJAX: Load one SMS conversation thread
add_action('wp_ajax_vmo_get_sms_thread', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }

    global $wpdb;
    $user_id = get_current_user_id();
    $contact = sanitize_text_field(wp_unslash($_POST['contact'] ?? ''));
    $contact_key = vmo_sms_normalize_phone($contact);
    $sms_table = $wpdb->prefix . 'vmo_sms_logs';
    $table_exists = function_exists('vmo_table_exists') ? vmo_table_exists($sms_table) : ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $sms_table)) === $sms_table);

    if ($contact_key === '' || !$table_exists) {
        wp_send_json_success(array('contact' => $contact, 'messages' => array()));
    }

    $rows = $wpdb->get_results($wpdb->prepare(
        "SELECT id, twilio_message_sid, from_number, to_number, direction, body, status, created_at
         FROM {$sms_table}
         WHERE user_id = %d AND direction IN ('inbound', 'outbound')
         ORDER BY created_at DESC, id DESC
         LIMIT 600",
        $user_id
    ), ARRAY_A);

    $messages = array();
    foreach (($rows ?: array()) as $row) {
        $direction = (string) ($row['direction'] ?? '');
        $message_contact = ($direction === 'inbound')
            ? (string) ($row['from_number'] ?? '')
            : (string) ($row['to_number'] ?? '');
        if (vmo_sms_normalize_phone($message_contact) !== $contact_key) {
            continue;
        }

        $messages[] = array(
            'id' => (int) ($row['id'] ?? 0),
            'message_sid' => (string) ($row['twilio_message_sid'] ?? ''),
            'from_number' => (string) ($row['from_number'] ?? ''),
            'to_number' => (string) ($row['to_number'] ?? ''),
            'direction' => $direction,
            'body' => (string) ($row['body'] ?? ''),
            'status' => (string) ($row['status'] ?? ''),
            'created_at' => (string) ($row['created_at'] ?? ''),
        );
    }

    if (count($messages) > 300) {
        $messages = array_slice($messages, 0, 300);
    }
    $messages = array_reverse($messages);

    wp_send_json_success(array(
        'contact' => $contact,
        'contact_key' => $contact_key,
        'messages' => $messages,
    ));
});

// AJAX: Send an SMS from the dashboard workspace
add_action('wp_ajax_vmo_send_sms_message', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }

    $user_id = get_current_user_id();
    $to = sanitize_text_field(wp_unslash($_POST['to'] ?? ''));
    $message = trim(sanitize_textarea_field(wp_unslash($_POST['message'] ?? '')));
    $activation = function_exists('vmo_get_sms_activation_snapshot')
        ? vmo_get_sms_activation_snapshot($user_id, true)
        : array(
            'sms_active' => (((string) get_user_meta($user_id, 'vmo_sms_activated', true) === '1') || ((string) get_user_meta($user_id, 'vmo_sms_activated', true) === 'active')),
            'brand_status' => (string) get_user_meta($user_id, 'vmo_a2p_brand_status', true),
            'campaign_status' => (string) get_user_meta($user_id, 'vmo_a2p_campaign_status', true),
            'tracked_in_plugin' => false,
        );

    if ($to === '' || $message === '') {
        wp_send_json_error(array('message' => 'Recipient and message are required'), 400);
    }

    if (empty($activation['sms_active'])) {
        $brand_status = strtoupper((string) ($activation['brand_status'] ?? 'not_started'));
        $campaign_status = strtoupper((string) ($activation['campaign_status'] ?? 'not_started'));
        $tracked_in_plugin = !empty($activation['tracked_in_plugin']);
        $message = $tracked_in_plugin
            ? 'SMS is not active on this business number yet. Current status — Brand: ' . str_replace('_', ' ', $brand_status) . ', Campaign: ' . str_replace('_', ' ', $campaign_status) . '. Open SMS Activation to refresh status inside the plugin.'
            : 'SMS is not active on this business number yet. VMO could not confirm a completed carrier registration on this number during the latest live check.';
        wp_send_json_error(array('message' => $message), 409);
    }

    if (!class_exists('VMO_Twilio_Enhanced_Client')) {
        $path = plugin_dir_path(__FILE__) . 'includes/class-vmo-twilio-enhanced-client.php';
        if (file_exists($path)) {
            require_once $path;
        }
    }
    if (!class_exists('VMO_Twilio_Manager')) {
        $path = plugin_dir_path(__FILE__) . 'includes/class-vmo-twilio-manager.php';
        if (file_exists($path)) {
            require_once $path;
        }
    }

    if (!class_exists('VMO_Twilio_Enhanced_Client')) {
        wp_send_json_error(array('message' => 'Twilio SMS client unavailable'), 500);
    }

    $client = new VMO_Twilio_Enhanced_Client();
    $result = $client->send_sms($to, $message, array('track_status' => true));
    if (is_wp_error($result)) {
        wp_send_json_error(array('message' => $result->get_error_message()), 400);
    }

    if (class_exists('VMO_Twilio_Manager')) {
        try {
            $manager = VMO_Twilio_Manager::get_instance();
            if (method_exists($manager, 'log_sms_message')) {
                $manager->log_sms_message(
                    $user_id,
                    sanitize_text_field((string) ($result['sid'] ?? '')),
                    vmo_sms_get_business_number($user_id),
                    $to,
                    'outbound',
                    $message,
                    sanitize_text_field((string) ($result['status'] ?? 'queued'))
                );
            }
        } catch (Throwable $e) {
        }
    }

    wp_send_json_success(array(
        'message_sid' => sanitize_text_field((string) ($result['sid'] ?? '')),
        'status' => sanitize_text_field((string) ($result['status'] ?? 'queued')),
        'token_balance' => function_exists('vmo_get_token_balance') ? (int) vmo_get_token_balance($user_id) : null,
    ));
});

// AJAX: Generate an AI-assisted SMS draft for the current conversation
add_action('wp_ajax_vmo_generate_sms_draft', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }

    $user_id = get_current_user_id();
    $to = sanitize_text_field(wp_unslash($_POST['to'] ?? ''));
    $intent = sanitize_text_field(wp_unslash($_POST['intent'] ?? 'general reply'));
    $notes = sanitize_textarea_field(wp_unslash($_POST['notes'] ?? ''));
    $contact_key = vmo_sms_normalize_phone($to);

    if (!function_exists('vmo_openai_chat')) {
        wp_send_json_error(array('message' => 'AI request helper unavailable.'), 500);
    }

    if (function_exists('vmo_deduct_tokens_for_operation') && !vmo_deduct_tokens_for_operation($user_id, 'chat')) {
        wp_send_json_error(array(
            'message' => 'Insufficient tokens to generate an AI SMS draft.',
            'tokens_required' => function_exists('vmo_get_token_cost') ? vmo_get_token_cost('chat') : 50,
            'tokens_available' => function_exists('vmo_get_token_balance') ? vmo_get_token_balance($user_id) : 0,
        ), 402);
    }

    global $wpdb;
    $thread_lines = array();
    $sms_table = $wpdb->prefix . 'vmo_sms_logs';
    $table_exists = function_exists('vmo_table_exists') ? vmo_table_exists($sms_table) : ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $sms_table)) === $sms_table);
    if ($table_exists && $contact_key !== '') {
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT from_number, to_number, direction, body, created_at
             FROM {$sms_table}
             WHERE user_id = %d AND direction IN ('inbound', 'outbound')
             ORDER BY created_at DESC, id DESC
             LIMIT 120",
            $user_id
        ), ARRAY_A);

        $filtered = array();
        foreach (($rows ?: array()) as $row) {
            $direction = (string) ($row['direction'] ?? '');
            $message_contact = ($direction === 'inbound')
                ? (string) ($row['from_number'] ?? '')
                : (string) ($row['to_number'] ?? '');
            if (vmo_sms_normalize_phone($message_contact) === $contact_key) {
                $filtered[] = $row;
            }
        }

        $filtered = array_reverse(array_slice($filtered, 0, 6));
        $profile = function_exists('vmo_get_consultation_profile') ? vmo_get_consultation_profile($user_id) : array();
        $business_name = sanitize_text_field($profile['business_name'] ?? (get_user_meta($user_id, 'business_name', true) ?: 'The business'));
        foreach ($filtered as $row) {
            $speaker = ((string) ($row['direction'] ?? '') === 'inbound') ? 'Customer' : $business_name;
            $thread_lines[] = $speaker . ': ' . trim((string) ($row['body'] ?? ''));
        }
    }

    $profile = function_exists('vmo_get_consultation_profile') ? vmo_get_consultation_profile($user_id) : array();
    $business_name = sanitize_text_field($profile['business_name'] ?? (get_user_meta($user_id, 'business_name', true) ?: 'The business'));
    $industry = sanitize_text_field($profile['industry'] ?? (get_user_meta($user_id, 'business_type', true) ?: 'General'));
    $intent_lower = strtolower($intent);
    $opt_out_guidance = (strpos($intent_lower, 'promo') !== false || strpos($intent_lower, 'market') !== false)
        ? 'Include a short opt-out line such as "Reply STOP to opt out."'
        : 'Do not include opt-out language unless the user specifically requested a promotional SMS.';

    $messages = array(
        array(
            'role' => 'system',
            'content' => 'You write concise, professional business SMS messages. Return only the final SMS body with no quotes, headings, markdown, or JSON. Keep it human, practical, and normally under 320 characters. ' . $opt_out_guidance,
        ),
        array(
            'role' => 'user',
            'content' => "Business name: {$business_name}\nIndustry: {$industry}\nRecipient: {$to}\nIntent: {$intent}\nAdditional guidance: {$notes}\nRecent thread:\n" . (empty($thread_lines) ? 'No recent thread available.' : implode("\n", $thread_lines)) . "\n\nDraft the next SMS reply now.",
        ),
    );

    $model = defined('VMO_OPENAI_MODEL') ? VMO_OPENAI_MODEL : 'gpt-5.2-2025-12-11';
    if (function_exists('vmo_get_optimal_model')) {
        try {
            $model = vmo_get_optimal_model('content_creation', array('skip_log' => true));
        } catch (Throwable $e) {
        }
    }

    $response = vmo_openai_chat($messages, $model, array(
        'temperature' => 0.55,
        'max_tokens' => 180,
        'timeout' => 60,
    ));

    if (is_wp_error($response)) {
        wp_send_json_error(array('message' => 'AI draft request failed: ' . $response->get_error_message()), 500);
    }

    $payload = json_decode(wp_remote_retrieve_body($response), true);
    $draft = trim((string) ($payload['choices'][0]['message']['content'] ?? ''));
    $draft = preg_replace('/^```(?:text)?\s*|\s*```$/i', '', $draft);
    $draft = trim((string) $draft, "\"' \n\r\t");

    if ($draft === '') {
        wp_send_json_error(array('message' => 'AI did not return a usable SMS draft.'), 500);
    }

    wp_send_json_success(array(
        'draft' => $draft,
        'tokens_used' => function_exists('vmo_get_token_cost') ? vmo_get_token_cost('chat') : 50,
        'token_balance' => function_exists('vmo_get_token_balance') ? (int) vmo_get_token_balance($user_id) : null,
    ));
});

// AJAX: Get call history with recordings
add_action('wp_ajax_vmo_get_call_history', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }
    
    global $wpdb;
    $user_id = get_current_user_id();
    $calls_table = $wpdb->prefix . 'vmo_phone_calls';
    $pd_table = $wpdb->prefix . 'vmo_power_dialer_calls';
    $limit = isset($_POST['limit']) ? max(1, min(100, intval($_POST['limit']))) : 50;
    $offset = isset($_POST['offset']) ? max(0, intval($_POST['offset'])) : 0;
    $contact_id = isset($_POST['contact_id']) ? intval($_POST['contact_id']) : 0;
    $is_admin = current_user_can('manage_options');
    $debug = array('user_id' => $user_id, 'is_admin' => $is_admin, 'steps' => array());

    // ── Step 1: Ensure vmo_phone_calls table exists with correct schema ──
    $table_exists = vmo_table_exists($calls_table);
    if (!$table_exists) {
        $charset_collate = $wpdb->get_charset_collate();
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta("CREATE TABLE IF NOT EXISTS {$calls_table} (
            id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            user_id BIGINT UNSIGNED NOT NULL,
            call_sid VARCHAR(255) NOT NULL,
            caller_number VARCHAR(50) DEFAULT NULL,
            direction ENUM('inbound','outbound') DEFAULT 'inbound',
            status VARCHAR(50) DEFAULT 'completed',
            duration INT UNSIGNED DEFAULT 0,
            recording_url VARCHAR(2083) DEFAULT NULL,
            transcript LONGTEXT DEFAULT NULL,
            summary LONGTEXT DEFAULT NULL,
            topic VARCHAR(255) DEFAULT NULL,
            ai_handled TINYINT(1) DEFAULT 0,
            contact_id BIGINT UNSIGNED DEFAULT NULL,
            tokens_used INT UNSIGNED DEFAULT 0,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            INDEX idx_user_id (user_id),
            INDEX idx_call_sid (call_sid),
            INDEX idx_status (status),
            INDEX idx_contact (contact_id)
        ) {$charset_collate};");
        $table_exists = vmo_table_exists($calls_table);
        $debug['steps'][] = $table_exists ? 'table_created' : 'table_create_failed';
    }
    $debug['table_exists'] = $table_exists;

    if (!$table_exists) {
        wp_send_json_success(array(
            'calls' => array(), 'total' => 0, 'limit' => $limit, 'offset' => $offset,
            'debug' => array_merge($debug, array('error' => 'Table could not be created'))
        ));
    }

    // ── Step 2: Verify critical columns exist (schema self-healing) ──
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $cols = $wpdb->get_col("SHOW COLUMNS FROM {$calls_table}", 0);
    $debug['columns'] = is_array($cols) ? $cols : array();
    $required_cols = array('user_id', 'call_sid', 'caller_number', 'direction', 'status', 'duration', 'ai_handled', 'contact_id', 'tokens_used', 'created_at');
    $missing = array_diff($required_cols, is_array($cols) ? $cols : array());
    if (!empty($missing)) {
        $suppress = $wpdb->suppress_errors(true);
        $col_defs = array(
            'user_id' => 'BIGINT UNSIGNED NOT NULL DEFAULT 0',
            'call_sid' => 'VARCHAR(255) NOT NULL DEFAULT ""',
            'caller_number' => 'VARCHAR(50) DEFAULT NULL',
            'direction' => "VARCHAR(20) DEFAULT 'inbound'",
            'status' => "VARCHAR(50) DEFAULT 'completed'",
            'duration' => 'INT UNSIGNED DEFAULT 0',
            'recording_url' => 'VARCHAR(2083) DEFAULT NULL',
            'transcript' => 'LONGTEXT DEFAULT NULL',
            'summary' => 'LONGTEXT DEFAULT NULL',
            'ai_handled' => 'TINYINT(1) DEFAULT 0',
            'contact_id' => 'BIGINT UNSIGNED DEFAULT NULL',
            'created_at' => 'DATETIME DEFAULT CURRENT_TIMESTAMP',
        );
        foreach ($missing as $col) {
            if (isset($col_defs[$col])) {
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
                $wpdb->query("ALTER TABLE {$calls_table} ADD COLUMN {$col} {$col_defs[$col]}");
            }
        }
        $wpdb->suppress_errors($suppress);
        $debug['steps'][] = 'schema_repaired:' . implode(',', $missing);
    }

    // ── Step 3: Query calls — progressive fallback ──
    $total = 0;
    $calls = array();
    $source = 'none';

    // 3a: Try user's own calls (last 30 days)
    $where_tpl = "user_id = %d AND created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)";
    $where_params = array( $user_id );
    if ($contact_id > 0 && in_array('contact_id', is_array($cols) ? $cols : array(), true)) {
        $where_tpl .= " AND contact_id = %d";
        $where_params[] = $contact_id;
    }
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- {$where_tpl} contains only placeholders; table name from internal constant
    $total = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$calls_table} WHERE {$where_tpl}", ...$where_params ) );
    if ($total > 0) {
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- {$where_tpl} contains only placeholders; table name from internal constant
        $calls = $wpdb->get_results( $wpdb->prepare(
            "SELECT * FROM {$calls_table} WHERE {$where_tpl} ORDER BY created_at DESC LIMIT %d OFFSET %d",
            ...array_merge( $where_params, array( $limit, $offset ) )
        ), ARRAY_A );
        $source = 'user_calls';
    }

    // 3b: Admin fallback — all users, last 30 days
    if ($total === 0 && $is_admin) {
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        $total = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$calls_table} WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)");
        if ($total > 0) {
            $calls = $wpdb->get_results($wpdb->prepare(
                "SELECT * FROM {$calls_table} WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY) ORDER BY created_at DESC LIMIT %d OFFSET %d",
                $limit, $offset
            ), ARRAY_A);
            $source = 'admin_all_30d';
            // Fix ownership for orphaned calls
            $wpdb->query($wpdb->prepare(
                "UPDATE {$calls_table} SET user_id = %d WHERE user_id NOT IN (SELECT ID FROM {$wpdb->users})",
                $user_id
            ));
        }
    }

    // 3c: Admin fallback — all calls ever (maybe created_at is off due to timezone)
    if ($total === 0 && $is_admin) {
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        $total = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$calls_table}");
        if ($total > 0) {
            $calls = $wpdb->get_results($wpdb->prepare(
                "SELECT * FROM {$calls_table} ORDER BY id DESC LIMIT %d OFFSET %d",
                $limit, $offset
            ), ARRAY_A);
            $source = 'admin_all_ever';
        }
    }

    // 3d: Pull from power dialer calls table as additional source
    $pd_exists = vmo_table_exists($pd_table);
    if ($pd_exists) {
        $pd_where = $is_admin ? "1=1" : $wpdb->prepare("user_id = %d", $user_id);
        $pd_calls = $wpdb->get_results($wpdb->prepare(
            "SELECT call_sid, phone AS caller_number, 'outbound' AS direction, status, duration, created_at, user_id FROM {$pd_table} WHERE {$pd_where} ORDER BY created_at DESC LIMIT %d",
            $limit
        ), ARRAY_A);
        if (!empty($pd_calls)) {
            // Merge power dialer calls, avoiding duplicates by call_sid
            $existing_sids = array();
            foreach ($calls as $c) {
                $sid = $c['call_sid'] ?? '';
                if ($sid !== '') $existing_sids[$sid] = true;
            }
            foreach ($pd_calls as $pdc) {
                $sid = $pdc['call_sid'] ?? '';
                if ($sid !== '' && isset($existing_sids[$sid])) continue;
                $calls[] = $pdc;
                $total++;
            }
            if (empty($source) || $source === 'none') $source = 'power_dialer';
            else $source .= '+power_dialer';
        }
    }

    // 3e: Last resort for admins — fetch recent calls from Twilio API and backfill DB
    //     Uses direct wp_remote_get() because twilio_request() is private.
    if ($total === 0 && $is_admin) {
        try {
            $tw_sid = defined('VMO_TWILIO_ACCOUNT_SID') ? VMO_TWILIO_ACCOUNT_SID : get_option('vmo_twilio_account_sid', '');
            $tw_tok = defined('VMO_TWILIO_AUTH_TOKEN')  ? VMO_TWILIO_AUTH_TOKEN  : get_option('vmo_twilio_auth_token', '');
            $biz_phone = '';
            if (class_exists('VMO_Twilio_Manager')) {
                $mgr = VMO_Twilio_Manager::get_instance();
                if ($mgr && method_exists($mgr, 'get_user_phone')) {
                    $biz_phone = (string) ($mgr->get_user_phone($user_id) ?: '');
                }
            }
            $debug['twilio_backfill_phone'] = $biz_phone;
            $debug['twilio_backfill_has_creds'] = ($tw_sid !== '' && $tw_tok !== '');

            if ($tw_sid !== '' && $tw_tok !== '' && $biz_phone !== '') {
                $tw_base = 'https://api.twilio.com/2010-04-01/Accounts/' . $tw_sid;
                $tw_auth = array('Authorization' => 'Basic ' . base64_encode($tw_sid . ':' . $tw_tok)); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- HTTP Basic auth header
                $tw_args = array('headers' => $tw_auth, 'timeout' => 15);

                // Helper to fetch + parse Twilio calls
                $fetch_twilio = function($params) use ($tw_base, $tw_args) {
                    $url = add_query_arg($params, $tw_base . '/Calls.json');
                    $resp = wp_remote_get($url, $tw_args);
                    if (is_wp_error($resp)) return array();
                    $code = wp_remote_retrieve_response_code($resp);
                    if ($code >= 400) return array();
                    $body = json_decode(wp_remote_retrieve_body($resp), true);
                    return (!empty($body['calls']) && is_array($body['calls'])) ? $body['calls'] : array();
                };

                // Fetch inbound calls (To = business phone)
                $twilio_in = $fetch_twilio(array('To' => $biz_phone, 'PageSize' => 20));
                // Fetch outbound calls (From = business phone)
                $twilio_out = $fetch_twilio(array('From' => $biz_phone, 'PageSize' => 20));
                $all_twilio = array_merge($twilio_in, $twilio_out);

                $debug['twilio_api_inbound_count'] = count($twilio_in);
                $debug['twilio_api_outbound_count'] = count($twilio_out);

                $seen_sids = array();
                foreach ($all_twilio as $tc) {
                    $sid = $tc['sid'] ?? '';
                    if ($sid === '' || isset($seen_sids[$sid])) continue;
                    $seen_sids[$sid] = true;

                    $created_raw = $tc['date_created'] ?? '';
                    $created_mysql = '';
                    if ($created_raw) {
                        $ts = strtotime($created_raw);
                        // Use wp_date() so the stored timestamp matches WP site timezone,
                        // consistent with current_time('mysql') used elsewhere.
                        if ($ts) $created_mysql = wp_date('Y-m-d H:i:s', $ts);
                    }
                    $dir = ($tc['direction'] ?? '') === 'outbound-api' ? 'outbound' : 'inbound';
                    $dur = (int) ($tc['duration'] ?? 0);
                    $st  = strtolower($tc['status'] ?? 'completed');
                    $caller = ($dir === 'inbound') ? ($tc['from'] ?? '') : ($tc['to'] ?? '');

                    // Backfill into DB for future queries (ignore duplicate errors)
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                    $wpdb->insert($calls_table, array(
                        'user_id'       => $user_id,
                        'call_sid'      => $sid,
                        'caller_number' => $caller,
                        'direction'     => $dir,
                        'status'        => $st,
                        'duration'      => $dur,
                        'ai_handled'    => ($dir === 'inbound') ? 1 : 0,
                        'created_at'    => $created_mysql ?: current_time('mysql'),
                    ), array('%d', '%s', '%s', '%s', '%s', '%d', '%d', '%s'));

                    $calls[] = array(
                        'call_sid'      => $sid,
                        'caller_number' => $caller,
                        'direction'     => $dir,
                        'status'        => $st,
                        'duration'      => $dur,
                        'created_at'    => $created_mysql,
                        'user_id'       => $user_id,
                    );
                    $total++;
                }
                if ($total > 0) $source = 'twilio_api_backfill';
            }
        } catch (Throwable $e) {
            $debug['twilio_error'] = $e->getMessage();
        }
    }

    // Sort merged results by created_at DESC
    usort($calls, function($a, $b) {
        return strcmp($b['created_at'] ?? '', $a['created_at'] ?? '');
    });
    $calls = array_slice($calls, 0, $limit);

    $debug['source'] = $source;
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $debug['total_in_table'] = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$calls_table}");
    $debug['pd_table_exists'] = $pd_exists;
    if ($pd_exists) {
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        $debug['pd_total'] = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$pd_table}");
    }

    // ── Step 4: Normalize fields for Strategic Dashboard UI ──
    $biz_number = '';
    if (class_exists('VMO_Twilio_Manager')) {
        try {
            $mgr = VMO_Twilio_Manager::get_instance();
            if ($mgr && method_exists($mgr, 'get_user_phone')) {
                $biz_number = (string) ($mgr->get_user_phone($user_id) ?: '');
            }
        } catch (Throwable $e) {
            $biz_number = '';
        }
    }

    $normalized = array();
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    $contacts_exist = vmo_table_exists($contacts_table);
    $contact_map = array();
    if ($contacts_exist && !empty($calls)) {
        $phones = array();
        foreach ($calls as $c) {
            $cn = preg_replace('/[^0-9]/', '', (string) ($c['caller_number'] ?? ''));
            if ($cn !== '') $phones[$cn] = true;
        }
        if ($phones) {
            // Use SELECT * to avoid 'Unknown column' errors from schema drift
            $rows = $wpdb->get_results($wpdb->prepare(
                "SELECT * FROM {$contacts_table} WHERE user_id = %d",
                $user_id
            ), ARRAY_A);
            foreach (($rows ?: array()) as $r) {
                $d = preg_replace('/[^0-9]/', '', (string) ($r['phone'] ?? ''));
                if ($d !== '') {
                    // Resolve name from whichever column exists
                    $cname = '';
                    if (!empty($r['full_name'])) {
                        $cname = (string) $r['full_name'];
                    } elseif (!empty($r['first_name']) || !empty($r['last_name'])) {
                        $cname = trim(($r['first_name'] ?? '') . ' ' . ($r['last_name'] ?? ''));
                    } elseif (!empty($r['name'])) {
                        $cname = (string) $r['name'];
                    }
                    $contact_map[$d] = array(
                        'name' => $cname,
                        'category' => (string) ($r['primary_category'] ?? 'lead'),
                    );
                }
            }
        }
    }
    // WordPress site timezone — used to annotate timestamps for browser conversion
    $wp_tz = wp_timezone();

    foreach (($calls ?: array()) as $c) {
        $direction = (string) ($c['direction'] ?? 'inbound');
        $status = (string) ($c['status'] ?? ($c['call_status'] ?? ''));
        $duration = (int) ($c['duration'] ?? ($c['call_duration'] ?? 0));
        $call_sid = (string) ($c['call_sid'] ?? '');
        $created = (string) ($c['created_at'] ?? ($c['call_start_time'] ?? ''));

        // Convert timestamp to ISO 8601 with timezone offset so the browser
        // can display it in the user's local time via toLocaleString().
        if ($created !== '') {
            try {
                $dt = new DateTime($created, $wp_tz);
                $created = $dt->format('c'); // e.g. "2026-02-21T04:41:11+00:00"
            } catch (Exception $e) {
                // keep original string on parse failure
            }
        }

        $other = (string) ($c['caller_number'] ?? ($c['caller'] ?? ''));
        if ($other === '' && isset($c['to_number'])) {
            $other = (string) $c['to_number'];
        }

        $from_number = ($direction === 'outbound') ? $biz_number : $other;
        $to_number = ($direction === 'outbound') ? $other : $biz_number;
        $rec = (string) ($c['recording_url'] ?? ($c['recording_local_path'] ?? ''));

        $caller_name = '';
        $caller_category = '';
        $other_digits = preg_replace('/[^0-9]/', '', $other);
        if ($other_digits !== '' && isset($contact_map[$other_digits])) {
            $caller_name = $contact_map[$other_digits]['name'];
            $caller_category = $contact_map[$other_digits]['category'];
        }

        $normalized[] = array(
            'call_sid' => $call_sid,
            'from_number' => $from_number,
            'to_number' => $to_number,
            'direction' => $direction,
            'call_start_time' => $created,
            'call_duration' => $duration,
            'call_status' => $status,
            'recording_url' => $rec,
            'caller_name' => $caller_name,
            'caller_category' => $caller_category,
        );
    }

    wp_send_json_success(array(
        'calls' => $normalized,
        'total' => intval($total),
        'limit' => $limit,
        'offset' => $offset,
        'debug' => $debug
    ));
});

// AJAX: Get recordings for a call (by Call SID)
add_action('wp_ajax_vmo_get_call_recordings', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }

    global $wpdb;
    $user_id = get_current_user_id();
    $call_sid = isset($_POST['call_sid']) ? sanitize_text_field(wp_unslash($_POST['call_sid'])) : '';
    if ($call_sid === '') {
        wp_send_json_error(array('message' => 'Missing call_sid'), 400);
    }

    $recording_url = '';
    $duration = 0;
    $created_at = '';
    $is_admin = current_user_can('manage_options');

    $phone_calls_table = $wpdb->prefix . 'vmo_phone_calls';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$phone_calls_table}'") === $phone_calls_table) {
        // Try user-scoped query first
        $row = $wpdb->get_row($wpdb->prepare(
            "SELECT recording_url, duration, created_at FROM {$phone_calls_table} WHERE user_id=%d AND call_sid=%s LIMIT 1",
            $user_id,
            $call_sid
        ), ARRAY_A);
        // Admin fallback: call_sid is globally unique; user_id may mismatch when
        // inbound calls are routed to a different user or stored with user_id=0.
        if (!is_array($row) && $is_admin) {
            $row = $wpdb->get_row($wpdb->prepare(
                "SELECT recording_url, duration, created_at FROM {$phone_calls_table} WHERE call_sid=%s LIMIT 1",
                $call_sid
            ), ARRAY_A);
        }
        if (is_array($row)) {
            $recording_url = (string) ($row['recording_url'] ?? '');
            $duration = (int) ($row['duration'] ?? 0);
            $created_at = (string) ($row['created_at'] ?? '');
        }
    }

    if ($recording_url === '') {
        $pd_calls_table = $wpdb->prefix . 'vmo_power_dialer_calls';
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        if ($wpdb->get_var("SHOW TABLES LIKE '{$pd_calls_table}'") === $pd_calls_table) {
            $row = $wpdb->get_row($wpdb->prepare(
                "SELECT recording_url, duration, created_at FROM {$pd_calls_table} WHERE user_id=%d AND call_sid=%s LIMIT 1",
                $user_id,
                $call_sid
            ), ARRAY_A);
            if (!is_array($row) && $is_admin) {
                $row = $wpdb->get_row($wpdb->prepare(
                    "SELECT recording_url, duration, created_at FROM {$pd_calls_table} WHERE call_sid=%s LIMIT 1",
                    $call_sid
                ), ARRAY_A);
            }
            if (is_array($row)) {
                $recording_url = (string) ($row['recording_url'] ?? '');
                $duration = (int) ($row['duration'] ?? 0);
                $created_at = (string) ($row['created_at'] ?? '');
            }
        }
    }

    // ── Twilio API fallback: use TwilioManager (proper credentials) ──
    $twilio_debug = array();
    if ($recording_url === '') {
        $twilio_source = 'none';
        if (class_exists('VMO_Twilio_Manager')) {
            $manager = VMO_Twilio_Manager::get_instance();
            $twilio_rec = $manager->fetch_call_recordings($call_sid);
            if (is_array($twilio_rec)) {
                $twilio_debug = $twilio_rec['debug'] ?? array();
                if (!empty($twilio_rec['url'])) {
                    $recording_url = $twilio_rec['url'];
                    $duration      = $twilio_rec['duration'] ?: $duration;
                    $created_at    = $twilio_rec['created'] ?: $created_at;
                    $twilio_source = $twilio_rec['source'];
                    if (defined('WP_DEBUG') && WP_DEBUG) error_log('[VMO Recording Playback] TwilioManager found recording for ' . $call_sid . ' via ' . $twilio_source);
                    // Persist to DB for instant future plays
                    if (isset($phone_calls_table)) {
                        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                        $wpdb->update($phone_calls_table, array('recording_url' => $recording_url), array('call_sid' => $call_sid));
                    }
                } else {
                    if (defined('WP_DEBUG') && WP_DEBUG) error_log('[VMO Recording Playback] TwilioManager found NO recording for ' . $call_sid . ' debug=' . wp_json_encode($twilio_debug));
                }
            } else {
                $twilio_debug['error'] = 'fetch returned null (simulation mode or empty sid)';
                if (defined('WP_DEBUG') && WP_DEBUG) error_log('[VMO Recording Playback] TwilioManager returned null for ' . $call_sid);
            }
        } else {
            $twilio_debug['error'] = 'VMO_Twilio_Manager class not available';
            if (defined('WP_DEBUG') && WP_DEBUG) error_log('[VMO Recording Playback] VMO_Twilio_Manager not available');
        }

    }

    // ── Transcript fallback when no recording exists ──
    $transcript_lines = array();
    if ($recording_url === '') {
        // Suppress DB errors so HTML doesn't pollute the JSON response
        $prev_suppress = $wpdb->suppress_errors(true);

        // 1. Try vmo_call_logs (actual schema: twilio_call_sid, transcription, ai_summary)
        $call_logs_table = $wpdb->prefix . 'vmo_call_logs';
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        if ($wpdb->get_var("SHOW TABLES LIKE '{$call_logs_table}'") === $call_logs_table) {
            // Check which columns exist — schema may vary between installs
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
            $cols = $wpdb->get_col("SHOW COLUMNS FROM {$call_logs_table}", 0);
            $cols_set = array_flip($cols);

            // Newer realtime schema has speaker/message/call_id columns
            if (isset($cols_set['speaker'], $cols_set['message'])) {
                $sid_col = isset($cols_set['call_sid']) ? 'call_sid' : (isset($cols_set['twilio_call_sid']) ? 'twilio_call_sid' : '');
                $time_col = isset($cols_set['timestamp']) ? 'timestamp' : (isset($cols_set['created_at']) ? 'created_at' : '');
                if ($sid_col !== '') {
                    $q = "SELECT speaker, message" . ($time_col ? ", {$time_col} AS ts" : "") . " FROM {$call_logs_table} WHERE {$sid_col}=%s ORDER BY id ASC LIMIT 200";
                    $logs = $wpdb->get_results($wpdb->prepare($q, $call_sid), ARRAY_A);
                    if (is_array($logs)) {
                        foreach ($logs as $l) {
                            $transcript_lines[] = array(
                                'speaker' => (string) ($l['speaker'] ?? ''),
                                'message' => (string) ($l['message'] ?? ''),
                                'time'    => (string) ($l['ts'] ?? ''),
                            );
                        }
                    }
                }
            }
            // Original schema has transcription/ai_summary as text blobs
            elseif (isset($cols_set['transcription'])) {
                $sid_col = isset($cols_set['twilio_call_sid']) ? 'twilio_call_sid' : (isset($cols_set['call_sid']) ? 'call_sid' : '');
                if ($sid_col !== '') {
                    $raw = (string) $wpdb->get_var($wpdb->prepare(
                        "SELECT COALESCE(transcription,'') FROM {$call_logs_table} WHERE {$sid_col}=%s LIMIT 1", $call_sid
                    ));
                    if ($raw !== '') {
                        $dec = json_decode($raw, true);
                        if (is_array($dec)) {
                            $entries = isset($dec['logs']) && is_array($dec['logs']) ? $dec['logs'] : $dec;
                            foreach ($entries as $e) {
                                if (!is_array($e)) continue;
                                $transcript_lines[] = array(
                                    'speaker' => (string) ($e['speaker'] ?? ($e['role'] ?? '')),
                                    'message' => (string) ($e['message'] ?? ($e['content'] ?? '')),
                                    'time'    => (string) ($e['timestamp'] ?? ($e['time'] ?? '')),
                                );
                            }
                        } else {
                            // Plain-text transcript
                            $transcript_lines[] = array('speaker' => '', 'message' => $raw, 'time' => '');
                        }
                    }
                }
            }
        }

        // 2. Fall back to vmo_phone_calls.transcript column
        if (empty($transcript_lines) && isset($phone_calls_table)) {
            $raw_t = (string) $wpdb->get_var($wpdb->prepare(
                "SELECT COALESCE(transcript,'') FROM {$phone_calls_table} WHERE call_sid=%s LIMIT 1",
                $call_sid
            ));
            if ($raw_t !== '') {
                $dec = json_decode($raw_t, true);
                if (is_array($dec)) {
                    $entries = isset($dec['logs']) && is_array($dec['logs']) ? $dec['logs'] : $dec;
                    foreach ($entries as $e) {
                        if (!is_array($e)) continue;
                        $transcript_lines[] = array(
                            'speaker' => (string) ($e['speaker'] ?? ($e['role'] ?? '')),
                            'message' => (string) ($e['message'] ?? ($e['content'] ?? '')),
                            'time'    => (string) ($e['timestamp'] ?? ($e['time'] ?? '')),
                        );
                    }
                } else {
                    // Plain-text transcript: "Caller: ...\nAI: ..." (stored by handle_ai_conversation)
                    $lines = preg_split('/\r?\n/', $raw_t);
                    foreach ($lines as $line) {
                        $line = trim($line);
                        if ($line === '') continue;
                        if (preg_match('/^(Caller|AI|User|Assistant|Bot|System)\s*:\s*(.+)$/i', $line, $m)) {
                            $speaker_raw = strtolower($m[1]);
                            $is_ai = in_array($speaker_raw, array('ai', 'assistant', 'bot', 'system'), true);
                            $transcript_lines[] = array(
                                'speaker' => $is_ai ? 'AI' : 'Caller',
                                'message' => trim($m[2]),
                                'time'    => '',
                            );
                        } else {
                            // Unprefixed line — treat as system message
                            $transcript_lines[] = array(
                                'speaker' => '',
                                'message' => $line,
                                'time'    => '',
                            );
                        }
                    }
                }
            }
        }

        $wpdb->suppress_errors($prev_suppress);

        // Sibling transcript search: if no transcript found and the call
        // is a simultaneous ring (outbound with no AI interaction), find
        // the related inbound call's transcript from the same time window.
        if (empty($transcript_lines) && $recording_url === '' && isset($phone_calls_table)) {
            $this_call = $wpdb->get_row($wpdb->prepare(
                "SELECT direction, created_at FROM {$phone_calls_table} WHERE call_sid = %s LIMIT 1",
                $call_sid
            ), ARRAY_A);
            if (is_array($this_call) && ($this_call['direction'] ?? '') === 'outbound' && !empty($this_call['created_at'])) {
                $ts = strtotime($this_call['created_at']);
                if ($ts) {
                    $w_start = date('Y-m-d H:i:s', $ts - 60);
                    $w_end   = date('Y-m-d H:i:s', $ts + 60);
                    $sibling_sid = $wpdb->get_var($wpdb->prepare(
                        "SELECT call_sid FROM {$phone_calls_table}
                         WHERE direction = 'inbound'
                           AND call_sid != %s
                           AND created_at BETWEEN %s AND %s
                         ORDER BY created_at DESC LIMIT 1",
                        $call_sid, $w_start, $w_end
                    ));
                    if ($sibling_sid) {
                        // Check vmo_call_logs for sibling transcript
                        $call_logs_table2 = $wpdb->prefix . 'vmo_call_logs';
                        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
                        if ($wpdb->get_var("SHOW TABLES LIKE '{$call_logs_table2}'") === $call_logs_table2) {
                            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
                            $cols2 = $wpdb->get_col("SHOW COLUMNS FROM {$call_logs_table2}", 0);
                            $cols2_set = array_flip($cols2 ?: array());
                            if (isset($cols2_set['speaker'], $cols2_set['message'])) {
                                $sid_col2 = isset($cols2_set['call_sid']) ? 'call_sid' : (isset($cols2_set['twilio_call_sid']) ? 'twilio_call_sid' : '');
                                if ($sid_col2 !== '') {
                                    $logs2 = $wpdb->get_results($wpdb->prepare(
                                        "SELECT speaker, message FROM {$call_logs_table2} WHERE {$sid_col2}=%s ORDER BY id ASC LIMIT 200",
                                        $sibling_sid
                                    ), ARRAY_A);
                                    if (is_array($logs2) && !empty($logs2)) {
                                        foreach ($logs2 as $l2) {
                                            $transcript_lines[] = array(
                                                'speaker' => (string) ($l2['speaker'] ?? ''),
                                                'message' => (string) ($l2['message'] ?? ''),
                                                'time'    => '',
                                            );
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    // Always return success; JS decides what to show based on recordings vs transcript.
    wp_send_json_success(array(
        'recordings' => $recording_url !== '' ? array(
            array(
                'recording_url' => $recording_url,
                'media_url' => $recording_url,
                'duration' => $duration,
                'date_created' => $created_at,
            ),
        ) : array(),
        'transcript' => $transcript_lines,
        'has_audio'  => $recording_url !== '',
        'debug'      => array(
            'db_had_url'   => $recording_url !== '' && empty($twilio_debug),
            'twilio_debug' => $twilio_debug,
            'call_sid'     => $call_sid,
        ),
    ));
});

// AJAX: Get call details (transcript/summary) by Call SID
add_action('wp_ajax_vmo_get_call_details', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }

    global $wpdb;
    $user_id = get_current_user_id();
    $call_sid = isset($_POST['call_sid']) ? sanitize_text_field(wp_unslash($_POST['call_sid'])) : '';
    if ($call_sid === '') {
        wp_send_json_error(array('message' => 'Missing call_sid'), 400);
    }

    $data = null;
    $phone_calls_table = $wpdb->prefix . 'vmo_phone_calls';
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$phone_calls_table}'") === $phone_calls_table) {
        $data = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$phone_calls_table} WHERE user_id=%d AND call_sid=%s LIMIT 1",
            $user_id,
            $call_sid
        ), ARRAY_A);
    }

    if (!$data) {
        $pd_calls_table = $wpdb->prefix . 'vmo_power_dialer_calls';
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        if ($wpdb->get_var("SHOW TABLES LIKE '{$pd_calls_table}'") === $pd_calls_table) {
            $data = $wpdb->get_row($wpdb->prepare(
                "SELECT * FROM {$pd_calls_table} WHERE user_id=%d AND call_sid=%s LIMIT 1",
                $user_id,
                $call_sid
            ), ARRAY_A);
        }
    }

    if (!$data) {
        wp_send_json_error(array('message' => 'Call not found'), 404);
    }

    // Best-effort transcript extraction. Strategic dashboard expects `logs: [{speaker,message,timestamp}]`.
    $raw_transcript = '';
    foreach (array('transcript', 'transcription', 'conversation_data') as $k) {
        if (!empty($data[$k])) {
            $raw_transcript = (string) $data[$k];
            break;
        }
    }

    $logs = array();
    if ($raw_transcript !== '') {
        $decoded = json_decode($raw_transcript, true);
        if (is_array($decoded)) {
            // Accept either an array of entries or a {logs:[...]} structure
            $entries = $decoded;
            if (isset($decoded['logs']) && is_array($decoded['logs'])) {
                $entries = $decoded['logs'];
            }
            if (is_array($entries)) {
                foreach ($entries as $e) {
                    if (!is_array($e)) continue;
                    $logs[] = array(
                        'speaker' => (string) ($e['speaker'] ?? ($e['role'] ?? '')),
                        'message' => (string) ($e['message'] ?? ($e['content'] ?? '')),
                        'timestamp' => (string) ($e['timestamp'] ?? ($e['time'] ?? ($e['created_at'] ?? ''))),
                    );
                }
            }
        } else {
            // Plain text transcript (e.g. "Caller: hello\nAI: welcome...")
            // Parse Caller/AI prefixed lines into structured log entries
            $lines = preg_split('/\n/', $raw_transcript);
            $parsed_any = false;
            foreach ($lines as $line) {
                $line = trim($line);
                if ($line === '') continue;
                if (preg_match('/^(Caller|AI|Agent|User|System|TRANSCRIPT)\s*:\s*(.+)/i', $line, $m)) {
                    $logs[] = array(
                        'speaker' => strtoupper($m[1]),
                        'message' => trim($m[2]),
                        'timestamp' => '',
                    );
                    $parsed_any = true;
                } elseif ($parsed_any && !empty($logs)) {
                    // Continuation line — append to the last entry
                    $logs[count($logs) - 1]['message'] .= "\n" . $line;
                } else {
                    $logs[] = array(
                        'speaker' => 'TRANSCRIPT',
                        'message' => $line,
                        'timestamp' => '',
                    );
                }
            }
            // Set timestamp on first entry
            if (!empty($logs)) {
                $logs[0]['timestamp'] = (string) ($data['created_at'] ?? '');
            }
        }
    }

    wp_send_json_success(array(
        'call_sid' => (string) ($data['call_sid'] ?? $call_sid),
        'status' => (string) ($data['status'] ?? ($data['call_status'] ?? '')),
        'duration' => (int) ($data['duration'] ?? 0),
        'summary' => (string) ($data['summary'] ?? ($data['call_summary'] ?? '')),
        'logs' => $logs,
    ));
});

// AJAX: Upload ringtone to media library
add_action('wp_ajax_vmo_upload_ringtone', function() {
    if (!current_user_can('upload_files')) {
        wp_send_json_error(array('message' => 'Forbidden'), 403);
    }
    // Accept both nonces for compatibility
    $nonce_valid = false;
    if (isset($_POST['security']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['security'])), 'vmo_builder_nonce')) {
        $nonce_valid = true;
    }
    if (isset($_POST['nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'vmo_admin_nonce')) {
        $nonce_valid = true;
    }
    if (!$nonce_valid) {
        wp_send_json_error(array('message' => 'Security check failed'), 403);
    }
    
    if (empty($_FILES['file'])) {
        wp_send_json_error(array('message' => 'No file uploaded'));
    }
    
    require_once(ABSPATH . 'wp-admin/includes/file.php');
    require_once(ABSPATH . 'wp-admin/includes/media.php');
    require_once(ABSPATH . 'wp-admin/includes/image.php');
    
    $file = $_FILES['file'];
    $upload_overrides = array('test_form' => false);
    $movefile = wp_handle_upload($file, $upload_overrides);
    
    if (isset($movefile['error'])) {
        wp_send_json_error(array('message' => $movefile['error']));
    }
    
    $attachment = array(
        'post_mime_type' => $movefile['type'],
        'post_title' => sanitize_file_name($file['name']),
        'post_content' => '',
        'post_status' => 'inherit'
    );
    
    $attach_id = wp_insert_attachment($attachment, $movefile['file']);
    $attach_data = wp_generate_attachment_metadata($attach_id, $movefile['file']);
    wp_update_attachment_metadata($attach_id, $attach_data);
    
    wp_send_json_success(array(
        'url' => $movefile['url'],
        'attachment_id' => $attach_id,
        'source_url' => wp_get_attachment_url($attach_id)
    ));
});

// === User Init — Admin Bypass + No Free Tokens ===
// No free tokens are granted. New users default to 'free' with 0 balance.
// They must purchase a plan to get tokens. Admin bypass is preserved.
add_action('init', function () {
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $current_balance = get_user_meta($user_id, 'vmo_token_balance', true);
        
        // First-time user setup — NO tokens granted
        if ($current_balance === '') {
            update_user_meta($user_id, 'vmo_token_balance', 0);
            update_user_meta($user_id, 'vmo_subscription_status', 'free');
            update_user_meta($user_id, 'vmo_token_type', 'none');
        }
        
        // ADMIN BYPASS: Master Account gets unlimited tokens
        if (user_can($user_id, 'manage_options')) {
            update_user_meta($user_id, 'vmo_subscription_status', 'enterprise_pack');
            update_user_meta($user_id, 'vmo_plan_name', 'Master Account');
            
            // Ensure unlimited balance
            if ((int)$current_balance < 5000000) {
                update_user_meta($user_id, 'vmo_token_balance', 10000000);
            }
        }
    }
});

// === AJAX Token Balance Endpoint ===
add_action('wp_ajax_vmo_get_token_balance', function () {
    check_ajax_referer('vmo_builder_nonce', 'security');
    
    // Get usage data with percentage and days remaining
    if (class_exists('VMO_Usage_Calculator')) {
        $usage_data = VMO_Usage_Calculator::get_usage_data(get_current_user_id());
        wp_send_json_success([
            'balance' => $usage_data['current_balance'],
            'usage_percent' => $usage_data['usage_percent'],
            'days_remaining' => $usage_data['days_remaining'],
            'display' => round($usage_data['usage_percent']) . '% used • ' . $usage_data['days_remaining'] . ' days remaining'
        ]);
    } else {
        // Fallback to old behavior
        $balance = vmo_get_token_balance(get_current_user_id());
        wp_send_json_success(['balance' => $balance]);
    }
});

// === AJAX Conversation Endpoints ===
// Note: These methods don't exist as static methods, so removing to prevent conflicts
// The conversation system uses vmo_conversation_chat action defined in generate-content.php

// === AJAX Business Consultation Endpoints ===
// Note: These are already registered in business-wizard-handler.php as standalone functions
// Removing duplicate registrations to prevent conflicts

//=== TEMPLATE REGISTRATION ===//
add_filter('theme_page_templates', function($templates) {
    $templates['vmo-fullwidth-template.php'] = 'VMO Fullwidth Template';
    return $templates;
});

/* --------------------------------------------------------------
   Frontend runtime for published VMO pages

   Problem: older generator output is a full HTML document (doctype/html/head/body)
   but publishing stores it in post_content. Without wp_head/wp_footer, scripts in
   <head> never load reliably and the site looks unstyled/broken.

   Fix: (1) extract head/body/footer on save and store head/footer in post meta
        (2) output stored head/footer and enqueue baseline libs on VMO pages
        (3) template calls wp_head/wp_footer (see templates/vmo-fullwidth-template.php)
---------------------------------------------------------------- */

if (!function_exists('vmo_is_vmo_fullwidth_page')) {
    function vmo_is_vmo_fullwidth_page($post_id = null) {
        if (!$post_id) {
            $post_id = function_exists('get_queried_object_id') ? (int) get_queried_object_id() : 0;
        }
        if (!$post_id) return false;
        return get_post_meta($post_id, '_wp_page_template', true) === 'vmo-fullwidth-template.php';
    }
}

if (!function_exists('vmo_split_html_document')) {
    function vmo_split_html_document($html) {
        $html = (string) $html;
        $trim = ltrim($html);

        $looks_full = (bool) preg_match('/^\s*(<!doctype\b|<html\b)/i', $trim);
        $has_html_tag = (bool) preg_match('/<html\b/i', $html);
        if (!$looks_full && !$has_html_tag) {
            return [
                'is_full_document' => false,
                'head' => '',
                'body' => $html,
                'footer' => ''
            ];
        }

        $out = [
            'is_full_document' => true,
            'head' => '',
            'body' => $html,
            'footer' => ''
        ];

        if (!class_exists('DOMDocument')) {
            // Regex fallback
            if (preg_match('/<head\b[^>]*>([\s\S]*?)<\/head>/i', $html, $m)) {
                $out['head'] = $m[1];
            }
            if (preg_match('/<body\b[^>]*>([\s\S]*?)<\/body>/i', $html, $m)) {
                $out['body'] = $m[1];
            }
            return $out;
        }

        $prev = libxml_use_internal_errors(true);
        $dom = new DOMDocument();
        // Best-effort parse; tolerate imperfect HTML from LLM output.
        $dom->loadHTML($html, LIBXML_NOWARNING | LIBXML_NOERROR);

        $to_inner_html = static function($node) use ($dom) {
            if (!$node) return '';
            $buf = '';
            foreach ($node->childNodes as $child) {
                $buf .= $dom->saveHTML($child);
            }
            return $buf;
        };

        $headNode = $dom->getElementsByTagName('head')->item(0);
        $bodyNode = $dom->getElementsByTagName('body')->item(0);

        $out['head'] = $to_inner_html($headNode);

        // Pull scripts out of body into a footer bucket (helps prevent inline scripts
        // getting neutered inside post_content and ensures correct placement).
        if ($bodyNode) {
            $footer = '';
            $scripts = [];
            foreach ($bodyNode->getElementsByTagName('script') as $script) {
                $scripts[] = $script;
            }
            foreach ($scripts as $script) {
                $footer .= $dom->saveHTML($script);
                if ($script->parentNode) {
                    $script->parentNode->removeChild($script);
                }
            }
            $out['footer'] = $footer;
            $out['body'] = $to_inner_html($bodyNode);
        }

        libxml_clear_errors();
        libxml_use_internal_errors($prev);
        return $out;
    }
}

if (!function_exists('vmo_kses_generated_head_footer')) {
    function vmo_kses_generated_head_footer($html) {
        // Allow a controlled subset of tags needed for generated sites.
        $allowed = [
            'meta' => [
                'charset' => true,
                'name' => true,
                'content' => true,
                'property' => true,
                'http-equiv' => true,
            ],
            'link' => [
                'rel' => true,
                'href' => true,
                'as' => true,
                'type' => true,
                'media' => true,
                'sizes' => true,
                'crossorigin' => true,
                'integrity' => true,
                'referrerpolicy' => true,
            ],
            'style' => [
                'type' => true,
                'media' => true,
                'id' => true,
            ],
            'script' => [
                'src' => true,
                'type' => true,
                'async' => true,
                'defer' => true,
                'nomodule' => true,
                'crossorigin' => true,
                'integrity' => true,
                'referrerpolicy' => true,
                'id' => true,
            ],
            'title' => [],
            'base' => [ 'href' => true, 'target' => true ],
        ];
        return wp_kses((string) $html, $allowed);
    }
}

// Enqueue a baseline runtime for published VMO pages (WordPress-native mode)
add_action('wp_enqueue_scripts', function() {
    if (is_admin()) return;
    if (!is_singular()) return;
    $post_id = (int) get_queried_object_id();
    if (!$post_id || !vmo_is_vmo_fullwidth_page($post_id)) return;

    // Tailwind CSS (bundled locally for generated site preview)
    wp_enqueue_script('vmo-tailwind-cdn', VMO_AI_BUILDER_URL . 'assets/vendor/tailwindcss.min.js', [], '3.4.0', false);

    // GSAP + ScrollTrigger for cinematic motion hooks (bundled locally)
    wp_enqueue_script('vmo-gsap', VMO_AI_BUILDER_URL . 'assets/vendor/gsap.min.js', [], '3.12.2', true);
    wp_enqueue_script('vmo-gsap-scrolltrigger', VMO_AI_BUILDER_URL . 'assets/vendor/ScrollTrigger.min.js', ['vmo-gsap'], '3.12.2', true);

    $motion_runtime_path = VMO_AI_BUILDER_DIR . 'assets/vmo-motion-runtime.js';
    if (file_exists($motion_runtime_path)) {
        wp_enqueue_script('vmo-motion-runtime', VMO_AI_BUILDER_URL . 'assets/vmo-motion-runtime.js', ['vmo-gsap-scrolltrigger'], filemtime($motion_runtime_path), true);
    }

    // AOS animation library (bundled locally)
    wp_enqueue_style('vmo-aos', VMO_AI_BUILDER_URL . 'assets/vendor/aos.css', [], '2.3.1');
    wp_enqueue_script('vmo-aos', VMO_AI_BUILDER_URL . 'assets/vendor/aos.min.js', [], '2.3.1', true);

    // 360° Product Spin Viewer (for interactive product rotation)
    $spin_viewer_path = VMO_AI_BUILDER_DIR . 'assets/vmo-360-spin-viewer.js';
    if (file_exists($spin_viewer_path)) {
        wp_enqueue_script('vmo-360-spin-viewer', VMO_AI_BUILDER_URL . 'assets/vmo-360-spin-viewer.js', [], filemtime($spin_viewer_path), true);
    }

    // Animated Overlays System (clouds, smoke, particles, ripples, etc.)
    $overlays_path = VMO_AI_BUILDER_DIR . 'assets/vmo-animated-overlays.js';
    if (file_exists($overlays_path)) {
        wp_enqueue_script('vmo-animated-overlays', VMO_AI_BUILDER_URL . 'assets/vmo-animated-overlays.js', [], filemtime($overlays_path), true);
    }

    // Minimal initializer for AOS (no-op if absent)
    wp_add_inline_script('vmo-aos', 'window.AOS && AOS.init({ once: true, duration: 650, easing: "ease-out" });');
}, 20);

// ══════════════════════════════════════════════════════════════════
// Frontend: Enqueue VMO Visit Tracker on ALL public pages
// Sends visitor analytics data to class-vmo-analytics-tracker.php
// ══════════════════════════════════════════════════════════════════
add_action('wp_enqueue_scripts', function() {
    if (is_admin()) return;

    $tracker_path = VMO_AI_BUILDER_DIR . 'assets/vmo-visit-tracker.js';
    if (!file_exists($tracker_path)) return;

    // Determine the site owner who installed VMO (for multi-tenant analytics)
    $site_owner_id = get_option('vmo_site_owner_id', 1);

    wp_enqueue_script(
        'vmo-visit-tracker',
        VMO_AI_BUILDER_URL . 'assets/vmo-visit-tracker.js',
        [],
        filemtime($tracker_path),
        true
    );
    wp_localize_script('vmo-visit-tracker', 'vmo_tracker_data', [
        'ajaxurl'       => admin_url('admin-ajax.php'),
        'site_user_id'  => (int) $site_owner_id,
    ]);
}, 5);

// Inject extracted <head> assets saved during publish
add_action('wp_head', function() {
    if (is_admin()) return;
    if (!is_singular()) return;
    $post_id = (int) get_queried_object_id();
    if (!$post_id || !vmo_is_vmo_fullwidth_page($post_id)) return;

    $head = (string) get_post_meta($post_id, 'vmo_generated_head_html', true);
    if ($head) {
        echo vmo_kses_generated_head_footer($head); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    }
}, 5);

// Inject extracted footer scripts saved during publish
add_action('wp_footer', function() {
    if (is_admin()) return;
    if (!is_singular()) return;
    $post_id = (int) get_queried_object_id();
    if (!$post_id || !vmo_is_vmo_fullwidth_page($post_id)) return;

    $footer = (string) get_post_meta($post_id, 'vmo_generated_footer_html', true);
    if ($footer) {
        echo vmo_kses_generated_head_footer($footer); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    }
}, 50);

// === Force Template on Publish ===
add_action('save_post_page', function($post_id) {
    // Only set if not already set
    if (get_post_meta($post_id, '_wp_page_template', true) !== 'vmo-fullwidth-template.php') {
        update_post_meta($post_id, '_wp_page_template', 'vmo-fullwidth-template.php');
    }
}, 10, 1);

// === Use Custom Template for Published Pages ===
add_filter('template_include', function($template) {
    // Only apply to frontend pages, never admin
    if (is_admin()) {
        return $template;
    }
    
    if (is_page()) {
        $selected = get_page_template_slug(get_queried_object_id());
        if ($selected === 'vmo-fullwidth-template.php') {
            return plugin_dir_path(__FILE__) . 'templates/vmo-fullwidth-template.php';
        }
    }
    return $template;
});

// === VMO AUTOMATIC SERVICE PROVISIONING CRON HOOKS ===
add_action('init', function() {
    // Only register cron hooks after WordPress and classes are fully loaded
    
    // SEO Automation Cron Hooks
    add_action('vmo_seo_audit', function($user_id) {
        if (class_exists('VMO_Enhanced_Business_Wizard')) {
            $wizard = new VMO_Enhanced_Business_Wizard();
            $wizard->execute_seo_audit($user_id);
        }
    });

    add_action('vmo_generate_automated_blog_post', function($user_id) {
        if (class_exists('VMO_Enhanced_Business_Wizard')) {
            $wizard = new VMO_Enhanced_Business_Wizard();
            $wizard->execute_automated_blog_generation($user_id);
        }
    });

    add_action('vmo_seo_competitive_analysis', function($user_id) {
        if (class_exists('VMO_Enhanced_Business_Wizard')) {
            $wizard = new VMO_Enhanced_Business_Wizard();
            $wizard->execute_seo_competitive_analysis($user_id);
        }
    });

    add_action('vmo_seo_strategy_review', function($user_id) {
        if (class_exists('VMO_Enhanced_Business_Wizard')) {
            $wizard = new VMO_Enhanced_Business_Wizard();
            $wizard->execute_seo_strategy_review($user_id);
        }
    });

    // Email Marketing Automation Cron Hooks
    add_action('vmo_email_performance_analysis', function($user_id) {
        if (class_exists('VMO_Enhanced_Business_Wizard')) {
            $wizard = new VMO_Enhanced_Business_Wizard();
            $wizard->execute_email_performance_analysis($user_id);
        }
    });

    add_action('vmo_email_optimization_check', function($user_id) {
        if (class_exists('VMO_Enhanced_Business_Wizard')) {
            $wizard = new VMO_Enhanced_Business_Wizard();
            $wizard->execute_email_optimization_check($user_id);
        }
    });

    add_action('vmo_email_competitive_analysis', function($user_id) {
        if (class_exists('VMO_Enhanced_Business_Wizard')) {
            $wizard = new VMO_Enhanced_Business_Wizard();
            $wizard->execute_email_competitive_analysis($user_id);
        }
    });

    add_action('vmo_email_strategy_review', function($user_id) {
        if (class_exists('VMO_Enhanced_Business_Wizard')) {
            $wizard = new VMO_Enhanced_Business_Wizard();
            $wizard->execute_email_strategy_review($user_id);
        }
    });

    // COMPREHENSIVE WEEKLY EMAIL AUTOMATION with Deep Research
    add_action('vmo_weekly_email_research', function($user_id) {
        if (class_exists('VMO_Enhanced_Business_Wizard')) {
            $wizard = new VMO_Enhanced_Business_Wizard();
            $wizard->execute_weekly_email_research($user_id);
        }
    });

    // CONTACT MANAGEMENT AUTOMATION
    add_action('vmo_sync_all_contacts', function($user_id) {
        if (class_exists('VMO_Enhanced_Business_Wizard')) {
            $wizard = new VMO_Enhanced_Business_Wizard();
            $wizard->execute_contact_sync($user_id);
        }
    });

    add_action('vmo_contact_engagement_action', function($user_id, $contact_id, $action) {
        if (class_exists('VMO_Enhanced_Business_Wizard')) {
            $wizard = new VMO_Enhanced_Business_Wizard();
            // Execute engagement action based on contact category and strategy
            $wizard->execute_contact_engagement($user_id, $contact_id, $action);
        }
    });
});

// Voice System AJAX Handlers
add_action('wp_ajax_vmo_start_conversation', 'vmo_handle_start_conversation');
function vmo_handle_start_conversation() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['security'])), 'vmo_builder_nonce')) {
        wp_send_json_error(array('message' => 'Security check failed'));
        return;
    }
    
    if (!is_user_logged_in()) {
        wp_send_json_error(array('message' => 'User not logged in'));
        return;
    }
    
    // Generate conversation ID
    $conversation_id = 'conv_' . get_current_user_id() . '_' . time();
    
    wp_send_json_success(array(
        'conversation_id' => $conversation_id,
        'message' => 'Conversation started',
        'status' => 'ready'
    ));
}

add_action('wp_ajax_vmo_send_message', 'vmo_handle_send_message');
function vmo_handle_send_message() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['security'])), 'vmo_builder_nonce')) {
        wp_send_json_error(array('message' => 'Security check failed'));
        return;
    }
    
    if (!is_user_logged_in()) {
        wp_send_json_error(array('message' => 'User not logged in'));
        return;
    }
    
    $message = sanitize_text_field(wp_unslash($_POST['message']));
    $conversation_id = sanitize_text_field(wp_unslash($_POST['conversation_id']));
    
    if (empty($message)) {
        wp_send_json_error(array('message' => 'Message cannot be empty'));
        return;
    }
    
    // Process message with OpenAI
    $api_key = get_option('vmo_openai_api_key');
    if (empty($api_key)) {
        wp_send_json_error(array('message' => 'OpenAI API key not configured'));
        return;
    }
    
    // Check token balance
    $user_id = get_current_user_id();
    $token_balance = (int)get_user_meta($user_id, 'vmo_token_balance', true);
    
    if ($token_balance < 50) { // 50 tokens for voice conversation
        wp_send_json_error(array('message' => 'Insufficient token balance'));
        return;
    }
    
    // Rate limit AI calls (voice: 10/min, 60/hr)
    $rate_check = vmo_check_ai_rate_limit($user_id, 'voice_msg', 10, 60);
    if (is_wp_error($rate_check)) { wp_send_json_error(array('message' => $rate_check->get_error_message())); return; }
    
    // Call OpenAI API
    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', array(
        'timeout' => 30,
        'headers' => array(
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type' => 'application/json',
        ),
        'body' => json_encode(array(
            'model' => 'gpt-4o', // Upgraded from mini for better voice intelligence
            'messages' => array(
                array(
                    'role' => 'system',
                    'content' => 'You are a helpful AI assistant for the VMO Website Builder. Provide concise, helpful responses that are suitable for voice conversation. Keep responses under 100 words.'
                ),
                array(
                    'role' => 'user',
                    'content' => $message
                )
            ),
            'max_tokens' => 250, // Increased slightly for richer responses
            'temperature' => 0.7
        )),
        'timeout' => 30
    ));
    
    if (is_wp_error($response)) {
        wp_send_json_error(array('message' => 'Failed to connect to OpenAI API'));
        return;
    }
    
    $body = wp_remote_retrieve_body($response);
    $data = json_decode($body, true);
    
    if (!isset($data['choices'][0]['message']['content'])) {
        wp_send_json_error(array('message' => 'Invalid API response'));
        return;
    }
    
    // Deduct tokens (Standardized with Admin Bypass)
    if (function_exists('vmo_deduct_tokens')) {
        vmo_deduct_tokens($user_id, 50, 'chat', 'Voice/Text Conversation');
    } else {
        update_user_meta($user_id, 'vmo_token_balance', max(0, $token_balance - 50));
    }
    
    wp_send_json_success(array(
        'response' => $data['choices'][0]['message']['content'],
        'conversation_id' => $conversation_id,
        'tokens_used' => 50
    ));
}

add_action('wp_ajax_vmo_conversation_message', 'vmo_handle_conversation_message');
function vmo_handle_conversation_message() {
    // Alias for vmo_send_message for compatibility
    vmo_handle_send_message();
}

// VOICE SYSTEM REMOVED: All voice API endpoints and functions removed for No Voice version

// === DEDICATED AI AGENT ENDPOINTS ===

if (!function_exists('vmo_extract_json_object_from_text')) {
    function vmo_extract_json_object_from_text($raw_text) {
        $raw_text = is_string($raw_text) ? trim($raw_text) : '';
        if ($raw_text === '') {
            return null;
        }

        $clean = preg_replace('/```(?:json)?(.*?)```/is', '$1', $raw_text) ?? $raw_text;
        $decoded = json_decode($clean, true);
        if (is_array($decoded)) {
            return $decoded;
        }

        $start = strpos($clean, '{');
        $end = strrpos($clean, '}');
        if ($start === false || $end === false || $end <= $start) {
            return null;
        }

        $candidate = substr($clean, $start, ($end - $start) + 1);
        $decoded = json_decode($candidate, true);
        return is_array($decoded) ? $decoded : null;
    }
}

if (!function_exists('vmo_build_default_grapes_strategy_plan')) {
    function vmo_build_default_grapes_strategy_plan($user_prompt, $business_name = 'Your Business', $phone = '', $email = '') {
        $headline_context = trim((string) $user_prompt);
        if (strlen($headline_context) > 120) {
            $headline_context = substr($headline_context, 0, 120) . '...';
        }

        return [
            'version' => '1.0',
            'site' => [
                'business_name' => $business_name,
                'tagline' => 'Built from your consultation and business strategy',
                'goal' => 'Convert visitors into qualified leads and customers'
            ],
            'brand' => [
                'tone' => 'professional',
                'primary_color' => '#0f172a',
                'accent_color' => '#0ea5e9'
            ],
            'tech_stack' => [
                'selected' => ['HTML5', 'CSS3', 'JavaScript (ES6+)', 'GSAP', 'AOS']
            ],
            'integrations' => [
                'phone_enabled' => !empty($phone),
                'phone_number' => (string) $phone,
                'email_enabled' => !empty($email),
                'email_address' => (string) $email,
                'contact_form' => true,
                'seo_ready' => true,
                'payments_ready' => true,
                'media_replace_ready' => true
            ],
            'sections' => [
                [
                    'type' => 'hero',
                    'headline' => $business_name,
                    'subheadline' => 'Professional website generated by your AI strategy team',
                    'body' => $headline_context !== '' ? $headline_context : 'Your consultation insights are now converted into a conversion-focused online presence.',
                    'cta_primary' => 'Get Started',
                    'cta_secondary' => 'Book a Consultation',
                    'media' => 'image'
                ],
                [
                    'type' => 'services',
                    'headline' => 'What We Deliver',
                    'subheadline' => 'Solutions tailored to your business goals',
                    'body' => 'Every section is designed for clarity, trust, and conversion across desktop and mobile.',
                    'bullets' => ['Strategy-led messaging', 'Conversion-focused layout', 'Scalable growth structure']
                ],
                [
                    'type' => 'about',
                    'headline' => 'Why Clients Choose Us',
                    'subheadline' => 'Credibility, consistency, and measurable outcomes',
                    'body' => 'The website foundation reflects your consultation profile, business priorities, and long-term growth direction.'
                ],
                [
                    'type' => 'contact',
                    'headline' => 'Let’s Build Your Next Step',
                    'subheadline' => 'Start a conversation with our team',
                    'body' => 'Use the contact form to request a callback, quote, or implementation session.'
                ]
            ]
        ];
    }
}

if (!function_exists('vmo_normalize_grapes_strategy_plan')) {
    function vmo_normalize_grapes_strategy_plan($plan, $fallback_prompt, $business_name = 'Your Business', $phone = '', $email = '') {
        if (!is_array($plan)) {
            $plan = [];
        }

        $defaults = vmo_build_default_grapes_strategy_plan($fallback_prompt, $business_name, $phone, $email);
        $plan = array_replace_recursive($defaults, $plan);

        if (empty($plan['site']['business_name'])) {
            $plan['site']['business_name'] = $business_name;
        }

        $available_stack = [
            'HTML5', 'CSS3', 'JavaScript (ES6+)', 'Tailwind CSS', 'Bootstrap 5',
            'GSAP', 'AOS', 'Splide.js', 'Three.js', 'Framer Motion',
            'React 18', 'Next.js App Router', 'Alpine.js', 'Schema.org SEO',
            'WordPress APIs', 'Web Components',
            'Lenis', 'Locomotive Scroll', 'Swiper.js', 'Barba.js', 'Lottie',
            'ShadCN UI', 'Chart.js', 'Recharts', 'D3.js', 'Spline',
            'Sora Video', 'DALL-E Images'
        ];

        $selected = $plan['tech_stack']['selected'] ?? [];
        if (!is_array($selected) || empty($selected)) {
            $selected = ['HTML5', 'CSS3', 'JavaScript (ES6+)', 'GSAP', 'AOS'];
        }
        $selected = array_values(array_unique(array_filter(array_map('strval', $selected))));
        $selected = array_values(array_intersect($selected, $available_stack));
        if (!in_array('React 18', $selected, true)) {
            array_unshift($selected, 'React 18');
        }
        if (!in_array('JavaScript (ES6+)', $selected, true)) {
            $selected[] = 'JavaScript (ES6+)';
        }
        if (empty($selected)) {
            $selected = ['React 18', 'HTML5', 'CSS3', 'JavaScript (ES6+)'];
        }
        $plan['tech_stack']['available'] = $available_stack;
        $plan['tech_stack']['selected'] = array_slice($selected, 0, 6);

        if (!isset($plan['integrations']) || !is_array($plan['integrations'])) {
            $plan['integrations'] = [];
        }
        $plan['integrations']['phone_enabled'] = !empty($phone);
        $plan['integrations']['phone_number'] = !empty($phone) ? (string) $phone : '';
        $plan['integrations']['email_enabled'] = !empty($email);
        $plan['integrations']['email_address'] = !empty($email) ? (string) $email : '';
        $plan['integrations']['contact_form'] = isset($plan['integrations']['contact_form']) ? (bool) $plan['integrations']['contact_form'] : true;
        $plan['integrations']['seo_ready'] = isset($plan['integrations']['seo_ready']) ? (bool) $plan['integrations']['seo_ready'] : true;
        $plan['integrations']['payments_ready'] = isset($plan['integrations']['payments_ready']) ? (bool) $plan['integrations']['payments_ready'] : true;
        $plan['integrations']['media_replace_ready'] = true;

        $sections = $plan['sections'] ?? [];
        if (!is_array($sections) || empty($sections)) {
            $sections = $defaults['sections'];
        }

        $normalized_sections = [];
        foreach ($sections as $section) {
            if (!is_array($section)) {
                continue;
            }
            $normalized_sections[] = [
                'type' => sanitize_key($section['type'] ?? 'section'),
                'headline' => sanitize_text_field($section['headline'] ?? ''),
                'subheadline' => sanitize_text_field($section['subheadline'] ?? ''),
                'body' => wp_kses_post($section['body'] ?? ''),
                'cta_primary' => sanitize_text_field($section['cta_primary'] ?? ''),
                'cta_secondary' => sanitize_text_field($section['cta_secondary'] ?? ''),
                'media' => sanitize_key($section['media'] ?? ''),
                'bullets' => array_values(array_filter(array_map('sanitize_text_field', is_array($section['bullets'] ?? null) ? $section['bullets'] : [])))
            ];
        }

        if (empty($normalized_sections)) {
            $normalized_sections = $defaults['sections'];
        }

        $plan['sections'] = array_slice($normalized_sections, 0, 10);
        $plan['source'] = 'openai_strategy_to_grapes';
        $plan['generated_at'] = current_time('mysql');

        return $plan;
    }
}

// Content Generation Agent
// NOTE: Primary handler lives in includes/generate-content.php (loaded first, returns grapes_plan).
// This duplicate registration is disabled to prevent race conditions.
// add_action('wp_ajax_vmo_generate_content', 'vmo_handle_content_generation');
function vmo_handle_content_generation() {
    $security = sanitize_text_field(wp_unslash($_POST['security'] ?? ''));
    if (!wp_verify_nonce($security, 'vmo_builder_nonce')) {
        wp_send_json_error(array('message' => 'Security check failed'));
        return;
    }
    
    if (!is_user_logged_in()) {
        wp_send_json_error(array('message' => 'User not logged in'));
        return;
    }
    
    $agent_type = sanitize_text_field(wp_unslash($_POST['agent_type'] ?? 'website_builder'));
    $user_id = get_current_user_id();

    $openai_key = trim((string) get_option('vmo_openai_api_key', ''));
    if ($openai_key === '') {
        wp_send_json_error(array('message' => 'OpenAI API key not configured'));
        return;
    }

    $grapes_key = trim((string) get_option('vmo_grapesjs_api_key', ''));
    if ($grapes_key === '') {
        wp_send_json_error(array('message' => 'GrapesJS API key is required. Please add it in Settings before generating websites.'));
        return;
    }

    if (!function_exists('vmo_check_token_balance')) {
        $token_manager = VMO_AI_BUILDER_DIR . 'includes/token-manager.php';
        if (file_exists($token_manager)) {
            require_once $token_manager;
        }
    }

    if (!function_exists('vmo_check_token_balance') || !function_exists('vmo_get_token_balance')) {
        wp_send_json_error(array('message' => 'Token system not available'));
        return;
    }

    $current_tokens = vmo_get_token_balance($user_id);
    if (!vmo_check_token_balance($user_id, 'content')) {
        $required_tokens = function_exists('vmo_get_token_cost') ? vmo_get_token_cost('content') : 2500;
        wp_send_json_error(array(
            'message' => 'Insufficient tokens. You need ' . $required_tokens . ' tokens to generate content. Current balance: ' . $current_tokens,
            'code' => 'insufficient_tokens',
            'current_balance' => $current_tokens,
            'required' => $required_tokens
        ));
        return;
    }

    $chat_raw = wp_unslash($_POST['chat'] ?? '[]');
    $chat = json_decode($chat_raw, true);
    if (!is_array($chat)) {
        $chat = [];
    }

    $prompt = sanitize_textarea_field(wp_unslash($_POST['prompt'] ?? ''));
    if ($prompt === '') {
        for ($index = count($chat) - 1; $index >= 0; $index--) {
            if (($chat[$index]['role'] ?? '') === 'user') {
                $prompt = sanitize_textarea_field($chat[$index]['content'] ?? '');
                if ($prompt !== '') {
                    break;
                }
            }
        }
    }

    if ($prompt === '') {
        wp_send_json_error(array('message' => 'Prompt is required'));
        return;
    }

    $consultation_profile = [];
    if (function_exists('vmo_get_consultation_profile')) {
        try {
            $consultation_profile = vmo_get_consultation_profile($user_id);
        } catch (Throwable $e) {
            $consultation_profile = [];
        }
    }

    $extracted = is_array($consultation_profile['extracted_information'] ?? null) ? $consultation_profile['extracted_information'] : [];
    $business_name = '';
    foreach (['business_name', 'businessName', 'company_name', 'companyName', 'brand_name'] as $name_key) {
        if (!empty($extracted[$name_key])) {
            $business_name = sanitize_text_field((string) $extracted[$name_key]);
            break;
        }
    }
    if ($business_name === '') {
        $business_name = sanitize_text_field((string) get_bloginfo('name'));
    }

    $phone_number = get_user_meta($user_id, 'vmo_business_phone', true);
    if (empty($phone_number)) {
        $phone_number = get_user_meta($user_id, 'vmo_phone_number', true);
    }
    if (empty($phone_number)) {
        $phone_number = get_user_meta($user_id, 'vmo_twilio_phone_number', true);
    }
    if (is_array($phone_number)) {
        $phone_number = (string) ($phone_number['number'] ?? '');
    }
    $phone_number = sanitize_text_field((string) $phone_number);

    $business_email = '';
    foreach (['businessEmail', 'business_email', 'supportEmail', 'support_email', 'email'] as $email_key) {
        if (!empty($extracted[$email_key])) {
            $business_email = sanitize_email((string) $extracted[$email_key]);
            break;
        }
    }

    $industry_insights = [];
    if (function_exists('vmo_get_cached_industry_insights')) {
        try {
            $industry_insights = vmo_get_cached_industry_insights($user_id);
        } catch (Throwable $e) {
            $industry_insights = [];
        }
    }

    $profile_snippet = !empty($consultation_profile) ? substr(wp_json_encode($consultation_profile, JSON_PRETTY_PRINT), 0, 7000) : '{}';
    $insights_snippet = !empty($industry_insights) ? substr(wp_json_encode($industry_insights, JSON_PRETTY_PRINT), 0, 3500) : '{}';

    $website_mode = (stripos($prompt, 'EXISTING CONTENT') !== false || stripos($prompt, 'refine') !== false) ? 'refinement' : 'new_generation';

    $strategy_system = "You are the VMO Website Strategy Agent working with a hidden GrapesJS frontend composer.\n"
        . "Do not return HTML. Return STRICT JSON only.\n"
        . "This JSON is consumed by GrapesJS to build the full frontend automatically.\n"
        . "Required root keys: version, site, brand, tech_stack, integrations, sections.\n"
        . "site: {business_name, tagline, goal}.\n"
        . "brand: {tone, primary_color, accent_color}.\n"
        . "tech_stack.selected: choose 3-6 applicable technologies only from this list: "
        . "HTML5, CSS3, JavaScript (ES6+), Tailwind CSS, Bootstrap 5, GSAP, AOS, Splide.js, Three.js, Framer Motion, React 18, Next.js App Router, Alpine.js, Schema.org SEO, WordPress APIs, Web Components.\n"
        . "React 18 must always be included in tech_stack.selected because generated websites are React-based.\n"
        . "integrations: include booleans and values for phone_enabled, phone_number, email_enabled, email_address, contact_form, seo_ready, payments_ready, media_replace_ready.\n"
        . "sections: array of 4-10 objects. Each section should include: type, headline, subheadline, body, cta_primary, cta_secondary, media, bullets (array).\n"
        . "\n"
        . "CONVERSION-FOCUSED DESIGN RULES (apply to every site):\n"
        . "1. Hero section MUST have a strong primary CTA above the fold. Include a clear value proposition in the headline.\n"
        . "2. Place trust signals early: testimonials, client logos, certifications, or social proof should appear within the first 3 sections.\n"
        . "3. Every section should have a clear purpose in the conversion funnel: Awareness → Interest → Desire → Action.\n"
        . "4. End with a compelling CTA section (not just contact) — restate the core offer and urgency.\n"
        . "5. Pricing sections should highlight the 'best value' tier and include a CTA button on each tier.\n"
        . "6. Use benefit-driven headlines, not feature-only. Frame around customer outcomes.\n"
        . "7. Contact section should minimize friction: short form (3-4 fields max), phone click-to-call, email mailto link.\n"
        . "8. Include social proof where possible: star ratings, review counts, client counts, years of experience.\n"
        . "9. For services/features sections, include 'outcomes' or 'results' language, not just descriptions.\n"
        . "10. cta_primary buttons should use action verbs: 'Get Started', 'Book a Call', 'Claim Your Spot', not 'Submit' or 'Learn More'.\n"
        . "\n"
        . "Preserve and honor consultation/deep-research context. If mode is refinement, keep the current site direction and only apply requested changes.";

    $messages = [
        ['role' => 'system', 'content' => $strategy_system],
        ['role' => 'system', 'content' => 'MODE: ' . $website_mode],
        ['role' => 'system', 'content' => "CONSULTATION PROFILE (authoritative):\n" . $profile_snippet],
        ['role' => 'system', 'content' => "DEEP RESEARCH INSIGHTS:\n" . $insights_snippet],
        ['role' => 'system', 'content' => 'KNOWN BUSINESS CONTACTS: ' . wp_json_encode([
            'phone_number' => $phone_number,
            'email_address' => $business_email
        ])],
        ['role' => 'user', 'content' => $prompt]
    ];

    $model = defined('VMO_OPENAI_MODEL') ? VMO_OPENAI_MODEL : 'gpt-5.2-2025-12-11';
    if (function_exists('vmo_get_optimal_model')) {
        try {
            $model = vmo_get_optimal_model('website_building');
        } catch (Throwable $e) {
        }
    }

    if (!function_exists('vmo_openai_chat')) {
        wp_send_json_error(array('message' => 'AI request helper unavailable'));
        return;
    }

    $response = vmo_openai_chat($messages, $model, [
        'temperature' => 0.35,
        'max_tokens' => 2400,
        'timeout' => 95,
    ]);

    if (is_wp_error($response)) {
        wp_send_json_error(array('message' => 'AI request failed: ' . $response->get_error_message()));
        return;
    }

    $payload = json_decode(wp_remote_retrieve_body($response), true);
    $raw_plan = trim((string)($payload['choices'][0]['message']['content'] ?? ''));

    $parsed_plan = vmo_extract_json_object_from_text($raw_plan);
    if (!is_array($parsed_plan)) {
        $parsed_plan = [];
    }

    $strategy_plan = vmo_normalize_grapes_strategy_plan($parsed_plan, $prompt, $business_name ?: 'Your Business', $phone_number, $business_email);

    if (function_exists('vmo_deduct_tokens_for_operation')) {
        if (!vmo_deduct_tokens_for_operation($user_id, 'content')) {
            wp_send_json_error(array('message' => 'Failed to deduct tokens for content generation.'));
            return;
        }
    }

    $tokens_used = function_exists('vmo_get_token_cost') ? vmo_get_token_cost('content') : 2500;
    $new_balance = function_exists('vmo_get_token_balance') ? vmo_get_token_balance($user_id) : null;

    wp_send_json_success(array(
        'grapes_plan' => $strategy_plan,
        'generation_mode' => 'grapesjs_background_composer',
        'agent_type' => $agent_type,
        'tokens_used' => $tokens_used,
        'new_balance' => $new_balance
    ));
}

// Content Refinement Agent  
add_action('wp_ajax_vmo_refine_content', 'vmo_handle_content_refinement');
function vmo_handle_content_refinement() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['security'])), 'vmo_builder_nonce')) {
        wp_send_json_error(array('message' => 'Security check failed'));
        return;
    }
    
    if (!is_user_logged_in()) {
        wp_send_json_error(array('message' => 'User not logged in'));
        return;
    }
    
    // ✅ TOKEN DEDUCTION v8.1.0: Charge for content refinement
    $user_id = get_current_user_id();
    if (function_exists('vmo_deduct_tokens')) {
        if (!vmo_deduct_tokens($user_id, 300, 'refinement', 'Content refinement')) {
            wp_send_json_error(array('message' => 'Insufficient tokens. Please top up your balance.'));
            return;
        }
    }
    
    $prompt = sanitize_textarea_field($_POST['prompt']);
    $section_index = intval($_POST['section_index']);
    $agent_type = sanitize_text_field(wp_unslash($_POST['agent_type']));
    
    if (empty($prompt)) {
        wp_send_json_error(array('message' => 'Refinement prompt is required'));
        return;
    }
    
    // Enhanced prompt for mobile-responsive refinement
    $enhanced_prompt = $prompt . "\n\nCRITICAL: Maintain mobile responsiveness in all changes. Ensure the refined content works perfectly on all screen sizes and devices.";
    
    // Get OpenAI API key
    $api_key = get_option('vmo_openai_api_key', '');
    if (empty($api_key)) {
        wp_send_json_error(array('message' => 'OpenAI API key not configured'));
        return;
    }
    
    // Rate limit AI calls
    $rate_check = vmo_check_ai_rate_limit($user_id, 'refine', 5, 30);
    if (is_wp_error($rate_check)) { wp_send_json_error(array('message' => $rate_check->get_error_message())); return; }
    
    // Call OpenAI API for refinement
    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', array(
        'timeout' => 55, // Capped: Kinsta 60s PHP limit
        'headers' => array(
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type' => 'application/json',
        ),
        'body' => json_encode(array(
            'model' => VMO_OPENAI_MODEL,
            'messages' => array(
                array(
                    'role' => 'user',
                    'content' => $enhanced_prompt
                )
            ),
            'max_completion_tokens' => 4000,
            'temperature' => 0.7,
        )),
    ));
    
    if (is_wp_error($response)) {
        wp_send_json_error(array('message' => 'Failed to connect to OpenAI API'));
        return;
    }
    
    $body = wp_remote_retrieve_body($response);
    $data = json_decode($body, true);
    
    if (isset($data['choices'][0]['message']['content'])) {
        $refined_html = $data['choices'][0]['message']['content'];
        
        wp_send_json_success(array(
            'html' => $refined_html,
            'section_index' => $section_index,
            'agent_type' => $agent_type
        ));
    } else {
        wp_send_json_error(array('message' => 'Failed to refine content'));
    }
}

// Media Integration Agent
add_action('wp_ajax_vmo_integrate_media', 'vmo_handle_media_integration');
function vmo_handle_media_integration() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['security'])), 'vmo_builder_nonce')) {
        wp_send_json_error(array('message' => 'Security check failed'));
        return;
    }
    
    if (!is_user_logged_in()) {
        wp_send_json_error(array('message' => 'User not logged in'));
        return;
    }
    
    // ✅ TOKEN DEDUCTION v8.1.0: Charge for media integration
    $user_id = get_current_user_id();
    if (function_exists('vmo_deduct_tokens')) {
        if (!vmo_deduct_tokens($user_id, 400, 'media_integration', 'Media integration')) {
            wp_send_json_error(array('message' => 'Insufficient tokens. Please top up your balance.'));
            return;
        }
    }
    
    $prompt = sanitize_textarea_field($_POST['prompt']);
    $media_url = esc_url_raw($_POST['media_url']);
    $media_type = sanitize_text_field(wp_unslash($_POST['media_type']));
    $section_index = intval($_POST['section_index']);
    $agent_type = sanitize_text_field(wp_unslash($_POST['agent_type']));
    
    if (empty($prompt) || empty($media_url)) {
        wp_send_json_error(array('message' => 'Prompt and media URL are required'));
        return;
    }
    
    // Enhanced prompt for mobile-responsive media integration
    $enhanced_prompt = $prompt . "\n\nMEDIA INTEGRATION REQUIREMENTS:\n- Make media fully responsive for all devices\n- Use proper responsive image/video techniques\n- Ensure touch-friendly interface\n- Optimize for mobile performance\n- Maintain existing content structure";
    
    // Get OpenAI API key
    $api_key = get_option('vmo_openai_api_key', '');
    if (empty($api_key)) {
        wp_send_json_error(array('message' => 'OpenAI API key not configured'));
        return;
    }
    
    // Call OpenAI API for media integration
    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', array(
        'timeout' => 60,
        'headers' => array(
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type' => 'application/json',
        ),
        'body' => json_encode(array(
            'model' => VMO_OPENAI_MODEL,
            'messages' => array(
                array(
                    'role' => 'user',
                    'content' => $enhanced_prompt
                )
            ),
            'max_completion_tokens' => 3000,
            'temperature' => 0.7,
        )),
    ));
    
    if (is_wp_error($response)) {
        wp_send_json_error(array('message' => 'Failed to connect to OpenAI API'));
        return;
    }
    
    $body = wp_remote_retrieve_body($response);
    $data = json_decode($body, true);
    
    if (isset($data['choices'][0]['message']['content'])) {
        $integrated_html = $data['choices'][0]['message']['content'];
        
        wp_send_json_success(array(
            'html' => $integrated_html,
            'media_url' => $media_url,
            'media_type' => $media_type,
            'section_index' => $section_index,
            'agent_type' => $agent_type
        ));
    } else {
        wp_send_json_error(array('message' => 'Failed to integrate media'));
    }
}

/* --------------------------------------------------------------
   AI-DRIVEN DASHBOARD BACKEND FUNCTIONS
---------------------------------------------------------------- */

// SEO Data Analytics Function
function vmo_get_seo_data() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if ((isset($_POST['security']) || isset($_POST['nonce'])) && !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce']);
    }

    global $wpdb;
    $user_id = get_current_user_id();
    $consultation_profile = function_exists('vmo_get_consultation_profile') ? vmo_get_consultation_profile($user_id) : [];
    $consultation_completed = !empty($consultation_profile);

    if (!$consultation_completed) {
        wp_send_json_success([
            'seo_score' => '—',
            'indexed_pages' => '—',
            'errors' => '—',
            'warnings' => '—',
            'onpage_score' => '—',
            'technical_score' => '—',
            'content_score' => '—',
            'backlink_authority' => '—',
            'domain_authority' => '—',
            'keyword_opportunity' => '—',
            'status' => 'consultation_pending',
            'message' => 'SEO analysis available after consultation completion'
        ]);
    }

    // AI-driven SEO analysis based on consultation profile and industry research
    $industry_insights = vmo_get_cached_industry_insights($user_id);
    $website_analysis = vmo_analyze_website_seo($user_id, $consultation_profile);
    
    wp_send_json_success([
        'seo_score' => $website_analysis['overall_score'] ?? 75,
        'indexed_pages' => $website_analysis['indexed_pages'] ?? 0,
        'errors' => $website_analysis['errors'] ?? 0,
        'warnings' => $website_analysis['warnings'] ?? 0,
        'onpage_score' => $website_analysis['onpage_score'] ?? 70,
        'technical_score' => $website_analysis['technical_score'] ?? 80,
        'content_score' => $website_analysis['content_score'] ?? 65,
        'backlink_authority' => $website_analysis['backlink_authority'] ?? 45,
        'domain_authority' => $website_analysis['domain_authority'] ?? 35,
        'keyword_opportunity' => $website_analysis['keyword_opportunity'] ?? 85,
        'recommendations' => $website_analysis['recommendations'] ?? [],
        'status' => 'active'
    ]);
}

// SEO Recommendations Function
function vmo_get_seo_recommendations() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    $user_id = get_current_user_id();
    $consultation_profile = function_exists('vmo_get_consultation_profile') ? vmo_get_consultation_profile($user_id) : [];
    $industry_insights = vmo_get_cached_industry_insights($user_id);
    
    $recommendations = vmo_generate_ai_seo_recommendations($user_id, $consultation_profile, $industry_insights);
    
    wp_send_json_success([
        'keywords' => $recommendations['keywords'] ?? [],
        'blog_topics' => $recommendations['blog_topics'] ?? [],
        'technical_actions' => $recommendations['technical_actions'] ?? [],
        'content_gaps' => $recommendations['content_gaps'] ?? []
    ]);
}

// Email Marketing Metrics Function
function vmo_get_email_metrics() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    global $wpdb;
    $user_id = get_current_user_id();
    $consultation_profile = function_exists('vmo_get_consultation_profile') ? vmo_get_consultation_profile($user_id) : [];
    
    if (empty($consultation_profile)) {
        wp_send_json_success([
            'emails_sent' => '—',
            'open_rate' => '—',
            'click_rate' => '—',
            'campaigns_count' => '—',
            'status' => 'consultation_pending'
        ]);
    }

    // Get email campaign data from AI-driven campaigns
    $email_table = $wpdb->prefix . 'vmo_email_campaigns';
    $has_email_table = vmo_table_exists($email_table);
    
    if (!$has_email_table) {
        // Create email campaigns table for tracking
        vmo_create_email_campaigns_table();
    }

    $from = isset($_POST['from']) ? sanitize_text_field(wp_unslash($_POST['from'])) : date('Y-m-d', strtotime('-30 days'));
    $to = isset($_POST['to']) ? sanitize_text_field(wp_unslash($_POST['to'])) : date('Y-m-d');

    $campaign_stats = vmo_get_email_campaign_stats($user_id, $from, $to);
    
    wp_send_json_success([
        'emails_sent' => $campaign_stats['sent'] ?? 0,
        'open_rate' => $campaign_stats['open_rate'] ?? 0,
        'click_rate' => $campaign_stats['click_rate'] ?? 0,
        'campaigns_count' => $campaign_stats['campaigns'] ?? 0,
        'revenue_generated' => $campaign_stats['revenue'] ?? 0,
        'chart_data' => $campaign_stats['chart'] ?? [],
        'status' => 'active'
    ]);
}

// Email Campaigns List Function
function vmo_get_email_campaigns() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    $user_id = get_current_user_id();
    $campaigns = vmo_get_user_email_campaigns($user_id);
    
    wp_send_json_success([
        'campaigns' => $campaigns,
        'total' => count($campaigns)
    ]);
}

// ========================================
// EMAIL CREDENTIALS MANAGEMENT (User-Provided IMAP/SMTP)
// Replaces legacy auto-provisioning with user-provided email accounts
// ========================================

/**
 * Save email credentials securely (IMAP/SMTP user-provided accounts)
 * Encrypts password before storage
 */
function vmo_save_email_credentials() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    // Verify nonce
    $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
    if (!wp_verify_nonce($nonce, 'vmo_nonce')) {
        wp_send_json_error(['message' => 'Invalid security token'], 403);
    }
    
    $user_id = get_current_user_id();
    $session_id = sanitize_text_field($_POST['session_id'] ?? '');
    $category = sanitize_text_field($_POST['category'] ?? '');
    $email = sanitize_email($_POST['email'] ?? '');
    $password = wp_unslash($_POST['password'] ?? ''); // wp_unslash only — sanitize_text_field strips valid password chars
    $display_name = sanitize_text_field($_POST['display_name'] ?? '');
    $provider_preset = sanitize_text_field($_POST['provider_preset'] ?? 'custom');
    $imap_host = sanitize_text_field($_POST['imap_host'] ?? '');
    $imap_port = absint($_POST['imap_port'] ?? 993);
    $smtp_host = sanitize_text_field($_POST['smtp_host'] ?? '');
    $smtp_port = absint($_POST['smtp_port'] ?? 587);
    
    // Validate required fields
    if (empty($category) || empty($email) || empty($password)) {
        wp_send_json_error(['message' => 'Missing required fields'], 400);
    }
    
    // Validate email format
    if (!is_email($email)) {
        wp_send_json_error(['message' => 'Invalid email format'], 400);
    }
    
    // Enforce domain validation - email must match the enforced domain
    $enforced_domain = vmo_get_enforced_domain();
    $email_domain = substr(strrchr($email, "@"), 1);
    
    // Check for protected/government domains
    $protected_domains = ['gov', 'mil', 'edu', 'fbi.gov', 'cia.gov', 'state.gov', 'whitehouse.gov'];
    foreach ($protected_domains as $pd) {
        if (str_ends_with($email_domain, $pd) || $email_domain === $pd) {
            wp_send_json_error(['message' => 'Protected/government domain - cannot be used'], 400);
        }
    }
    
    if ($enforced_domain && $email_domain !== $enforced_domain && !str_ends_with($email_domain, '.' . $enforced_domain)) {
        wp_send_json_error(['message' => "Email must be @{$enforced_domain}"], 400);
    }
    
    // Encrypt password before storage
    $encrypted_password = vmo_encrypt_credential($password);
    if (!$encrypted_password) {
        wp_send_json_error(['message' => 'Failed to secure credentials'], 500);
    }
    
    global $wpdb;
    $table_name = $wpdb->prefix . 'vmo_email_accounts';
    
    // Ensure table exists
    vmo_ensure_email_accounts_table();
    
    // Check if account already exists for this category
    $existing = $wpdb->get_row($wpdb->prepare(
        "SELECT id FROM {$table_name} WHERE user_id = %d AND category = %s",
        $user_id, $category
    ));
    
    $account_data = [
        'user_id' => $user_id,
        'session_id' => $session_id,
        'category' => $category,
        'email' => $email,
        'display_name' => $display_name,
        'provider_preset' => $provider_preset,
        'imap_host' => $imap_host,
        'imap_port' => $imap_port,
        'smtp_host' => $smtp_host,
        'smtp_port' => $smtp_port,
        'encrypted_password' => $encrypted_password,
        'verified' => 0,
        'updated_at' => current_time('mysql')
    ];
    
    if ($existing) {
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->update($table_name, $account_data, ['id' => $existing->id]);
    } else {
        $account_data['created_at'] = current_time('mysql');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->insert($table_name, $account_data);
    }
    
    // ✅ SYNC v9.0.2: Also sync to email_sender_identities for dashboard integration
    vmo_sync_email_to_sender_identities($user_id, $email, $category, $display_name);
    
    wp_send_json_success([
        'message' => 'Email account saved successfully',
        'category' => $category,
        'email' => $email
    ]);
}

/**
 * Novice-friendly flow: test IMAP/SMTP reachability first, then save credentials.
 * - If IMAP extension exists, requires successful IMAP auth before saving.
 * - If IMAP extension is unavailable, saves but marks verification as pending.
 */
function vmo_test_and_save_email_credentials() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
    if (!wp_verify_nonce($nonce, 'vmo_nonce')) {
        wp_send_json_error(['message' => 'Invalid security token'], 403);
    }

    $user_id = get_current_user_id();
    $session_id = sanitize_text_field($_POST['session_id'] ?? '');
    $category = sanitize_text_field($_POST['category'] ?? '');
    $email = sanitize_email($_POST['email'] ?? '');
    $password = wp_unslash($_POST['password'] ?? ''); // wp_unslash only — sanitize_text_field strips valid password chars
    $display_name = sanitize_text_field($_POST['display_name'] ?? '');
    $provider_preset = sanitize_text_field($_POST['provider_preset'] ?? 'custom');
    $imap_host = sanitize_text_field($_POST['imap_host'] ?? '');
    $imap_port = absint($_POST['imap_port'] ?? 993);
    $smtp_host = sanitize_text_field($_POST['smtp_host'] ?? '');
    $smtp_port = absint($_POST['smtp_port'] ?? 587);

    if (empty($category) || empty($email) || empty($password)) {
        wp_send_json_error(['message' => 'Missing required fields'], 400);
    }
    if (empty($imap_host) || empty($smtp_host)) {
        wp_send_json_error(['message' => 'Missing mail server settings. Please choose a provider preset or enter advanced settings.'], 400);
    }
    if (!is_email($email)) {
        wp_send_json_error(['message' => 'Invalid email format'], 400);
    }

    $enforced_domain = vmo_get_enforced_domain();
    $email_domain = substr(strrchr($email, "@"), 1);

    $protected_domains = ['gov', 'mil', 'edu', 'fbi.gov', 'cia.gov', 'state.gov', 'whitehouse.gov'];
    foreach ($protected_domains as $pd) {
        if (str_ends_with($email_domain, $pd) || $email_domain === $pd) {
            wp_send_json_error(['message' => 'Protected/government domain - cannot be used'], 400);
        }
    }

    if ($enforced_domain && $email_domain !== $enforced_domain && !str_ends_with($email_domain, '.' . $enforced_domain)) {
        wp_send_json_error(['message' => "Email must be @{$enforced_domain}"], 400);
    }

    // Basic SMTP reachability check (not authentication)
    $smtp_ok = false;
    $smtp_error = '';
    if (!empty($smtp_host) && !empty($smtp_port)) {
        $timeout = 5;
        $errno = 0;
        $errstr = '';
        $socket = @fsockopen($smtp_host, $smtp_port, $errno, $errstr, $timeout);
        if ($socket) {
            $smtp_ok = true;
            fclose($socket);
        } else {
            $smtp_error = $errstr ?: 'Unable to reach SMTP server.';
        }
    }

    // IMAP authentication check when possible
    $imap_ok = false;
    $imap_pending = false;
    $imap_error = '';
    if (function_exists('imap_open')) {
        $mailbox_base = '{' . $imap_host . ':' . $imap_port . '/imap';
        $candidates = [];
        if ($imap_port === 993) {
            $candidates[] = $mailbox_base . '/ssl}INBOX';
            $candidates[] = $mailbox_base . '/ssl/novalidate-cert}INBOX';
        } else {
            $candidates[] = $mailbox_base . '/tls}INBOX';
            $candidates[] = $mailbox_base . '/notls}INBOX';
            $candidates[] = $mailbox_base . '/novalidate-cert}INBOX';
        }

        foreach ($candidates as $mailbox) {
            $connection = @imap_open($mailbox, $email, $password, OP_READONLY, 1);
            if ($connection) {
                imap_close($connection);
                $imap_ok = true;
                break;
            }
            $imap_error = imap_last_error() ?: $imap_error;
        }

        if (!$imap_ok) {
            wp_send_json_error([
                'message' => 'Could not sign in to IMAP with those credentials. ' . ($imap_error ? ('(' . $imap_error . ')') : 'Please double-check your email/password.'),
                'needs_advanced' => true,
                'smtp_reachable' => $smtp_ok,
                'smtp_error' => $smtp_error,
            ], 400);
        }
    } else {
        $imap_pending = true;
    }

    if (!$smtp_ok) {
        wp_send_json_error([
            'message' => 'Could not reach the SMTP server at the provided address/port. ' . ($smtp_error ? ('(' . $smtp_error . ')') : ''),
            'needs_advanced' => true,
            'smtp_reachable' => false,
            'smtp_error' => $smtp_error,
        ], 400);
    }

    $encrypted_password = vmo_encrypt_credential($password);
    if (!$encrypted_password) {
        wp_send_json_error(['message' => 'Failed to secure credentials'], 500);
    }

    global $wpdb;
    $table_name = $wpdb->prefix . 'vmo_email_accounts';
    vmo_ensure_email_accounts_table();

    $existing = $wpdb->get_row($wpdb->prepare(
        "SELECT id FROM {$table_name} WHERE user_id = %d AND category = %s",
        $user_id, $category
    ));

    $account_data = [
        'user_id' => $user_id,
        'session_id' => $session_id,
        'category' => $category,
        'email' => $email,
        'display_name' => $display_name,
        'provider_preset' => $provider_preset,
        'imap_host' => $imap_host,
        'imap_port' => $imap_port,
        'smtp_host' => $smtp_host,
        'smtp_port' => $smtp_port,
        'encrypted_password' => $encrypted_password,
        'verified' => $imap_pending ? 0 : 1,
        'updated_at' => current_time('mysql')
    ];

    if ($existing) {
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->update($table_name, $account_data, ['id' => $existing->id]);
    } else {
        $account_data['created_at'] = current_time('mysql');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->insert($table_name, $account_data);
    }

    vmo_sync_email_to_sender_identities($user_id, $email, $category, $display_name);

    wp_send_json_success([
        'message' => $imap_pending ? 'Email account saved. Automatic verification is not available on this server.' : 'Email account verified and saved successfully.',
        'category' => $category,
        'email' => $email,
        'verified' => $imap_pending ? 'pending' : true,
    ]);
}

/**
 * ✅ NEW v9.0.2: Sync consultation email accounts to sender identities for dashboard
 */
function vmo_sync_email_to_sender_identities($user_id, $email, $category, $display_name = '') {
    global $wpdb;
    $identities_table = $wpdb->prefix . 'vmo_email_sender_identities';
    
    // Check if identities table exists
    if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $identities_table)) !== $identities_table) {
        // Create the table if it doesn't exist
        vmo_ensure_sender_identities_table();
    }
    
    // Map consultation categories to email roles
    $role_map = [
        'info' => 'info',
        'support' => 'support',
        'sales' => 'sales',
        'marketing' => 'marketing',
        'billing' => 'billing',
        'noreply' => 'noreply',
        'personal' => 'primary',
        'primary' => 'primary'
    ];
    $email_role = $role_map[$category] ?? $category;
    
    // Check if email already exists for this user
    $existing = $wpdb->get_row($wpdb->prepare(
        "SELECT id FROM {$identities_table} WHERE user_id = %d AND email_address = %s",
        $user_id, $email
    ));
    
    $identity_data = [
        'user_id' => $user_id,
        'email_address' => $email,
        'email_role' => $email_role,
        'display_name' => $display_name ?: '',
        'verification_status' => 'pending',
        'is_primary' => ($category === 'primary' || $category === 'personal') ? 1 : 0,
        'updated_at' => current_time('mysql')
    ];
    
    if ($existing) {
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->update($identities_table, $identity_data, ['id' => $existing->id]);
    } else {
        $identity_data['created_at'] = current_time('mysql');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->insert($identities_table, $identity_data);
    }
}

/**
 * ✅ NEW v9.0.2: Ensure sender identities table exists
 */
function vmo_ensure_sender_identities_table() {
    global $wpdb;
    $table_name = $wpdb->prefix . 'vmo_email_sender_identities';
    $charset_collate = $wpdb->get_charset_collate();
    
    $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        user_id BIGINT(20) UNSIGNED NOT NULL,
        email_address VARCHAR(255) NOT NULL,
        email_role VARCHAR(50) DEFAULT 'primary',
        display_name VARCHAR(255) DEFAULT '',
        verification_status VARCHAR(50) DEFAULT 'pending',
        is_primary TINYINT(1) DEFAULT 0,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY user_id (user_id),
        KEY email_address (email_address),
        UNIQUE KEY user_email (user_id, email_address)
    ) {$charset_collate};";
    
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
}

/**
 * Verify email credentials by testing IMAP connection
 */
function vmo_verify_email_credentials() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    $user_id = get_current_user_id();
    $category = sanitize_text_field($_POST['category'] ?? '');
    
    global $wpdb;
    $table_name = $wpdb->prefix . 'vmo_email_accounts';
    
    $account = $wpdb->get_row($wpdb->prepare(
        "SELECT * FROM {$table_name} WHERE user_id = %d AND category = %s",
        $user_id, $category
    ));
    
    if (!$account) {
        wp_send_json_error(['message' => 'Account not found'], 404);
    }
    
    // Decrypt password
    $password = vmo_decrypt_credential($account->encrypted_password);
    if (!$password) {
        wp_send_json_error(['message' => 'Failed to decrypt credentials'], 500);
    }
    
    // Test IMAP connection
    if (function_exists('imap_open')) {
        $mailbox = '{' . $account->imap_host . ':' . $account->imap_port . '/imap/ssl}INBOX';
        $connection = @imap_open($mailbox, $account->email, $password, OP_READONLY, 1);
        
        if ($connection) {
            imap_close($connection);
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->update($table_name, ['verified' => 1], ['id' => $account->id]);
            wp_send_json_success(['message' => 'Email account verified successfully', 'verified' => true]);
        } else {
            $error = imap_last_error();
            wp_send_json_error(['message' => 'Connection failed: ' . $error, 'verified' => false], 400);
        }
    } else {
        // IMAP extension not available, mark as pending verification
        wp_send_json_success(['message' => 'IMAP verification not available on server', 'verified' => 'pending']);
    }
}

/**
 * Get all email accounts for current user
 */
function vmo_get_email_accounts() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    $user_id = get_current_user_id();
    
    global $wpdb;
    $table_name = $wpdb->prefix . 'vmo_email_accounts';
    
    // Return empty if table doesn't exist yet
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") !== $table_name) {
        wp_send_json_success(['accounts' => [], 'total' => 0]);
    }
    
    $accounts = $wpdb->get_results($wpdb->prepare(
        "SELECT id, category, email, display_name, provider_preset, imap_host, smtp_host, verified, created_at 
         FROM {$table_name} WHERE user_id = %d ORDER BY created_at ASC",
        $user_id
    ));
    
    wp_send_json_success([
        'accounts' => $accounts,
        'total' => count($accounts),
        'enforced_domain' => vmo_get_enforced_domain()
    ]);
}

/**
 * Delete an email account
 */
function vmo_delete_email_account() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    $user_id = get_current_user_id();
    $category = sanitize_text_field($_POST['category'] ?? '');
    
    global $wpdb;
    $table_name = $wpdb->prefix . 'vmo_email_accounts';
    
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $deleted = $wpdb->delete($table_name, [
        'user_id' => $user_id,
        'category' => $category
    ]);
    
    if ($deleted) {
        wp_send_json_success(['message' => 'Email account deleted']);
    } else {
        wp_send_json_error(['message' => 'Failed to delete account'], 400);
    }
}

/**
 * Save bulk email provider configuration (Brevo, MailerLite)
 */
function vmo_save_bulk_email_provider() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    $user_id = get_current_user_id();
    $provider = sanitize_text_field($_POST['provider'] ?? '');
    $api_key = sanitize_text_field($_POST['api_key'] ?? '');
    
    if (empty($provider) || empty($api_key)) {
        wp_send_json_error(['message' => 'Missing provider or API key'], 400);
    }
    
    $valid_providers = ['brevo', 'mailerlite'];
    if (!in_array($provider, $valid_providers)) {
        wp_send_json_error(['message' => 'Invalid provider'], 400);
    }
    
    // Encrypt API key
    $encrypted_key = vmo_encrypt_credential($api_key);
    
    // Store in user meta
    update_user_meta($user_id, 'vmo_bulk_email_provider', $provider);
    update_user_meta($user_id, 'vmo_bulk_email_api_key', $encrypted_key);
    update_user_meta($user_id, 'vmo_bulk_email_verified', 0);
    
    wp_send_json_success(['message' => 'Bulk email provider saved', 'provider' => $provider]);
}

/**
 * Verify bulk email provider API key
 */
function vmo_verify_bulk_email_provider() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    $user_id = get_current_user_id();
    $provider = get_user_meta($user_id, 'vmo_bulk_email_provider', true);
    $encrypted_key = get_user_meta($user_id, 'vmo_bulk_email_api_key', true);
    
    if (empty($provider) || empty($encrypted_key)) {
        wp_send_json_error(['message' => 'No bulk email provider configured'], 400);
    }
    
    $api_key = vmo_decrypt_credential($encrypted_key);
    
    $verified = false;
    $error_message = '';
    
    switch ($provider) {
        case 'brevo':
            // Test Brevo API
            $response = wp_remote_get('https://api.brevo.com/v3/account', [
                'timeout' => 10,
                'headers' => ['api-key' => $api_key]
            ]);
            if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
                $verified = true;
            } else {
                $error_message = is_wp_error($response) ? $response->get_error_message() : 'Invalid API key';
            }
            break;
            
        case 'mailerlite':
            // Test MailerLite API
            $response = wp_remote_get('https://connect.mailerlite.com/api/subscribers?limit=1', [
                'timeout' => 10,
                'headers' => ['Authorization' => 'Bearer ' . $api_key]
            ]);
            if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
                $verified = true;
            } else {
                $error_message = is_wp_error($response) ? $response->get_error_message() : 'Invalid API key';
            }
            break;
    }
    
    if ($verified) {
        update_user_meta($user_id, 'vmo_bulk_email_verified', 1);
        wp_send_json_success(['message' => 'API key verified', 'verified' => true, 'provider' => $provider]);
    } else {
        wp_send_json_error(['message' => $error_message, 'verified' => false], 400);
    }
}

/**
 * Get enforced domain for this WordPress installation
 */
function vmo_get_enforced_domain() {
    $site_url = get_site_url();
    $host = wp_parse_url($site_url, PHP_URL_HOST);
    $host = strtolower(preg_replace('/^www\./', '', $host ?? ''));
    
    // Detect staging/dev environments
    $staging_patterns = [
        'kinsta.cloud', 'wpengine.com', 'flywheel.com', 'pantheonsite.io',
        'azurewebsites.net', 'herokuapp.com', 'vercel.app', 'netlify.app',
        'ngrok.io', 'localhost', '127.0.0.1', 'test', 'staging', 'dev'
    ];
    
    foreach ($staging_patterns as $pattern) {
        if (stripos($host, $pattern) !== false) {
            return null; // Staging environment, domain enforcement relaxed
        }
    }
    
    return $host;
}

/**
 * Encrypt credential for secure storage
 */
function vmo_encrypt_credential($plaintext) {
    if (empty($plaintext)) return '';
    
    $key = wp_salt('auth');
    $method = 'aes-256-cbc';
    $iv_length = openssl_cipher_iv_length($method);
    $iv = openssl_random_pseudo_bytes($iv_length);
    
    $encrypted = openssl_encrypt($plaintext, $method, $key, 0, $iv);
    if ($encrypted === false) return '';
    
    return base64_encode($iv . $encrypted); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- encryption/decryption
}

/**
 * Decrypt credential from secure storage
 */
function vmo_decrypt_credential($encrypted_string) {
    if (empty($encrypted_string)) return '';
    
    $key = wp_salt('auth');
    $method = 'aes-256-cbc';
    $iv_length = openssl_cipher_iv_length($method);
    
    $decoded = base64_decode($encrypted_string); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- encryption/decryption
    if ($decoded === false) return '';
    
    $iv = substr($decoded, 0, $iv_length);
    $encrypted = substr($decoded, $iv_length);
    
    $decrypted = openssl_decrypt($encrypted, $method, $key, 0, $iv);
    return $decrypted !== false ? $decrypted : '';
}

/**
 * Ensure email accounts table exists
 */
function vmo_ensure_email_accounts_table() {
    global $wpdb;
    $table_name = $wpdb->prefix . 'vmo_email_accounts';
    $charset_collate = $wpdb->get_charset_collate();
    
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") === $table_name) {
        return; // Table already exists
    }
    
    $sql = "CREATE TABLE {$table_name} (
        id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        user_id bigint(20) UNSIGNED NOT NULL,
        session_id varchar(100) DEFAULT '',
        category varchar(50) NOT NULL,
        email varchar(255) NOT NULL,
        display_name varchar(255) DEFAULT '',
        provider_preset varchar(50) DEFAULT 'custom',
        imap_host varchar(255) DEFAULT '',
        imap_port int(5) DEFAULT 993,
        smtp_host varchar(255) DEFAULT '',
        smtp_port int(5) DEFAULT 587,
        encrypted_password text NOT NULL,
        verified tinyint(1) DEFAULT 0,
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        updated_at datetime DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY user_id (user_id),
        KEY category (category),
        UNIQUE KEY user_category (user_id, category)
    ) {$charset_collate};";
    
    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta($sql);
}

// Payment/Revenue Data Function
function vmo_get_payment_data() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    global $wpdb;
    $user_id = get_current_user_id();
    $consultation_profile = function_exists('vmo_get_consultation_profile') ? vmo_get_consultation_profile($user_id) : [];
    
    if (empty($consultation_profile)) {
        wp_send_json_success([
            'day_revenue' => '—',
            'week_revenue' => '—',
            'month_revenue' => '—',
            'year_revenue' => '—',
            'mom_change' => '—',
            'status' => 'consultation_pending'
        ]);
    }

    // Get revenue data from AI-managed store/services
    $revenue_data = vmo_calculate_revenue_metrics($user_id);
    
    wp_send_json_success([
        'day_revenue' => vmo_format_currency($revenue_data['today'] ?? 0),
        'week_revenue' => vmo_format_currency($revenue_data['week'] ?? 0),
        'month_revenue' => vmo_format_currency($revenue_data['month'] ?? 0),
        'year_revenue' => vmo_format_currency($revenue_data['year'] ?? 0),
        'mom_change' => ($revenue_data['mom_change'] ?? 0) . '%',
        'chart_data' => $revenue_data['chart'] ?? [],
        'transactions' => $revenue_data['transactions'] ?? [],
        'status' => 'active'
    ]);
}

// Revenue Analytics Function
function vmo_get_revenue_analytics() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    $user_id = get_current_user_id();
    $period = sanitize_text_field($_POST['period'] ?? 'month');
    
    $analytics = vmo_generate_revenue_analytics($user_id, $period);
    
    wp_send_json_success([
        'analytics' => $analytics,
        'period' => $period
    ]);
}

// CRM Data Function
function vmo_get_crm_data() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    global $wpdb;
    $user_id = get_current_user_id();
    $consultation_profile = function_exists('vmo_get_consultation_profile') ? vmo_get_consultation_profile($user_id) : [];
    
    if (empty($consultation_profile)) {
        wp_send_json_success([
            'total_contacts' => '—',
            'leads_count' => '—',
            'conversion_rate' => '—',
            'status' => 'consultation_pending'
        ]);
    }

    // Get CRM data from AI-managed lead system
    $crm_data = vmo_get_crm_analytics($user_id);
    
    wp_send_json_success([
        'total_contacts' => $crm_data['contacts'] ?? 0,
        'leads_count' => $crm_data['leads'] ?? 0,
        'conversion_rate' => ($crm_data['conversion_rate'] ?? 0) . '%',
        'pipeline_value' => vmo_format_currency($crm_data['pipeline_value'] ?? 0),
        'recent_activities' => $crm_data['activities'] ?? [],
        'lead_sources' => $crm_data['sources'] ?? [],
        'status' => 'active'
    ]);
}

// Leads Data Function
function vmo_get_leads_data() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    $user_id = get_current_user_id();
    $search = sanitize_text_field($_POST['search'] ?? '');
    $offset = (int) wp_unslash($_POST['offset'] ?? 0);
    $limit = (int) wp_unslash($_POST['limit'] ?? 20);
    
    $leads = vmo_get_user_leads($user_id, $search, $offset, $limit);
    
    wp_send_json_success([
        'leads' => $leads['data'] ?? [],
        'total' => $leads['total'] ?? 0,
        'has_more' => ($offset + $limit) < ($leads['total'] ?? 0)
    ]);
}

// AI Recommendations Function
function vmo_get_ai_recommendations() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    $user_id = get_current_user_id();
    $consultation_profile = function_exists('vmo_get_consultation_profile') ? vmo_get_consultation_profile($user_id) : [];
    $industry_insights = vmo_get_cached_industry_insights($user_id);
    
    // Generate AI-driven recommendations based on weekly research and roadmap
    $recommendations = vmo_generate_ai_recommendations($user_id, $consultation_profile, $industry_insights);
    
    wp_send_json_success([
        'recommendations' => $recommendations['actions'] ?? [],
        'priority_score' => $recommendations['priority'] ?? 0,
        'estimated_impact' => $recommendations['impact'] ?? 'medium',
        'implementation_time' => $recommendations['time_estimate'] ?? '1-2 weeks',
        'requires_approval' => true
    ]);
}

// AI Action Approval Function
function vmo_approve_ai_action() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    $action_id = sanitize_text_field($_POST['action_id'] ?? '');
    $approval_status = sanitize_text_field($_POST['status'] ?? ''); // 'approved', 'rejected', 'modified'
    
    if (empty($action_id)) {
        wp_send_json_error(['message' => 'Action ID required']);
    }

    $user_id = get_current_user_id();
    $result = vmo_process_ai_action_approval($user_id, $action_id, $approval_status);
    
    if ($result['success']) {
        wp_send_json_success([
            'message' => 'Action ' . $approval_status,
            'next_steps' => $result['next_steps'] ?? [],
            'tracking_id' => $result['tracking_id'] ?? null
        ]);
    } else {
        wp_send_json_error(['message' => $result['error'] ?? 'Failed to process approval']);
    }
}

// Industry Insights Function
function vmo_get_industry_insights() {
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? $_REQUEST['security'] ?? '')), 'vmo_nonce')) { wp_send_json_error(['message' => 'Security check failed'], 403); }
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    $user_id = get_current_user_id();
    $insights = vmo_get_cached_industry_insights($user_id);
    
    wp_send_json_success([
        'insights' => $insights['data'] ?? [],
        'last_scan' => $insights['last_scan'] ?? null,
        'next_scan' => $insights['next_scan'] ?? null,
        'industry' => $insights['industry'] ?? 'General',
        'competitors_monitored' => $insights['competitors_count'] ?? 0
    ]);
}

// Detailed Analytics Function
function vmo_get_detailed_analytics() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    $user_id = get_current_user_id();
    $type = sanitize_text_field($_POST['type'] ?? 'referrers'); // referrers, activities, pages
    $period = sanitize_text_field($_POST['period'] ?? 'week');
    
    $analytics = vmo_get_detailed_user_analytics($user_id, $type, $period);
    
    wp_send_json_success([
        'type' => $type,
        'data' => $analytics['data'] ?? [],
        'total' => $analytics['total'] ?? 0,
        'period' => $period
    ]);
}

// Geographical Data Function  
function vmo_get_geographical_data() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    $user_id = get_current_user_id();
    $type = sanitize_text_field($_POST['type'] ?? 'countries'); // countries, states
    
    $geo_data = vmo_get_visitor_geography($user_id, $type);
    
    wp_send_json_success([
        'type' => $type,
        'data' => $geo_data['data'] ?? [],
        'top_locations' => $geo_data['top'] ?? []
    ]);
}

/* --------------------------------------------------------------
   AI-DRIVEN DATA ANALYSIS HELPER FUNCTIONS
---------------------------------------------------------------- */

// Get cached industry insights from weekly research scans
function vmo_get_cached_industry_insights($user_id) {
    $cache_key = 'vmo_industry_insights_' . $user_id;
    $cached = get_transient($cache_key);
    
    if ($cached !== false) {
        return $cached;
    }
    
    // Generate new insights based on consultation profile
    $consultation_profile = function_exists('vmo_get_consultation_profile') ? vmo_get_consultation_profile($user_id) : [];
    
    if (empty($consultation_profile)) {
        return ['data' => [], 'industry' => 'General'];
    }
    
    // AI-driven industry research and competitive analysis
    $insights = [
        'data' => [
            'market_trends' => vmo_analyze_market_trends($consultation_profile['industry'] ?? 'General'),
            'competitor_moves' => vmo_track_competitor_activities($user_id),
            'opportunity_gaps' => vmo_identify_opportunities($consultation_profile),
            'risk_factors' => vmo_assess_market_risks($consultation_profile['industry'] ?? 'General')
        ],
        'industry' => $consultation_profile['industry'] ?? 'General',
        'last_scan' => current_time('mysql'),
        'next_scan' => date('Y-m-d H:i:s', strtotime('+7 days')),
        'competitors_count' => vmo_count_monitored_competitors($user_id)
    ];
    
    // Cache for 24 hours
    set_transient($cache_key, $insights, DAY_IN_SECONDS);
    
    return $insights;
}

// Analyze website SEO based on actual site data and consultation profile
function vmo_analyze_website_seo($user_id, $consultation_profile) {
    global $wpdb;
    
    $analysis = [
        'overall_score' => 0,
        'indexed_pages' => 0,
        'errors' => 0,
        'warnings' => 0,
        'onpage_score' => 0,
        'technical_score' => 0,
        'content_score' => 0,
        'backlink_authority' => 0,
        'domain_authority' => 0,
        'keyword_opportunity' => 0,
        'recommendations' => []
    ];
    
    $industry = $consultation_profile['industry'] ?? 'General';
    $target_keywords = $consultation_profile['target_keywords'] ?? [];
    
    // Count real published content
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $pages_count = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_status='publish' AND post_type IN ('post','page')");
    $analysis['indexed_pages'] = max(1, $pages_count);

    // ── On-page score: check for common SEO elements in published posts ──
    $onpage = 30; // base
    // Check if posts have meta descriptions (via popular SEO plugins or post_meta)
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $has_meta = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID WHERE p.post_status='publish' AND pm.meta_key IN ('_yoast_wpseo_metadesc','_aioseo_description','_rank_math_description','_vmo_seo_description') AND pm.meta_value != '' LIMIT 1");
    if ($has_meta) $onpage += 15;
    // Check if pages have featured images
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $has_thumbs = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID WHERE p.post_status='publish' AND pm.meta_key = '_thumbnail_id' LIMIT 1");
    if ($has_thumbs) $onpage += 10;
    // Bonus for having enough content
    if ($pages_count >= 5) $onpage += 10;
    if ($pages_count >= 15) $onpage += 10;
    // Bonus for keywords configured
    if (count($target_keywords) >= 3) $onpage += 10;
    $analysis['onpage_score'] = min(95, $onpage);

    // ── Technical score: check site fundamentals ──
    $tech = 40; // base (WordPress provides decent technical foundation)
    // Check SSL
    if (is_ssl()) $tech += 15;
    // Check for XML sitemap (common paths)
    $sitemap_check = wp_remote_head(get_site_url() . '/sitemap.xml', ['timeout' => 3, 'sslverify' => false]);
    if (!is_wp_error($sitemap_check) && wp_remote_retrieve_response_code($sitemap_check) === 200) $tech += 10;
    // Check robots.txt
    $robots_check = wp_remote_head(get_site_url() . '/robots.txt', ['timeout' => 3, 'sslverify' => false]);
    if (!is_wp_error($robots_check) && wp_remote_retrieve_response_code($robots_check) === 200) $tech += 10;
    // WordPress + good theme = decent mobile responsiveness
    $tech += 10;
    $analysis['technical_score'] = min(95, $tech);

    // ── Content score ──
    $content = 25;
    // Average word count of published posts
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    $avg_words = (int) $wpdb->get_var("SELECT AVG(LENGTH(post_content) - LENGTH(REPLACE(post_content, ' ', '')) + 1) FROM {$wpdb->posts} WHERE post_status='publish' AND post_type IN ('post','page') AND post_content != ''");
    if ($avg_words >= 300) $content += 15;
    if ($avg_words >= 800) $content += 15;
    if ($pages_count >= 10) $content += 10;
    if (count($target_keywords) > 0) $content += 10;
    $analysis['content_score'] = min(95, $content);

    // ── Backlink / domain authority: real data would need Ahrefs/SEMrush API ──
    // Check if user has connected an SEO integration
    $has_ahrefs = !empty(get_user_meta($user_id, 'vmo_ahrefs_api_key', true));
    $has_semrush = !empty(get_user_meta($user_id, 'vmo_semrush_api_key', true));
    if ($has_ahrefs || $has_semrush) {
        // With real integrations, we'd pull actual DA/backlinks.
        // For now signal that integration is connected.
        $analysis['backlink_authority'] = 0; // Will be populated on next refresh cycle
        $analysis['domain_authority'] = 0;
    } else {
        // Without integrations, estimate conservatively from site age + content
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
        $oldest_post = $wpdb->get_var("SELECT MIN(post_date) FROM {$wpdb->posts} WHERE post_status='publish'");
        $site_age_months = $oldest_post ? max(1, (time() - strtotime($oldest_post)) / (30 * 86400)) : 0;
        $analysis['backlink_authority'] = min(40, (int) ($site_age_months * 0.5) + ($pages_count * 0.5));
        $analysis['domain_authority'] = min(45, (int) ($site_age_months * 0.5) + ($pages_count * 0.3) + 5);
    }

    // ── Keyword opportunity ──
    $analysis['keyword_opportunity'] = min(95, 50 + (count($target_keywords) * 5) + ($pages_count * 1));

    // ── Overall score (weighted average) ──
    $analysis['overall_score'] = (int) round(
        ($analysis['onpage_score'] * 0.3) +
        ($analysis['technical_score'] * 0.25) +
        ($analysis['content_score'] * 0.25) +
        ($analysis['backlink_authority'] * 0.1) +
        ($analysis['keyword_opportunity'] * 0.1)
    );

    // ── Errors & warnings ──
    $analysis['errors'] = 0;
    $analysis['warnings'] = 0;
    if (!is_ssl()) $analysis['errors']++;
    if ($pages_count < 5) $analysis['warnings']++;
    if (!$has_meta) $analysis['warnings']++;
    if ($avg_words < 300) $analysis['warnings']++;
    if (count($target_keywords) === 0) $analysis['warnings']++;

    // Generate recommendations
    $analysis['recommendations'] = vmo_generate_ai_seo_recommendations($user_id, $consultation_profile, []);
    
    return $analysis;
}

// Generate AI-driven SEO recommendations
function vmo_generate_ai_seo_recommendations($user_id, $consultation_profile, $industry_insights) {
    $recommendations = [
        'keywords' => [],
        'blog_topics' => [],
        'technical_actions' => [],
        'content_gaps' => []
    ];
    
    if (empty($consultation_profile)) {
        return $recommendations;
    }
    
    $industry = $consultation_profile['industry'] ?? 'General';
    $target_audience = $consultation_profile['target_audience'] ?? 'General';
    $business_goals = $consultation_profile['business_goals'] ?? [];
    
    // AI-generated keyword recommendations based on industry and profile
    $base_keywords = [
        'Business' => ['business solutions', 'professional services', 'business consulting'],
        'Technology' => ['tech solutions', 'software development', 'digital transformation'],
        'Healthcare' => ['healthcare services', 'medical solutions', 'patient care'],
        'Finance' => ['financial services', 'investment solutions', 'financial planning'],
        'Education' => ['educational services', 'online learning', 'skill development'],
        'Retail' => ['retail solutions', 'customer experience', 'product sales'],
    ];
    
    $industry_keywords = $base_keywords[$industry] ?? $base_keywords['Business'];
    // Assign deterministic difficulty / volume / opportunity based on keyword position
    // so values are stable across requests (no rand()).
    $kw_index = 0;
    $difficulty_steps = [35, 50, 65]; // low → high per keyword slot
    $volume_steps     = [8000, 4500, 2000]; // high-volume first
    $opportunity_steps = [90, 75, 65]; // best opportunity first
    foreach ($industry_keywords as $keyword) {
        $idx = $kw_index % 3;
        $recommendations['keywords'][] = [
            'keyword' => $keyword,
            'difficulty' => $difficulty_steps[$idx],
            'volume' => $volume_steps[$idx],
            'opportunity' => $opportunity_steps[$idx],
        ];
        $kw_index++;
    }
    
    // AI-generated blog topic recommendations
    $recommendations['blog_topics'] = [
        "How to Optimize Your {$industry} Business in 2025",
        "Top 5 Trends Shaping the {$industry} Industry",
        "Complete Guide to {$target_audience} Engagement",
        "Why {$industry} Businesses Need Digital Transformation",
        "Case Study: Successful {$industry} Growth Strategies"
    ];
    
    // Technical action recommendations
    $recommendations['technical_actions'] = [
        'Optimize page loading speed',
        'Implement schema markup',
        'Fix mobile responsiveness issues',
        'Improve internal linking structure',
        'Optimize images for SEO'
    ];
    
    // Content gap analysis
    $recommendations['content_gaps'] = [
        "Missing content for '{$target_audience}' audience segment",
        "No content targeting '{$industry}' specific pain points",
        "Lack of comparison content vs competitors",
        "Missing FAQ section for common queries",
        "No testimonials or case studies"
    ];
    
    return $recommendations;
}

// Create email campaigns tracking table
function vmo_create_email_campaigns_table() {
    global $wpdb;
    
    $table_name = $wpdb->prefix . 'vmo_email_campaigns';
    
    $charset_collate = $wpdb->get_charset_collate();
    
    $sql = "CREATE TABLE $table_name (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        user_id bigint(20) NOT NULL,
        campaign_name varchar(255) NOT NULL,
        subject varchar(255) NOT NULL,
        content longtext NOT NULL,
        recipients_count int(11) DEFAULT 0,
        sent_count int(11) DEFAULT 0,
        opened_count int(11) DEFAULT 0,
        clicked_count int(11) DEFAULT 0,
        revenue_generated decimal(10,2) DEFAULT 0,
        status varchar(20) DEFAULT 'draft',
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        sent_at datetime NULL,
        PRIMARY KEY (id),
        KEY user_id (user_id),
        KEY status (status)
    ) $charset_collate;";
    
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
}

// Get email campaign statistics
function vmo_get_email_campaign_stats($user_id, $from, $to) {
    global $wpdb;
    $table_name = $wpdb->prefix . 'vmo_email_campaigns';
    
    $stats = [
        'sent' => 0,
        'open_rate' => 0,
        'click_rate' => 0,
        'campaigns' => 0,
        'revenue' => 0,
        'chart' => []
    ];
    
    // Check if table exists
    $table_exists = vmo_table_exists($table_name);
    if (!$table_exists) {
        return $stats;
    }
    
    $campaign_data = $wpdb->get_row($wpdb->prepare("
        SELECT 
            SUM(sent_count) as total_sent,
            SUM(opened_count) as total_opened,
            SUM(clicked_count) as total_clicked,
            SUM(revenue_generated) as total_revenue,
            COUNT(*) as campaign_count
        FROM {$table_name} 
        WHERE user_id = %d AND sent_at BETWEEN %s AND %s
    ", $user_id, $from, $to));
    
    if ($campaign_data) {
        $stats['sent'] = (int) $campaign_data->total_sent;
        $stats['open_rate'] = $campaign_data->total_sent > 0 ? round(($campaign_data->total_opened / $campaign_data->total_sent) * 100, 1) : 0;
        $stats['click_rate'] = $campaign_data->total_opened > 0 ? round(($campaign_data->total_clicked / $campaign_data->total_opened) * 100, 1) : 0;
        $stats['campaigns'] = (int) $campaign_data->campaign_count;
        $stats['revenue'] = (float) $campaign_data->total_revenue;
    }
    
    // Generate chart data for the last 7 days
    for ($i = 6; $i >= 0; $i--) {
        $date = date('Y-m-d', strtotime("-{$i} days"));
        $daily_sent = $wpdb->get_var($wpdb->prepare("
            SELECT SUM(sent_count) 
            FROM {$table_name} 
            WHERE user_id = %d AND DATE(sent_at) = %s
        ", $user_id, $date));
        
        $stats['chart'][] = [
            'date' => $date,
            'sent' => (int) ($daily_sent ?? 0)
        ];
    }
    
    return $stats;
}

// Get user email campaigns
function vmo_get_user_email_campaigns($user_id, $limit = 10) {
    global $wpdb;
    $table_name = $wpdb->prefix . 'vmo_email_campaigns';
    
    $table_exists = vmo_table_exists($table_name);
    if (!$table_exists) {
        return [];
    }
    
    $campaigns = $wpdb->get_results($wpdb->prepare("
        SELECT * FROM {$table_name} 
        WHERE user_id = %d 
        ORDER BY created_at DESC 
        LIMIT %d
    ", $user_id, $limit));
    
    return $campaigns ?? [];
}

// Calculate revenue metrics
function vmo_calculate_revenue_metrics($user_id) {
    global $wpdb;
    
    $revenue_data = [
        'today' => 0,
        'week' => 0,
        'month' => 0,
        'year' => 0,
        'mom_change' => 0,
        'chart' => [],
        'transactions' => []
    ];
    
    // Check for store orders table
    $orders_table = $wpdb->prefix . 'vmo_store_orders';
    $table_exists = vmo_table_exists($orders_table);
    
    if (!$table_exists) {
        return $revenue_data;
    }
    
    $now = current_time('mysql');
    $today = date('Y-m-d');
    $week_ago = date('Y-m-d', strtotime('-7 days'));
    $month_ago = date('Y-m-d', strtotime('-30 days'));
    $year_ago = date('Y-m-d', strtotime('-365 days'));
    $last_month = date('Y-m-d', strtotime('-60 days'));
    
    // Today's revenue
    $revenue_data['today'] = (float) $wpdb->get_var($wpdb->prepare("
        SELECT SUM(amount) FROM {$orders_table} 
        WHERE user_id = %d AND status = 'paid' AND DATE(created_at) = %s
    ", $user_id, $today));
    
    // Week revenue
    $revenue_data['week'] = (float) $wpdb->get_var($wpdb->prepare("
        SELECT SUM(amount) FROM {$orders_table} 
        WHERE user_id = %d AND status = 'paid' AND created_at >= %s
    ", $user_id, $week_ago));
    
    // Month revenue
    $revenue_data['month'] = (float) $wpdb->get_var($wpdb->prepare("
        SELECT SUM(amount) FROM {$orders_table} 
        WHERE user_id = %d AND status = 'paid' AND created_at >= %s
    ", $user_id, $month_ago));
    
    // Year revenue
    $revenue_data['year'] = (float) $wpdb->get_var($wpdb->prepare("
        SELECT SUM(amount) FROM {$orders_table} 
        WHERE user_id = %d AND status = 'paid' AND created_at >= %s
    ", $user_id, $year_ago));
    
    // Calculate month-over-month change
    $last_month_revenue = (float) $wpdb->get_var($wpdb->prepare("
        SELECT SUM(amount) FROM {$orders_table} 
        WHERE user_id = %d AND status = 'paid' 
        AND created_at BETWEEN %s AND %s
    ", $user_id, $last_month, $month_ago));
    
    if ($last_month_revenue > 0) {
        $revenue_data['mom_change'] = round((($revenue_data['month'] - $last_month_revenue) / $last_month_revenue) * 100, 1);
    }
    
    // Generate chart data for last 30 days
    for ($i = 29; $i >= 0; $i--) {
        $date = date('Y-m-d', strtotime("-{$i} days"));
        $daily_revenue = $wpdb->get_var($wpdb->prepare("
            SELECT SUM(amount) FROM {$orders_table} 
            WHERE user_id = %d AND status = 'paid' AND DATE(created_at) = %s
        ", $user_id, $date));
        
        $revenue_data['chart'][] = [
            'date' => $date,
            'revenue' => (float) ($daily_revenue ?? 0)
        ];
    }
    
    // Recent transactions
    $revenue_data['transactions'] = $wpdb->get_results($wpdb->prepare("
        SELECT * FROM {$orders_table} 
        WHERE user_id = %d AND status = 'paid' 
        ORDER BY created_at DESC 
        LIMIT 10
    ", $user_id)) ?? [];
    
    return $revenue_data;
}

// Format currency helper
function vmo_format_currency($amount, $currency = 'USD') {
    return '$' . number_format((float) $amount, 2);
}

// Get CRM analytics
function vmo_get_crm_analytics($user_id) {
    global $wpdb;
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    
    $crm_data = [
        'contacts' => 0,
        'leads' => 0,
        'conversion_rate' => 0,
        'pipeline_value' => 0,
        'activities' => [],
        'sources' => []
    ];
    
    // Check if contacts table exists
    $table_exists = vmo_table_exists($contacts_table);
    if (!$table_exists) {
        return $crm_data;
    }
    
    // Get total contacts
    $crm_data['contacts'] = (int) $wpdb->get_var($wpdb->prepare("
        SELECT COUNT(*) FROM {$contacts_table} WHERE user_id = %d
    ", $user_id));
    
    // Treat any non-customer contact as a lead for dashboard rollups.
    $crm_data['leads'] = (int) $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM {$contacts_table} WHERE user_id = %d AND lifecycle_stage <> 'customer'",
        $user_id
    ));

    // Calculate conversion rate (customers / contacts)
    $converted = (int) $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM {$contacts_table} WHERE user_id = %d AND lifecycle_stage = 'customer'",
        $user_id
    ));
    
    if ($crm_data['contacts'] > 0) {
        $crm_data['conversion_rate'] = round(($converted / $crm_data['contacts']) * 100, 1);
    }
    
    // Best-effort pipeline rollup from value_potential (0-100) for non-customers.
    // This avoids querying non-existent columns like estimated_value.
    $crm_data['pipeline_value'] = (float) $wpdb->get_var($wpdb->prepare(
        "SELECT SUM(COALESCE(value_potential, 0)) FROM {$contacts_table} WHERE user_id = %d AND lifecycle_stage <> 'customer'",
        $user_id
    ));
    
    return $crm_data;
}

// Get user leads data with pagination
function vmo_get_user_leads($user_id, $search = '', $offset = 0, $limit = 20) {
    global $wpdb;
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    
    $leads_data = [
        'data' => [],
        'total' => 0
    ];
    
    // Check if contacts table exists
    $table_exists = vmo_table_exists($contacts_table);
    if (!$table_exists) {
        return $leads_data;
    }
    
    // Lead = any contact that isn't a customer.
    $where_clause = "WHERE user_id = %d AND lifecycle_stage <> 'customer'";
    $params = [$user_id];
    
    if (!empty($search)) {
        $where_clause .= " AND (COALESCE(full_name, CONCAT(COALESCE(first_name,''),' ',COALESCE(last_name,'')), '') LIKE %s OR email LIKE %s OR company LIKE %s OR notes LIKE %s)";
        $search_param = '%' . $wpdb->esc_like($search) . '%';
        $params[] = $search_param;
        $params[] = $search_param;
        $params[] = $search_param;
        $params[] = $search_param;
    }
    
    // Get total count
    $leads_data['total'] = (int) $wpdb->get_var($wpdb->prepare("
        SELECT COUNT(*) FROM {$contacts_table} {$where_clause}
    ", ...$params));
    
    // Get paginated data
    $params_with_limit = array_merge($params, [$limit, $offset]);
    $leads_data['data'] = $wpdb->get_results(
        $wpdb->prepare(
            "SELECT *
             FROM {$contacts_table} {$where_clause}
             ORDER BY updated_at DESC, created_at DESC
             LIMIT %d OFFSET %d",
            ...$params_with_limit
        ),
        ARRAY_A
    );
    
    return $leads_data;
}

// Generate AI recommendations based on industry insights and roadmaps
function vmo_generate_ai_recommendations($user_id, $consultation_profile, $industry_insights) {
    global $wpdb;

    $recommendations = [
        'actions' => [],
        'priority' => 0,
        'impact' => 'medium',
        'time_estimate' => '1-2 weeks'
    ];

    if (empty($consultation_profile)) {
        return $recommendations;
    }

    $industry = $consultation_profile['industry'] ?? 'General';
    $business_name = $consultation_profile['business_name'] ?? '';
    $business_goals = $consultation_profile['business_goals'] ?? [];

    // ── Gather actual business state to derive recommendations ──

    // 1. Check published page count
    $page_count = (int) $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_author = %d AND post_status = 'publish' AND post_type IN ('page','post')",
        $user_id
    ));

    // 2. Check SEO basics
    $has_ssl = is_ssl();
    $has_yoast = is_plugin_active('wordpress-seo/wp-seo.php') || is_plugin_active('wordpress-seo-premium/wp-seo-premium.php');
    $has_seo_plugin = $has_yoast || is_plugin_active('all-in-one-seo-pack/all_in_one_seo_pack.php') || is_plugin_active('seo-by-rank-math/rank-math.php');

    // 3. Check contacts / leads
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    $lead_count = 0;
    if (vmo_table_exists($contacts_table)) {
        $lead_count = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$contacts_table} WHERE user_id = %d", $user_id
        ));
    }

    // 4. Check phone system
    $has_phone = !empty(get_user_meta($user_id, 'vmo_twilio_phone_number', true));

    // 5. Check VMO Pay / store
    $has_store = !empty(get_user_meta($user_id, 'vmo_pay_account_id', true));

    // 6. Check roadmap progress
    $roadmap_table = $wpdb->prefix . 'vmo_business_roadmaps';
    $roadmap_completion = 0;
    if (vmo_table_exists($roadmap_table)) {
        $roadmap_completion = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT AVG(progress_percent) FROM {$roadmap_table} WHERE user_id = %d AND status = 'active'",
            $user_id
        ));
    }

    // 7. Check recent content freshness
    $last_post = $wpdb->get_var($wpdb->prepare(
        "SELECT MAX(post_date) FROM {$wpdb->posts} WHERE post_author = %d AND post_status = 'publish'",
        $user_id
    ));
    $days_since_publish = $last_post ? (int) ((time() - strtotime($last_post)) / 86400) : 999;

    // ── Build recommendations from actual gaps ──
    $recs = [];

    // Content recommendations
    if ($page_count < 5) {
        $recs[] = [
            'title' => "Create foundational website pages (About, Services, Contact, FAQ, Blog)",
            'description' => "Your site has only {$page_count} published pages. Search engines and visitors need more content to understand your {$industry} business.",
            'priority' => 9,
            'estimated_hours' => 8,
            'expected_impact' => ['conversion_rate' => '+30%', 'traffic' => '+50%']
        ];
    } elseif ($page_count < 15) {
        $recs[] = [
            'title' => "Expand content with service detail pages and blog posts",
            'description' => "With {$page_count} pages you have a good start. Adding detailed service pages and regular blog content will significantly boost SEO.",
            'priority' => 6,
            'estimated_hours' => 12,
            'expected_impact' => ['conversion_rate' => '+15%', 'traffic' => '+25%']
        ];
    }

    if ($days_since_publish > 30 && $page_count > 0) {
        $recs[] = [
            'title' => "Publish fresh content to maintain search rankings",
            'description' => "Your last published content was {$days_since_publish} days ago. Search engines reward sites with regular updates.",
            'priority' => 7,
            'estimated_hours' => 4,
            'expected_impact' => ['conversion_rate' => '+5%', 'traffic' => '+15%']
        ];
    }

    // SEO recommendations
    if (!$has_seo_plugin) {
        $recs[] = [
            'title' => "Set up SEO optimization for all pages",
            'description' => "No SEO plugin detected. Adding meta descriptions, title tags, and structured data will improve search visibility for your {$industry} business.",
            'priority' => 8,
            'estimated_hours' => 4,
            'expected_impact' => ['conversion_rate' => '+10%', 'traffic' => '+35%']
        ];
    }

    if (!$has_ssl) {
        $recs[] = [
            'title' => "Enable SSL certificate for website security",
            'description' => "Your site is not using HTTPS. This hurts search rankings and customer trust. SSL is essential for any business website.",
            'priority' => 10,
            'estimated_hours' => 2,
            'expected_impact' => ['conversion_rate' => '+20%', 'traffic' => '+10%']
        ];
    }

    // Lead generation recommendations
    if ($lead_count === 0) {
        $recs[] = [
            'title' => "Implement lead capture to start building your customer pipeline",
            'description' => "You have no contacts in your CRM yet. Add contact forms, lead magnets, or call-to-action buttons to start capturing leads.",
            'priority' => 8,
            'estimated_hours' => 6,
            'expected_impact' => ['conversion_rate' => '+25%', 'traffic' => '+5%']
        ];
    } elseif ($lead_count < 20) {
        $recs[] = [
            'title' => "Grow your lead pipeline with targeted outreach",
            'description' => "You have {$lead_count} contacts. Consider email campaigns, social media promotion, or paid advertising to grow your pipeline.",
            'priority' => 5,
            'estimated_hours' => 8,
            'expected_impact' => ['conversion_rate' => '+15%', 'traffic' => '+20%']
        ];
    }

    // Phone system
    if (!$has_phone) {
        $recs[] = [
            'title' => "Set up business phone system for professional communication",
            'description' => "A dedicated business phone number with IVR, call recording, and voicemail increases credibility and captures phone leads automatically.",
            'priority' => 5,
            'estimated_hours' => 2,
            'expected_impact' => ['conversion_rate' => '+15%', 'traffic' => '+0%']
        ];
    }

    // E-commerce / payments
    if (!$has_store) {
        $has_products = !empty($consultation_profile['products'] ?? $consultation_profile['offerings'] ?? []);
        if ($has_products || in_array(strtolower($industry), ['retail', 'ecommerce', 'food', 'fashion', 'beauty'])) {
            $recs[] = [
                'title' => "Enable VMO Pay to accept online payments",
                'description' => "Connect Stripe to start selling products/services directly from your website. Online payment capability can significantly increase revenue.",
                'priority' => 7,
                'estimated_hours' => 3,
                'expected_impact' => ['conversion_rate' => '+20%', 'traffic' => '+5%']
            ];
        }
    }

    // Roadmap progress
    if ($roadmap_completion > 0 && $roadmap_completion < 50) {
        $recs[] = [
            'title' => "Complete outstanding roadmap milestones",
            'description' => "Your business roadmap is {$roadmap_completion}% complete. Finishing active milestones will accelerate growth and keep your strategy on track.",
            'priority' => 4,
            'estimated_hours' => 10,
            'expected_impact' => ['conversion_rate' => '+10%', 'traffic' => '+10%']
        ];
    }

    // Add business-goal-specific recommendations
    foreach ($business_goals as $goal) {
        $goal_lower = strtolower((string) $goal);
        if (strpos($goal_lower, 'ecommerce') !== false || strpos($goal_lower, 'sell') !== false) {
            if (!$has_store) {
                $recs[] = [
                    'title' => "Launch your online store to achieve your e-commerce goals",
                    'description' => "You listed e-commerce as a business goal but don't have payments set up yet. Connect VMO Pay and add product listings.",
                    'priority' => 8,
                    'estimated_hours' => 6,
                    'expected_impact' => ['conversion_rate' => '+25%', 'traffic' => '+15%']
                ];
            }
        }
        if (strpos($goal_lower, 'brand') !== false || strpos($goal_lower, 'awareness') !== false) {
            $recs[] = [
                'title' => "Build brand awareness through content marketing",
                'description' => "Create consistent branded content, social media profiles, and local directory listings to increase {$business_name} visibility.",
                'priority' => 5,
                'estimated_hours' => 12,
                'expected_impact' => ['conversion_rate' => '+10%', 'traffic' => '+30%']
            ];
        }
    }

    // Ensure at least one recommendation
    if (empty($recs)) {
        $recs[] = [
            'title' => "Review and optimize your existing website content",
            'description' => "Your {$industry} business setup is looking good. Review existing pages for accuracy, update outdated information, and ensure all calls-to-action are working.",
            'priority' => 3,
            'estimated_hours' => 4,
            'expected_impact' => ['conversion_rate' => '+5%', 'traffic' => '+5%']
        ];
    }

    // Sort by priority descending, cap at 6 recommendations
    usort($recs, function($a, $b) { return $b['priority'] - $a['priority']; });
    $recs = array_slice($recs, 0, 6);

    // Build final actions array with stable IDs
    foreach ($recs as $rec) {
        $recommendations['actions'][] = array_merge($rec, [
            'id' => 'rec_' . md5($rec['title'] . $user_id),
        ]);
    }

    $recommendations['priority'] = array_sum(array_column($recommendations['actions'], 'priority'));

    // Set overall impact and time estimate based on top recommendation
    $top_priority = $recommendations['actions'][0]['priority'] ?? 0;
    $recommendations['impact'] = $top_priority >= 8 ? 'high' : ($top_priority >= 5 ? 'medium' : 'low');
    $total_hours = array_sum(array_column($recommendations['actions'], 'estimated_hours'));
    $recommendations['time_estimate'] = $total_hours <= 10 ? '1 week' : ($total_hours <= 30 ? '2-3 weeks' : '4+ weeks');

    return $recommendations;
}

// Process AI action approval
function vmo_process_ai_action_approval($user_id, $action_id, $approval_status) {
    global $wpdb;
    
    // Create AI actions table if it doesn't exist
    $actions_table = $wpdb->prefix . 'vmo_ai_actions';
    $table_exists = vmo_table_exists($actions_table);
    
    if (!$table_exists) {
        vmo_create_ai_actions_table();
    }
    
    $result = [
        'success' => false,
        'error' => null,
        'next_steps' => [],
        'tracking_id' => null
    ];
    
    // Insert or update action approval
    $tracking_id = 'track_' . uniqid();
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $inserted = $wpdb->insert(
        $actions_table,
        [
            'user_id' => $user_id,
            'action_id' => $action_id,
            'status' => $approval_status,
            'tracking_id' => $tracking_id,
            'created_at' => current_time('mysql'),
            'updated_at' => current_time('mysql')
        ],
        ['%d', '%s', '%s', '%s', '%s', '%s']
    );
    
    if ($inserted) {
        $result['success'] = true;
        $result['tracking_id'] = $tracking_id;
        
        if ($approval_status === 'approved') {
            $result['next_steps'] = [
                'AI will begin implementation within 1 hour',
                'Progress updates will be sent to your dashboard',
                'Results will be tracked and reported automatically'
            ];
        }
    } else {
        $result['error'] = 'Failed to process approval';
    }
    
    return $result;
}

// Create AI actions tracking table
function vmo_create_ai_actions_table() {
    global $wpdb;
    
    $table_name = $wpdb->prefix . 'vmo_ai_actions';
    
    $charset_collate = $wpdb->get_charset_collate();
    
    $sql = "CREATE TABLE $table_name (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        user_id bigint(20) NOT NULL,
        action_id varchar(100) NOT NULL,
        status varchar(20) DEFAULT 'pending',
        tracking_id varchar(100) NOT NULL,
        results longtext NULL,
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY user_id (user_id),
        KEY action_id (action_id),
        KEY status (status)
    ) $charset_collate;";
    
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
}

// Get detailed user analytics
function vmo_get_detailed_user_analytics($user_id, $type, $period) {
    global $wpdb;

    $user_id = (int) $user_id;
    $type = sanitize_key((string) $type);
    $period = sanitize_key((string) $period);

    $analytics = ['data' => [], 'total' => 0];

    $interactions_table = $wpdb->prefix . 'vmo_contact_interactions';
    $table_exists = vmo_table_exists($interactions_table);
    if (!$table_exists) {
        return $analytics;
    }

    $interval = '30 DAY';
    if (in_array($period, ['7d', '7days', 'week'], true)) $interval = '7 DAY';
    elseif (in_array($period, ['14d', '14days'], true)) $interval = '14 DAY';
    elseif (in_array($period, ['30d', '30days', 'month'], true)) $interval = '30 DAY';
    elseif (in_array($period, ['90d', '90days', 'quarter'], true)) $interval = '90 DAY';
    elseif (in_array($period, ['365d', '365days', 'year', '12m'], true)) $interval = '365 DAY';
    elseif (in_array($period, ['all', 'lifetime'], true)) $interval = null;

    $since_sql = $interval ? " AND created_at >= DATE_SUB(NOW(), INTERVAL {$interval})" : '';

    if ($type === 'referrers') {
        $rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT referrer, COUNT(*) AS visits FROM (
                    SELECT COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(full_details,'$.referrer')),''),'(direct)') AS referrer
                    FROM {$interactions_table}
                    WHERE user_id=%d AND interaction_type='website_visit' {$since_sql}
                ) t
                GROUP BY referrer
                ORDER BY visits DESC
                LIMIT 20",
                $user_id
            ),
            ARRAY_A
        );

        // Fallback if JSON functions not supported
        if ($wpdb->last_error) {
            $wpdb->last_error = '';
            $rows_raw = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT summary FROM {$interactions_table} WHERE user_id=%d AND interaction_type='website_visit' {$since_sql} ORDER BY created_at DESC LIMIT 2000",
                    $user_id
                ),
                ARRAY_A
            );
            $counts = [];
            foreach (($rows_raw ?: []) as $rr) {
                $s = (string) ($rr['summary'] ?? '');
                $ref = '(direct)';
                if (strpos($s, 'Visitor from ') === 0) {
                    $ref = trim(substr($s, strlen('Visitor from ')));
                    if ($ref === '') $ref = '(direct)';
                }
                $counts[$ref] = ($counts[$ref] ?? 0) + 1;
            }
            arsort($counts);
            $rows = [];
            foreach (array_slice($counts, 0, 20, true) as $ref => $visits) {
                $rows[] = ['referrer' => $ref, 'visits' => (int) $visits];
            }
        }

        $total = 0;
        foreach (($rows ?: []) as $r) $total += (int) ($r['visits'] ?? 0);
        $analytics['total'] = $total;
        foreach (($rows ?: []) as $r) {
            $visits = (int) ($r['visits'] ?? 0);
            $analytics['data'][] = [
                'source' => (string) ($r['referrer'] ?? '(direct)'),
                'visits' => $visits,
                'percentage' => $total > 0 ? (int) round(($visits / $total) * 100) : 0,
            ];
        }
        return $analytics;
    }

    if ($type === 'pages') {
        $rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT interaction_subtype AS page, COUNT(*) AS views, COUNT(DISTINCT contact_id) AS unique_visitors
                 FROM {$interactions_table}
                 WHERE user_id=%d AND interaction_type='website_visit' {$since_sql}
                 GROUP BY interaction_subtype
                 ORDER BY views DESC
                 LIMIT 50",
                $user_id
            ),
            ARRAY_A
        );

        $total = 0;
        foreach (($rows ?: []) as $r) $total += (int) ($r['views'] ?? 0);
        $analytics['total'] = $total;
        foreach (($rows ?: []) as $r) {
            $analytics['data'][] = [
                'page' => (string) ($r['page'] ?? ''),
                'views' => (int) ($r['views'] ?? 0),
                'unique' => (int) ($r['unique_visitors'] ?? 0),
            ];
        }
        return $analytics;
    }

    if ($type === 'activities') {
        $rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT interaction_type AS activity, COUNT(*) AS cnt
                 FROM {$interactions_table}
                 WHERE user_id=%d {$since_sql}
                 GROUP BY interaction_type
                 ORDER BY cnt DESC
                 LIMIT 20",
                $user_id
            ),
            ARRAY_A
        );

        $total = 0;
        foreach (($rows ?: []) as $r) $total += (int) ($r['cnt'] ?? 0);
        $analytics['total'] = $total;
        foreach (($rows ?: []) as $r) {
            $analytics['data'][] = [
                'activity' => (string) ($r['activity'] ?? ''),
                'count' => (int) ($r['cnt'] ?? 0),
            ];
        }
        return $analytics;
    }

    return $analytics;
}

// Get visitor geography data
function vmo_get_visitor_geography($user_id, $type) {
    global $wpdb;

    $user_id = (int) $user_id;
    $type = sanitize_key((string) $type);

    $geo_data = ['data' => [], 'top' => []];

    $interactions_table = $wpdb->prefix . 'vmo_contact_interactions';
    $table_exists = vmo_table_exists($interactions_table);
    if (!$table_exists) {
        return $geo_data;
    }

    $json_key = ($type === 'countries') ? '$.country' : '$.region';

    $rows = $wpdb->get_results(
        $wpdb->prepare(
            "SELECT COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(metadata,%s)),'') ,'(unknown)') AS name,
                    COUNT(*) AS visitors
             FROM {$interactions_table}
             WHERE user_id=%d AND interaction_type='website_visit'
             GROUP BY name
             ORDER BY visitors DESC
             LIMIT 20",
            $json_key,
            $user_id
        ),
        ARRAY_A
    );

    // Fallback if JSON functions not supported
    if ($wpdb->last_error) {
        $wpdb->last_error = '';
        $raw = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT metadata FROM {$interactions_table} WHERE user_id=%d AND interaction_type='website_visit' ORDER BY created_at DESC LIMIT 2000",
                $user_id
            ),
            ARRAY_A
        );
        $counts = [];
        $field = ($type === 'countries') ? 'country' : 'region';
        foreach (($raw ?: []) as $rr) {
            $m = json_decode((string) ($rr['metadata'] ?? ''), true);
            $name = '(unknown)';
            if (is_array($m) && !empty($m[$field])) $name = (string) $m[$field];
            $counts[$name] = ($counts[$name] ?? 0) + 1;
        }
        arsort($counts);
        $rows = [];
        foreach (array_slice($counts, 0, 20, true) as $name => $visitors) {
            $rows[] = ['name' => $name, 'visitors' => (int) $visitors];
        }
    }

    $total = 0;
    foreach (($rows ?: []) as $r) $total += (int) ($r['visitors'] ?? 0);

    foreach (($rows ?: []) as $r) {
        $visitors = (int) ($r['visitors'] ?? 0);
        $geo_data['data'][] = [
            'name' => (string) ($r['name'] ?? '(unknown)'),
            'visitors' => $visitors,
            'percentage' => $total > 0 ? (int) round(($visitors / $total) * 100) : 0,
        ];
    }

    $geo_data['top'] = array_slice($geo_data['data'], 0, 5);
    return $geo_data;
}

// Generate revenue analytics
function vmo_generate_revenue_analytics($user_id, $period) {
    global $wpdb;

    $user_id = (int) $user_id;
    $period = sanitize_key((string) $period);

    $analytics = [
        'revenue_trend' => [],
        'top_products' => [],
        'customer_segments' => [],
        'growth_metrics' => []
    ];

    $orders_table = $wpdb->prefix . 'vmo_store_orders';
    $products_table = $wpdb->prefix . 'vmo_store_products';
    $has_orders = vmo_table_exists($orders_table);
    if (!$has_orders) {
        return $analytics;
    }

    $interval = '30 DAY';
    if (in_array($period, ['7d', '7days', 'week'], true)) $interval = '7 DAY';
    elseif (in_array($period, ['14d', '14days'], true)) $interval = '14 DAY';
    elseif (in_array($period, ['30d', '30days', 'month'], true)) $interval = '30 DAY';
    elseif (in_array($period, ['90d', '90days', 'quarter'], true)) $interval = '90 DAY';
    elseif (in_array($period, ['365d', '365days', 'year', '12m'], true)) $interval = '365 DAY';
    elseif (in_array($period, ['all', 'lifetime'], true)) $interval = null;

    $since_sql = $interval ? " AND created_at >= DATE_SUB(NOW(), INTERVAL {$interval})" : '';

    // Revenue trend
    $bucket = ($interval === '365 DAY') ? "DATE_FORMAT(created_at,'%%Y-%%m')" : "DATE(created_at)";
    $trend_rows = $wpdb->get_results(
        $wpdb->prepare(
            "SELECT {$bucket} AS period_label, SUM(amount_total) AS revenue_cents, COUNT(*) AS orders
             FROM {$orders_table}
             WHERE user_id=%d AND status='paid' {$since_sql}
             GROUP BY period_label
             ORDER BY period_label ASC
             LIMIT 100",
            $user_id
        ),
        ARRAY_A
    );

    foreach (($trend_rows ?: []) as $r) {
        $analytics['revenue_trend'][] = [
            'period' => (string) ($r['period_label'] ?? ''),
            'revenue' => (float) (((int) ($r['revenue_cents'] ?? 0)) / 100),
            'orders' => (int) ($r['orders'] ?? 0),
        ];
    }

    // Top products (best-effort: relies on metadata.product_id)
    $has_products = vmo_table_exists($products_table);
    if ($has_products) {
        $top_rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata,'$.product_id')) AS UNSIGNED) AS product_id,
                        SUM(amount_total) AS revenue_cents,
                        COUNT(*) AS units
                 FROM {$orders_table}
                 WHERE user_id=%d AND status='paid' {$since_sql}
                 AND JSON_UNQUOTE(JSON_EXTRACT(metadata,'$.product_id')) IS NOT NULL
                 GROUP BY product_id
                 ORDER BY revenue_cents DESC
                 LIMIT 10",
                $user_id
            ),
            ARRAY_A
        );

        if (!$wpdb->last_error) {
            foreach (($top_rows ?: []) as $tr) {
                $pid = (int) ($tr['product_id'] ?? 0);
                if ($pid <= 0) continue;
                $name = (string) $wpdb->get_var($wpdb->prepare("SELECT name FROM {$products_table} WHERE id=%d", $pid));
                if ($name === '') $name = 'Product #' . $pid;
                $analytics['top_products'][] = [
                    'name' => $name,
                    'revenue' => (float) (((int) ($tr['revenue_cents'] ?? 0)) / 100),
                    'units' => (int) ($tr['units'] ?? 0),
                ];
            }
        } else {
            $wpdb->last_error = '';
        }
    }

    // Customer segments (new vs returning vs guest) based on paid orders
    $cust_rows = $wpdb->get_results(
        $wpdb->prepare(
            "SELECT buyer_user_id, COUNT(*) AS orders, SUM(amount_total) AS revenue_cents
             FROM {$orders_table}
             WHERE user_id=%d AND status='paid' {$since_sql}
             GROUP BY buyer_user_id",
            $user_id
        ),
        ARRAY_A
    );

    $seg = [
        'new' => ['orders' => 0, 'revenue_cents' => 0],
        'returning' => ['orders' => 0, 'revenue_cents' => 0],
        'guest' => ['orders' => 0, 'revenue_cents' => 0],
    ];

    foreach (($cust_rows ?: []) as $cr) {
        $buyer = isset($cr['buyer_user_id']) ? (int) $cr['buyer_user_id'] : 0;
        $orders = (int) ($cr['orders'] ?? 0);
        $rev = (int) ($cr['revenue_cents'] ?? 0);
        if ($buyer <= 0) {
            $seg['guest']['orders'] += $orders;
            $seg['guest']['revenue_cents'] += $rev;
        } elseif ($orders <= 1) {
            $seg['new']['orders'] += $orders;
            $seg['new']['revenue_cents'] += $rev;
        } else {
            $seg['returning']['orders'] += $orders;
            $seg['returning']['revenue_cents'] += $rev;
        }
    }

    $total_rev = $seg['new']['revenue_cents'] + $seg['returning']['revenue_cents'] + $seg['guest']['revenue_cents'];
    $mk = function($label, $bucket) use ($total_rev) {
        $rev_cents = (int) ($bucket['revenue_cents'] ?? 0);
        return [
            'segment' => $label,
            'percentage' => $total_rev > 0 ? (int) round(($rev_cents / $total_rev) * 100) : 0,
            'revenue' => (float) ($rev_cents / 100),
        ];
    };
    $analytics['customer_segments'] = [
        $mk('New Customers', $seg['new']),
        $mk('Returning Customers', $seg['returning']),
        $mk('Guest Checkout', $seg['guest']),
    ];

    // Growth metrics: current vs previous interval (if interval known)
    $now = current_time('mysql');
    $curr_rev = (int) $wpdb->get_var($wpdb->prepare("SELECT COALESCE(SUM(amount_total),0) FROM {$orders_table} WHERE user_id=%d AND status='paid' {$since_sql}", $user_id));
    $curr_orders = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$orders_table} WHERE user_id=%d AND status='paid' {$since_sql}", $user_id));
    $avg = $curr_orders > 0 ? ($curr_rev / $curr_orders) : 0;

    $prev_rev = 0;
    if ($interval) {
        $prev_rev = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COALESCE(SUM(amount_total),0) FROM {$orders_table}
             WHERE user_id=%d AND status='paid'
             AND created_at < DATE_SUB(NOW(), INTERVAL {$interval})
             AND created_at >= DATE_SUB(NOW(), INTERVAL " . ($interval === '7 DAY' ? '14 DAY' : ($interval === '14 DAY' ? '28 DAY' : ($interval === '30 DAY' ? '60 DAY' : ($interval === '90 DAY' ? '180 DAY' : '730 DAY')))) . ")",
            $user_id
        ));
    }
    $change = 0.0;
    if ($prev_rev > 0) $change = round((($curr_rev - $prev_rev) / $prev_rev) * 100, 2);
    elseif ($curr_rev > 0) $change = 100.0;

    $analytics['growth_metrics'] = [
        'revenue_total' => (float) ($curr_rev / 100),
        'orders_total' => $curr_orders,
        'avg_order_value' => (float) ($avg / 100),
        'revenue_change_percent' => $change,
        'as_of' => $now,
    ];

    return $analytics;
}

// Market Intelligence functions — derive insights from consultation profile,
// phase outputs, and CRM data instead of returning empty arrays.

function vmo_analyze_market_trends($industry) {
    if (empty($industry) || $industry === 'General') return [];

    $user_id = get_current_user_id();
    global $wpdb;

    // Check phase_outputs for market_research data first
    $table = $wpdb->prefix . 'vmo_phase_outputs';
    if (vmo_table_exists($table)) {
        $latest = $wpdb->get_var($wpdb->prepare(
            "SELECT output_json FROM {$table} WHERE user_id = %d AND output_type = 'market_research' AND is_active = 1 ORDER BY created_at DESC LIMIT 1",
            $user_id
        ));
        if ($latest) {
            $data = json_decode($latest, true);
            if (is_array($data) && !empty($data['trends'])) {
                return $data['trends'];
            }
        }
    }

    // Derive basic trends from industry profile
    $industry_trends = [
        'Technology' => [
            ['trend' => 'AI integration in business workflows', 'direction' => 'up', 'impact' => 'high'],
            ['trend' => 'Cloud-first operations', 'direction' => 'up', 'impact' => 'high'],
            ['trend' => 'Cybersecurity investment increasing', 'direction' => 'up', 'impact' => 'medium'],
        ],
        'Healthcare' => [
            ['trend' => 'Telehealth adoption growing', 'direction' => 'up', 'impact' => 'high'],
            ['trend' => 'Patient data privacy regulations tightening', 'direction' => 'up', 'impact' => 'high'],
            ['trend' => 'AI-assisted diagnostics', 'direction' => 'up', 'impact' => 'medium'],
        ],
        'Finance' => [
            ['trend' => 'Fintech disruption accelerating', 'direction' => 'up', 'impact' => 'high'],
            ['trend' => 'Embedded finance growing', 'direction' => 'up', 'impact' => 'medium'],
            ['trend' => 'Regulatory compliance costs rising', 'direction' => 'up', 'impact' => 'medium'],
        ],
        'Retail' => [
            ['trend' => 'Omnichannel commerce essential', 'direction' => 'up', 'impact' => 'high'],
            ['trend' => 'Social commerce expansion', 'direction' => 'up', 'impact' => 'medium'],
            ['trend' => 'Sustainable products demand', 'direction' => 'up', 'impact' => 'medium'],
        ],
        'Education' => [
            ['trend' => 'Online learning platforms growing', 'direction' => 'up', 'impact' => 'high'],
            ['trend' => 'Micro-credentials and certification demand', 'direction' => 'up', 'impact' => 'medium'],
            ['trend' => 'AI tutoring tools adoption', 'direction' => 'up', 'impact' => 'medium'],
        ],
    ];

    return $industry_trends[$industry] ?? [
        ['trend' => 'Digital transformation accelerating', 'direction' => 'up', 'impact' => 'high'],
        ['trend' => 'Customer experience focus increasing', 'direction' => 'up', 'impact' => 'medium'],
        ['trend' => 'AI adoption across industries', 'direction' => 'up', 'impact' => 'high'],
    ];
}

function vmo_track_competitor_activities($user_id) {
    global $wpdb;

    // Pull competitor data from phase_outputs market_research
    $table = $wpdb->prefix . 'vmo_phase_outputs';
    if (vmo_table_exists($table)) {
        $latest = $wpdb->get_var($wpdb->prepare(
            "SELECT output_json FROM {$table} WHERE user_id = %d AND output_type = 'market_research' AND is_active = 1 ORDER BY created_at DESC LIMIT 1",
            $user_id
        ));
        if ($latest) {
            $data = json_decode($latest, true);
            if (is_array($data) && !empty($data['competitors'])) {
                return $data['competitors'];
            }
        }
    }

    return [];
}

function vmo_identify_opportunities($profile) {
    if (empty($profile)) return [];

    $user_id = get_current_user_id();
    global $wpdb;

    // Check phase_outputs
    $table = $wpdb->prefix . 'vmo_phase_outputs';
    if (vmo_table_exists($table)) {
        $latest = $wpdb->get_var($wpdb->prepare(
            "SELECT output_json FROM {$table} WHERE user_id = %d AND output_type = 'market_research' AND is_active = 1 ORDER BY created_at DESC LIMIT 1",
            $user_id
        ));
        if ($latest) {
            $data = json_decode($latest, true);
            if (is_array($data) && !empty($data['opportunities'])) {
                return $data['opportunities'];
            }
        }
    }

    // Derive from profile
    $industry = $profile['industry'] ?? 'General';
    $target = $profile['target_audience'] ?? '';
    $opportunities = [];
    if (!empty($target)) {
        $opportunities[] = ['opportunity' => "Underserved {$target} segment in {$industry}", 'potential' => 'high'];
    }
    $opportunities[] = ['opportunity' => 'AI-powered automation for competitive advantage', 'potential' => 'high'];
    $opportunities[] = ['opportunity' => 'Digital-first customer engagement', 'potential' => 'medium'];

    return $opportunities;
}

function vmo_assess_market_risks($industry) {
    if (empty($industry) || $industry === 'General') return [];

    $user_id = get_current_user_id();
    global $wpdb;

    // Check phase_outputs
    $table = $wpdb->prefix . 'vmo_phase_outputs';
    if (vmo_table_exists($table)) {
        $latest = $wpdb->get_var($wpdb->prepare(
            "SELECT output_json FROM {$table} WHERE user_id = %d AND output_type = 'market_research' AND is_active = 1 ORDER BY created_at DESC LIMIT 1",
            $user_id
        ));
        if ($latest) {
            $data = json_decode($latest, true);
            if (is_array($data) && !empty($data['threats'])) {
                return $data['threats'];
            }
        }
    }

    // Standard risks per industry
    $base_risks = [
        ['risk' => 'Market saturation in ' . $industry, 'severity' => 'medium'],
        ['risk' => 'Emerging low-cost competitors', 'severity' => 'medium'],
        ['risk' => 'Regulatory changes', 'severity' => 'low'],
    ];

    return $base_risks;
}

function vmo_count_monitored_competitors($user_id) {
    global $wpdb;

    // Count from phase_outputs market_research competitors
    $table = $wpdb->prefix . 'vmo_phase_outputs';
    if (vmo_table_exists($table)) {
        $latest = $wpdb->get_var($wpdb->prepare(
            "SELECT output_json FROM {$table} WHERE user_id = %d AND output_type = 'market_research' AND is_active = 1 ORDER BY created_at DESC LIMIT 1",
            $user_id
        ));
        if ($latest) {
            $data = json_decode($latest, true);
            if (is_array($data) && !empty($data['competitors'])) {
                return count($data['competitors']);
            }
        }
    }

    return 0;
}

/* --------------------------------------------------------------
   SHIPPING & FULFILLMENT BACKEND FUNCTIONS
---------------------------------------------------------------- */

// Get Active Orders awaiting shipment
function vmo_get_active_orders() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    global $wpdb;
    $user_id = get_current_user_id();

    // Ensure expected schema exists (dbDelta is safe to call repeatedly)
    if (function_exists('vmo_create_internal_store_orders_table')) {
        vmo_create_internal_store_orders_table();
    }
    
    // Check for orders table
    $orders_table = $wpdb->prefix . 'vmo_internal_store_orders';
    $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $orders_table));
    
    if (!$table_exists) {
        // If table doesn't exist, return empty list instead of error
        wp_send_json_success([
            'orders' => [],
            'total' => 0
        ]);
        return;
    }
    
    // Get orders that are paid but not yet shipped
    $orders = $wpdb->get_results($wpdb->prepare("
        SELECT 
            id,
            customer_name,
            customer_email,
            total_amount as amount,
            items_count,
            shipping_status,
            created_at
        FROM {$orders_table} 
        WHERE user_id = %d 
        AND status = 'paid' 
        AND (shipping_status IS NULL OR shipping_status IN ('pending', 'processing'))
        ORDER BY created_at DESC
        LIMIT 20
    ", $user_id));
    
    // If no orders found, return empty array
    if (empty($orders)) {
        wp_send_json_success([
            'orders' => [],
            'total' => 0
        ]);
        return;
    }
    
    // Format orders for display
    $formatted_orders = [];
    foreach ($orders as $order) {
        $formatted_orders[] = [
            'id' => $order->id,
            'customer_name' => $order->customer_name ?: 'Unknown Customer',
            'customer_email' => $order->customer_email ?: '',
            'total' => number_format((float) $order->amount, 2),
            'items_count' => $order->items_count ?: 1,
            'shipping_status' => $order->shipping_status ?: 'pending',
            'created_at' => $order->created_at
        ];
    }
    
    wp_send_json_success([
        'orders' => $formatted_orders,
        'total' => count($formatted_orders)
    ]);
}

// Approve Shipment for an order
function vmo_approve_shipment() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    $order_id = sanitize_text_field($_POST['order_id'] ?? '');
    if (empty($order_id)) {
        wp_send_json_error(['message' => 'Order ID required']);
    }

    global $wpdb;
    $user_id = get_current_user_id();
    $orders_table = $wpdb->prefix . 'vmo_internal_store_orders';

    // Ensure expected schema exists (dbDelta is safe to call repeatedly)
    if (function_exists('vmo_create_internal_store_orders_table')) {
        vmo_create_internal_store_orders_table();
    }
    
    // Verify order belongs to user
    $order = $wpdb->get_row($wpdb->prepare("
        SELECT * FROM {$orders_table} 
        WHERE id = %s AND user_id = %d AND status = 'paid'
    ", $order_id, $user_id));
    
    if (!$order) {
        wp_send_json_error(['message' => 'Order not found or unauthorized']);
    }
    
    // Update shipping status to approved
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $updated = $wpdb->update(
        $orders_table,
        [
            'shipping_status' => 'approved',
            'shipping_approved_at' => current_time('mysql')
        ],
        ['id' => $order_id, 'user_id' => $user_id],
        ['%s', '%s'],
        ['%d', '%d']
    );
    
    if ($updated) {
        // Automated label purchase / tracking is not implemented.
        // Users can contact carriers directly to arrange contracts and shipping workflows.
        wp_send_json_success([
            'message' => 'Shipment approved. Arrange carrier pickup/labels directly with your selected shipping company.',
            'order_id' => (int) $order_id,
        ]);
    } else {
        wp_send_json_error(['message' => 'Failed to approve shipment']);
    }
}

/**
 * Immediately fulfill an order: Generate Label -> Save Tracking -> Email Customer
 */
// Helper: Generate Tracking Number
function vmo_generate_tracking_number($order_id, $carrier) {
    // Tracking numbers must come from real carrier API label purchases (e.g., EasyPost, ShipStation, Shippo).
    // This function checks if the user has carrier API credentials configured and attempts to create a label.
    _deprecated_function(__FUNCTION__, '1.0', 'Carrier API label purchase via vmo_fulfill_order_now()');

    $user_id = get_current_user_id();
    $settings = get_user_meta($user_id, 'vmo_shipping_settings', true);

    // Attempt to find configured carrier API for this carrier
    if (is_array($settings) && !empty($settings['carriers'])) {
        foreach ($settings['carriers'] as $c) {
            $c_name = strtolower($c['name'] ?? $c['id'] ?? '');
            if ($c_name === strtolower($carrier) && !empty($c['api_key'])) {
                // Carrier API key found but no SDK integration yet — log and return empty
                if (defined('WP_DEBUG') && WP_DEBUG) error_log("[VMO] vmo_generate_tracking_number: Carrier '{$carrier}' has API key but SDK integration is pending for order #{$order_id}");
                return '';
            }
        }
    }

    // No carrier API configured
    return '';
}

function vmo_fulfill_order_now($order_id, $order) {
    global $wpdb;
    $table = $wpdb->prefix . 'vmo_internal_store_orders';

    // Validate the order exists and is pending
    if (!$order || empty($order->id)) {
        return ['success' => false, 'message' => 'Invalid order data.'];
    }
    if (($order->shipping_status ?? '') === 'shipped') {
        return ['success' => false, 'message' => 'Order has already been shipped.'];
    }

    // Determine carrier from order or user settings
    $carrier = $order->carrier ?? '';
    if (empty($carrier)) {
        $user_id = $order->user_id ?? get_current_user_id();
        $settings = get_user_meta($user_id, 'vmo_shipping_settings', true);
        if (is_array($settings) && !empty($settings['carriers'])) {
            foreach ($settings['carriers'] as $c) {
                if (!empty($c['enabled'])) { $carrier = $c['name'] ?? $c['id'] ?? ''; break; }
            }
        }
    }

    // Update order to shipped
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $updated = $wpdb->update(
        $table,
        [
            'shipping_status' => 'shipped',
            'carrier' => $carrier ?: 'Manual',
            'shipped_at' => current_time('mysql'),
        ],
        ['id' => (int) $order_id],
        ['%s', '%s', '%s'],
        ['%d']
    );

    if ($updated === false) {
        return ['success' => false, 'message' => 'Database update failed: ' . $wpdb->last_error];
    }

    // Log to shipping analytics
    $analytics_table = $wpdb->prefix . 'vmo_shipping_analytics';
    if (vmo_table_exists($analytics_table)) {
        $created = $order->created_at ?? current_time('mysql');
        $hours = max(0.1, (time() - strtotime($created)) / 3600);
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->insert($analytics_table, [
            'user_id' => $order->user_id ?? get_current_user_id(),
            'order_id' => (string) $order_id,
            'carrier' => $carrier ?: 'Manual',
            'service_type' => 'Standard',
            'shipping_cost' => 0,
            'fulfillment_hours' => round($hours, 2),
            'on_time_delivery' => ($hours <= 48) ? 1 : 0,
            'shipped_date' => current_time('mysql'),
        ], ['%d','%s','%s','%s','%s','%s','%d','%s']);
    }

    return ['success' => true, 'message' => 'Order #' . $order_id . ' marked as shipped via ' . ($carrier ?: 'Manual') . '.'];
}

// Get Order Details (for the Shipping UI modal)
function vmo_get_order_details() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }

    $order_id = sanitize_text_field($_POST['order_id'] ?? '');
    if ($order_id === '') {
        wp_send_json_error(['message' => 'Order ID required']);
    }

    global $wpdb;
    $user_id = get_current_user_id();
    $orders_table = $wpdb->prefix . 'vmo_internal_store_orders';

    if (function_exists('vmo_create_internal_store_orders_table')) {
        vmo_create_internal_store_orders_table();
    }

    $order = $wpdb->get_row($wpdb->prepare(
        "SELECT id, customer_name, customer_email, shipping_address, total_amount, currency, status, carrier, tracking_number, created_at, shipped_at, shipping_status FROM {$orders_table} WHERE id = %d AND user_id = %d",
        (int) $order_id,
        (int) $user_id
    ));

    if (!$order) {
        wp_send_json_error(['message' => 'Order not found']);
    }

    wp_send_json_success([
        'id' => (int) $order->id,
        'customer_name' => (string) ($order->customer_name ?? ''),
        'customer_email' => (string) ($order->customer_email ?? ''),
        'shipping_address' => (string) ($order->shipping_address ?? ''),
        'total' => number_format((float) ($order->total_amount ?? 0), 2),
        'currency' => (string) ($order->currency ?? 'USD'),
        'status' => (string) ($order->status ?? ''),
        'shipping_status' => (string) ($order->shipping_status ?? ''),
        'carrier' => (string) ($order->carrier ?? ''),
        'tracking_number' => (string) ($order->tracking_number ?? ''),
        'created_at' => (string) ($order->created_at ?? ''),
        'shipped_at' => (string) ($order->shipped_at ?? ''),
    ]);
}

// Get Carrier Config (per-user)
function vmo_get_carrier_config() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }

    $carrier_id = sanitize_key($_POST['carrier_id'] ?? '');
    if ($carrier_id === '') {
        wp_send_json_error(['message' => 'carrier_id required']);
    }

    $user_id = get_current_user_id();
    $configs = get_user_meta($user_id, 'vmo_carrier_configs', true);
    if (!is_array($configs)) {
        $configs = [];
    }

    $cfg = $configs[$carrier_id] ?? [];
    wp_send_json_success([
        'carrier_id' => $carrier_id,
        'api_key' => (string) ($cfg['api_key'] ?? ''),
        'secret' => (string) ($cfg['secret'] ?? ''),
        'service' => (string) ($cfg['service'] ?? ''),
        'updated_at' => (string) ($cfg['updated_at'] ?? ''),
    ]);
}

// Save Carrier Config (per-user)
function vmo_save_carrier_config() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }

    $carrier_id = sanitize_key($_POST['carrier_id'] ?? '');
    if ($carrier_id === '') {
        wp_send_json_error(['message' => 'carrier_id required']);
    }

    $api_key = sanitize_text_field($_POST['api_key'] ?? '');
    $secret = sanitize_text_field($_POST['secret'] ?? '');
    $service = sanitize_text_field($_POST['service'] ?? '');

    $user_id = get_current_user_id();
    $configs = get_user_meta($user_id, 'vmo_carrier_configs', true);
    if (!is_array($configs)) {
        $configs = [];
    }

    $configs[$carrier_id] = [
        'api_key' => $api_key,
        'secret' => $secret,
        'service' => $service,
        'updated_at' => current_time('mysql')
    ];

    update_user_meta($user_id, 'vmo_carrier_configs', $configs);
    wp_send_json_success(['message' => 'Carrier configuration saved', 'carrier_id' => $carrier_id]);
}

// Save Fulfillment Config (per-user)
function vmo_save_fulfillment_config() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }

    $provider = sanitize_text_field($_POST['provider'] ?? '');
    if ($provider === '') {
        wp_send_json_error(['message' => 'provider required']);
    }

    $sync_frequency = sanitize_text_field($_POST['sync_frequency'] ?? '');
    $auto_fulfill = !empty($_POST['auto_fulfill']) && $_POST['auto_fulfill'] !== '0';

    $user_id = get_current_user_id();
    $configs = get_user_meta($user_id, 'vmo_fulfillment_configs', true);
    if (!is_array($configs)) {
        $configs = [];
    }

    $configs[$provider] = [
        'sync_frequency' => $sync_frequency,
        'auto_fulfill' => $auto_fulfill,
        'updated_at' => current_time('mysql')
    ];

    update_user_meta($user_id, 'vmo_fulfillment_configs', $configs);
    wp_send_json_success(['message' => 'Fulfillment settings saved', 'provider' => $provider]);
}

// Bulk approve shipments (used by Bulk Process modal)
function vmo_bulk_approve_shipments() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }

    $order_ids_raw = wp_unslash($_POST['order_ids'] ?? '[]');
    if (is_string($order_ids_raw)) {
        $order_ids = json_decode($order_ids_raw, true);
    } else {
        $order_ids = $order_ids_raw;
    }
    if (!is_array($order_ids)) {
        wp_send_json_error(['message' => 'Invalid order_ids']);
    }

    $order_ids = array_values(array_filter(array_map('intval', $order_ids), fn($v) => $v > 0));
    if (!$order_ids) {
        wp_send_json_success(['processed' => 0, 'errors' => []]);
    }

    global $wpdb;
    $user_id = get_current_user_id();
    $orders_table = $wpdb->prefix . 'vmo_internal_store_orders';

    if (function_exists('vmo_create_internal_store_orders_table')) {
        vmo_create_internal_store_orders_table();
    }

    $processed = 0;
    $errors = [];
    foreach ($order_ids as $order_id) {
        $order = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$orders_table} WHERE id = %d AND user_id = %d AND status = 'paid'",
            (int) $order_id,
            (int) $user_id
        ));

        if (!$order) {
            $errors[] = ['order_id' => $order_id, 'message' => 'Order not found or not eligible'];
            continue;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->update(
            $orders_table,
            [
                'shipping_status' => 'approved',
                'shipping_approved_at' => current_time('mysql')
            ],
            ['id' => $order_id, 'user_id' => $user_id],
            ['%s', '%s'],
            ['%d', '%d']
        );

        $processed++;
    }

    wp_send_json_success(['processed' => $processed, 'errors' => $errors]);
}

// Get Shipping Settings
function vmo_get_shipping_settings() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    $user_id = get_current_user_id();
    
    // Get user's shipping preferences
    $shipping_settings = get_user_meta($user_id, 'vmo_shipping_settings', true);
    
    if (!$shipping_settings) {
        // Default settings
        $shipping_settings = [
            'carriers' => [
                ['id' => 'fedex', 'name' => 'FedEx Global', 'enabled' => true],
                ['id' => 'ups', 'name' => 'UPS Worldwide', 'enabled' => true],
                ['id' => 'dhl', 'name' => 'DHL Express', 'enabled' => false],
                ['id' => 'usps', 'name' => 'USPS International', 'enabled' => false],
                ['id' => 'aramex', 'name' => 'Aramex', 'enabled' => false],
                ['id' => 'tnt', 'name' => 'TNT (FedEx)', 'enabled' => false],
                ['id' => 'sf-express', 'name' => 'SF Express', 'enabled' => false],
                ['id' => 'correios', 'name' => 'Correios', 'enabled' => false],
                ['id' => 'ems', 'name' => 'EMS Global', 'enabled' => false]
            ],
            'fulfillment' => [
                ['id' => 'shipbob', 'name' => 'ShipBob Global', 'enabled' => false],
                ['id' => 'amazon-fba', 'name' => 'Amazon FBA Global', 'enabled' => false],
                ['id' => 'easyship', 'name' => 'Easyship', 'enabled' => false],
                ['id' => 'flexport', 'name' => 'Flexport', 'enabled' => false],
                ['id' => 'shipmonk', 'name' => 'ShipMonk', 'enabled' => false]
            ]
        ];
    }

    // Enrich settings with provider contact info (no placeholders; only known official contacts)
    $contacts = vmo_get_shipping_provider_contacts();
    foreach (['carriers' => 'carriers', 'fulfillment' => 'fulfillment'] as $groupKey => $contactKey) {
        if (empty($shipping_settings[$groupKey]) || !is_array($shipping_settings[$groupKey])) continue;
        foreach ($shipping_settings[$groupKey] as $i => $item) {
            $id = is_array($item) ? (string) ($item['id'] ?? '') : '';
            if ($id && isset($contacts[$contactKey][$id]) && is_array($contacts[$contactKey][$id])) {
                $shipping_settings[$groupKey][$i] = array_merge($item, $contacts[$contactKey][$id]);
            }
        }
    }
    
    wp_send_json_success($shipping_settings);
}

// Provider contact map for carriers/fulfillment (only include details that can be verified / stable)
function vmo_get_shipping_provider_contacts(): array {
    return [
        'carriers' => [
            'fedex' => [
                'support_url' => 'https://www.fedex.com/en-us/customer-support/contact-us.html',
                'support_phone' => '1-800-463-3339',
                'support_phone_label' => 'US Customer Service',
                'support_email' => 'ftn_us@fedex.com',
                'support_email_label' => 'FedEx Logistics Contact Center (US)'
            ],
            'ups' => [
                'support_url' => 'https://www.ups.com/us/en/support/contact-us.page',
            ],
            'dhl' => [
                'support_url' => 'https://www.dhl.com/us-en/home/customer-service.html',
            ],
            'usps' => [
                'support_url' => 'https://www.usps.com/help/contact-us.htm',
            ],
            'aramex' => [
                'support_url' => 'https://www.aramex.com/support/help-center',
                'office_locator_url' => 'https://www.aramex.com/find-aramex-office'
            ],
            'tnt' => [
                'support_url' => 'https://www.tnt.com/express/en_us/site/home/help-center.html',
            ],
            'sf-express' => [
                'support_url' => 'https://www.sf-international.com/us/en/',
            ],
            'correios' => [
                'support_url' => 'https://www.correios.com.br/falecomoscorreios/central-de-atendimento',
                'sales_url' => 'https://c.correios.com.br/atendimentocomercial'
            ],
            'ems' => [
                'support_url' => 'https://www.ems.post/en',
            ],
        ],
        'fulfillment' => [
            'shipbob' => [
                'support_url' => 'https://www.shipbob.com/contact/',
                'support_phone' => '1-312-313-1104',
                'support_phone_label' => 'Customer support',
                'sales_phone' => '1-844-474-4726',
                'sales_phone_label' => 'Sales'
            ],
            'amazon-fba' => [
                'support_url' => 'https://sell.amazon.com/fulfillment-by-amazon',
            ],
            'easyship' => [
                'support_url' => 'https://support.easyship.com/hc/en-us/requests/new',
                'contact_url' => 'https://www.easyship.com/contact'
            ],
            'flexport' => [
                'support_url' => 'https://www.flexport.com/get-started/',
            ],
            'shipmonk' => [
                'support_url' => 'https://www.shipmonk.com/get-a-quote',
            ],
        ],
    ];
}

// Save Shipping Settings
function vmo_save_shipping_settings() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    $user_id = get_current_user_id();
    $carriers = map_deep(wp_unslash($_POST['carriers'] ?? []), 'sanitize_text_field');
    $fulfillment = map_deep(wp_unslash($_POST['fulfillment'] ?? []), 'sanitize_text_field');
    
    // Format settings
    $settings = [
        'carriers' => [],
        'fulfillment' => [],
        'updated_at' => current_time('mysql')
    ];
    
    // Process carriers (covers all options in the Shipping Settings UI)
    $carrier_names = [
        'fedex' => 'FedEx Global',
        'ups' => 'UPS Worldwide',
        'dhl' => 'DHL Express',
        'usps' => 'USPS International',
        'aramex' => 'Aramex',
        'tnt' => 'TNT (FedEx)',
        'sf-express' => 'SF Express',
        'correios' => 'Correios',
        'ems' => 'EMS Global',
    ];
    foreach ($carrier_names as $id => $name) {
        $settings['carriers'][] = [
            'id' => $id,
            'name' => $name,
            'enabled' => !empty($carriers[$id])
        ];
    }

    // Process fulfillment (covers all options in the Shipping Settings UI)
    $fulfillment_names = [
        'shipbob' => 'ShipBob Global',
        'amazon-fba' => 'Amazon FBA Global',
        'easyship' => 'Easyship',
        'flexport' => 'Flexport',
        'shipmonk' => 'ShipMonk',
    ];
    foreach ($fulfillment_names as $id => $name) {
        $settings['fulfillment'][] = [
            'id' => $id,
            'name' => $name,
            'enabled' => !empty($fulfillment[$id])
        ];
    }
    
    $saved = update_user_meta($user_id, 'vmo_shipping_settings', $settings);
    
    if ($saved !== false) {
        wp_send_json_success(['message' => 'Shipping settings saved successfully']);
    } else {
        wp_send_json_error(['message' => 'Failed to save shipping settings']);
    }
}

// Helper: Test Carrier Connection
function vmo_test_carrier_connection($carrier_id = null) {
    $resolve_connection = function($resolved_carrier_id, $user_id) {
        $shipping_settings = get_user_meta($user_id, 'vmo_shipping_settings', true);
        $carrier_config = null;

        if (is_array($shipping_settings) && isset($shipping_settings['carriers']) && is_array($shipping_settings['carriers'])) {
            foreach ($shipping_settings['carriers'] as $carrier) {
                if (($carrier['id'] ?? '') === $resolved_carrier_id) {
                    $carrier_config = $carrier;
                    break;
                }
            }
        }

        if (!$carrier_config || empty($carrier_config['api_key'])) {
            return [
                'connected' => false,
                'simulated' => false,
                'message' => "No API credentials configured for {$resolved_carrier_id}. Add the provider API key before testing the connection.",
                'latency' => '0ms'
            ];
        }

        $endpoint = trim((string) ($carrier_config['endpoint'] ?? ''));
        if ($endpoint === '') {
            return [
                'connected' => false,
                'simulated' => false,
                'message' => "API credentials are present for {$resolved_carrier_id}, but no carrier endpoint is configured for live validation.",
                'latency' => '0ms'
            ];
        }

        $health_url = trailingslashit(untrailingslashit($endpoint)) . 'health';
        $started_at = microtime(true);
        $response = wp_remote_get($health_url, [
            'timeout' => 10,
            'headers' => ['Authorization' => 'Bearer ' . $carrier_config['api_key']],
        ]);
        $latency = round((microtime(true) - $started_at) * 1000) . 'ms';

        if (!is_wp_error($response)) {
            $status_code = (int) wp_remote_retrieve_response_code($response);
            if ($status_code >= 200 && $status_code < 300) {
                return [
                    'connected' => true,
                    'simulated' => false,
                    'message' => "Connected to {$resolved_carrier_id} successfully",
                    'latency' => $latency
                ];
            }

            return [
                'connected' => false,
                'simulated' => false,
                'message' => "Failed to connect to {$resolved_carrier_id}: HTTP {$status_code}",
                'latency' => $latency
            ];
        }

        return [
            'connected' => false,
            'simulated' => false,
            'message' => "Failed to connect to {$resolved_carrier_id}: " . $response->get_error_message(),
            'latency' => $latency
        ];
    };

    // AJAX handler mode
    if ($carrier_id === null) {
        if (!current_user_can('read')) {
            wp_send_json_error(['message' => 'Unauthorized'], 403);
        }
        if (!vmo_verify_admin_security()) {
            wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
        }

        $carrier_id = sanitize_text_field($_POST['carrier_id'] ?? '');
        if ($carrier_id === '') {
            wp_send_json_error(['message' => 'carrier_id required']);
        }

        wp_send_json_success($resolve_connection($carrier_id, get_current_user_id()));
    }

    // Helper mode (used internally by other endpoints)
    $result = $resolve_connection($carrier_id, get_current_user_id());
    $result['success'] = !empty($result['connected']);
    return $result;
}

// Helper: Test Fulfillment Connection
function vmo_test_fulfillment_connection($fulfillment_id) {
    // Check if the user has configured fulfillment provider credentials
    $user_id = get_current_user_id();
    $shipping_settings = get_user_meta($user_id, 'vmo_shipping_settings', true);
    $provider_config = null;

    if (is_array($shipping_settings) && !empty($shipping_settings['fulfillment_providers'])) {
        foreach ($shipping_settings['fulfillment_providers'] as $p) {
            if (($p['id'] ?? '') === $fulfillment_id && !empty($p['api_key'])) {
                $provider_config = $p;
                break;
            }
        }
    }

    if (!$provider_config) {
        return [
            'success' => false,
            'simulated' => false,
            'message' => "No API credentials configured for fulfillment provider '{$fulfillment_id}'. Please add your API key in Shipping Settings.",
            'inventory_sync' => 'not_configured'
        ];
    }

    // Attempt a real API health-check to the provider's endpoint
    $endpoint = $provider_config['endpoint'] ?? '';
    if (!empty($endpoint)) {
        $response = wp_remote_get($endpoint . '/health', [
            'timeout' => 10,
            'headers' => ['Authorization' => 'Bearer ' . $provider_config['api_key']],
        ]);
        if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) >= 200 && wp_remote_retrieve_response_code($response) < 300) {
            return [
                'success' => true,
                'simulated' => false,
                'message' => "Connected to {$fulfillment_id} successfully",
                'inventory_sync' => $provider_config['sync_enabled'] ?? 'unknown',
                'latency' => round((microtime(true) - ($_SERVER['REQUEST_TIME_FLOAT'] ?? microtime(true))) * 1000) . 'ms',
            ];
        } else {
            $err = is_wp_error($response) ? $response->get_error_message() : 'HTTP ' . wp_remote_retrieve_response_code($response);
            return [
                'success' => false,
                'simulated' => false,
                'message' => "Failed to connect to {$fulfillment_id}: {$err}",
                'inventory_sync' => 'error'
            ];
        }
    }

    return [
        'success' => false,
        'simulated' => false,
        'message' => "No endpoint URL configured for fulfillment provider '{$fulfillment_id}'.",
        'inventory_sync' => 'not_configured'
    ];
}

// Test Shipping Integration
function vmo_test_shipping_integration() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    $user_id = get_current_user_id();
    $shipping_settings = get_user_meta($user_id, 'vmo_shipping_settings', true);
    
    $results = [];
    $contacts = vmo_get_shipping_provider_contacts();

    // Report status (no simulation)
    if ($shipping_settings && isset($shipping_settings['carriers']) && is_array($shipping_settings['carriers'])) {
        foreach ($shipping_settings['carriers'] as $carrier) {
            if (!empty($carrier['enabled'])) {
                $cid = (string) ($carrier['id'] ?? '');
                $url = $contacts['carriers'][$cid]['support_url'] ?? '';
                $results[(string) ($carrier['name'] ?? $cid)] = [
                    'success' => false,
                    'message' => $url ? ('Manual setup required. Contact: ' . $url) : 'Manual setup required. Contact the provider directly.',
                ];
            }
        }
    }

    if ($shipping_settings && isset($shipping_settings['fulfillment']) && is_array($shipping_settings['fulfillment'])) {
        foreach ($shipping_settings['fulfillment'] as $fulfillment) {
            if (!empty($fulfillment['enabled'])) {
                $fid = (string) ($fulfillment['id'] ?? '');
                $url = $contacts['fulfillment'][$fid]['support_url'] ?? '';
                $results[(string) ($fulfillment['name'] ?? $fid)] = [
                    'success' => false,
                    'message' => $url ? ('Manual setup required. Contact: ' . $url) : 'Manual setup required. Contact the provider directly.',
                ];
            }
        }
    }

    wp_send_json_success([
        'results' => $results,
        'message' => 'No automated carrier integrations are enabled. Use the contacts provided to arrange your contracts and shipping workflows.'
    ]);
}


// Create Store Orders Table (VMO Internal Store)
function vmo_create_internal_store_orders_table() {
    global $wpdb;
    $table_name = $wpdb->prefix . 'vmo_internal_store_orders';
    $charset_collate = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE $table_name (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        user_id bigint(20) NOT NULL,
        customer_name varchar(255) NOT NULL,
        customer_email varchar(255) NOT NULL,
        shipping_address text NOT NULL,
        items longtext NOT NULL,
        items_count int(11) DEFAULT 1,
        total_amount decimal(10,2) NOT NULL,
        currency varchar(3) DEFAULT 'USD',
        status varchar(50) DEFAULT 'pending',
        shipping_status varchar(50) DEFAULT 'pending',
        shipping_approved_at datetime NULL,
        payment_status varchar(50) DEFAULT 'unpaid',
        stripe_payment_intent varchar(255),
        carrier varchar(100),
        tracking_number varchar(100),
        shipped_at datetime NULL,
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY  (id),
        KEY user_id (user_id)
    ) $charset_collate;";

    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
}

// Get Pending Shipping Orders (VMO Store Only - No WooCommerce Dependency)
function vmo_get_pending_shipping_orders() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    global $wpdb;
    $user_id = get_current_user_id();
    $table_name = $wpdb->prefix . 'vmo_internal_store_orders';
    
    // Ensure table exists
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
        vmo_create_internal_store_orders_table();
    }
    
    // Fetch orders from VMO internal system
    $results = $wpdb->get_results($wpdb->prepare(
        "SELECT * FROM $table_name WHERE user_id = %d AND status IN ('processing', 'paid') ORDER BY created_at DESC LIMIT 20",
        $user_id
    ));
    
    $orders = [];
    if ($results) {
        foreach ($results as $order) {
            $orders[] = [
                'id' => $order->id,
                'customer' => $order->customer_name,
                'destination' => $order->shipping_address,
                'carrier' => $order->carrier ?: 'Pending carrier selection',
                'rate' => '0.00'
            ];
        }
    }
    
    wp_send_json_success(['orders' => $orders]);
}
add_action('wp_ajax_vmo_get_pending_shipping_orders', 'vmo_get_pending_shipping_orders');

// === VMO Pay (Stripe Express) Integration ===

add_action('wp_ajax_vmo_pay_connect_onboarding', 'vmo_pay_connect_onboarding');
function vmo_pay_connect_onboarding() {
    check_ajax_referer('vmo_nonce', 'nonce');
    if (!current_user_can('manage_options')) wp_send_json_error(['message' => 'Unauthorized']);

    $user_id = get_current_user_id();
    $stripe_secret = get_option('vmo_stripe_secret_key', '');

    // If Stripe API key is configured, create a real Connect Account + Account Link
    if (!empty($stripe_secret) && class_exists('Stripe\\Stripe')) {
        try {
            \Stripe\Stripe::setApiKey($stripe_secret);

            // Check if user already has a Connect account
            $existing_account_id = get_user_meta($user_id, 'vmo_pay_account_id', true);

            if (empty($existing_account_id)) {
                // Create a new Express Connect account
                $account = \Stripe\Account::create([
                    'type' => 'express',
                    'metadata' => [
                        'vmo_user_id' => $user_id,
                        'wp_site' => get_site_url(),
                    ],
                ]);
                $existing_account_id = $account->id;
                update_user_meta($user_id, 'vmo_pay_account_id', $existing_account_id);
            }

            // Create an Account Link for onboarding
            $return_url = admin_url('admin.php?page=vmo-dashboard&vmo_pay=return');
            $refresh_url = admin_url('admin.php?page=vmo-dashboard&vmo_pay=refresh');

            $account_link = \Stripe\AccountLink::create([
                'account' => $existing_account_id,
                'refresh_url' => $refresh_url,
                'return_url' => $return_url,
                'type' => 'account_onboarding',
            ]);

            update_user_meta($user_id, 'vmo_pay_status', 'onboarding');

            wp_send_json_success([
                'url' => $account_link->url,
                'message' => 'Redirecting to Stripe Connect secure onboarding...'
            ]);

        } catch (\Exception $e) {
            if (defined('WP_DEBUG') && WP_DEBUG) error_log('VMO Pay Connect Error: ' . $e->getMessage());
            wp_send_json_error([
                'message' => 'Stripe Connect error: ' . $e->getMessage()
            ]);
        }
    } else {
        // No Stripe key configured — inform the admin
        update_user_meta($user_id, 'vmo_pay_status', 'not_connected');
        wp_send_json_error([
            'message' => 'Stripe API key not configured. Go to VMO Settings → Payments and enter your Stripe Secret Key to enable VMO Pay.'
        ]);
    }
}

add_action('wp_ajax_vmo_pay_get_status', 'vmo_pay_get_status');
function vmo_pay_get_status() {
    check_ajax_referer('vmo_nonce', 'nonce');
    $user_id = get_current_user_id();
    $status = get_user_meta($user_id, 'vmo_pay_status', true) ?: 'not_connected';
    $account_id = get_user_meta($user_id, 'vmo_pay_account_id', true);
    
    wp_send_json_success([
        'status' => $status,
        'account_id' => $account_id ? '****' . substr($account_id, -4) : null
    ]);
}

// === Custom Carrier Management ===

add_action('wp_ajax_vmo_save_custom_carrier', 'vmo_save_custom_carrier');
function vmo_save_custom_carrier() {
    check_ajax_referer('vmo_nonce', 'nonce');
    if (!current_user_can('manage_options')) wp_send_json_error(['message' => 'Unauthorized']);

    $carrier_json = wp_unslash($_POST['carrier'] ?? '');
    $carrier_data = json_decode($carrier_json, true);
    
    if (!$carrier_data || empty($carrier_data['name'])) {
        wp_send_json_error(['message' => 'Invalid carrier data']);
    }

    $carriers = get_option('vmo_custom_carriers', []);
    if (!is_array($carriers)) $carriers = [];
    
    // Generate ID if new
    if (empty($carrier_data['id'])) {
        $carrier_data['id'] = 'custom_' . uniqid();
        $carrier_data['created_at'] = current_time('mysql');
        $carrier_data['enabled'] = true;
    } else {
        // Preserve existing fields if editing
        foreach ($carriers as $existing) {
            if ($existing['id'] === $carrier_data['id']) {
                $carrier_data = array_merge($existing, $carrier_data);
                break;
            }
        }
    }

    // Update or add
    $found = false;
    foreach ($carriers as $key => $c) {
        if ($c['id'] === $carrier_data['id']) {
            $carriers[$key] = $carrier_data;
            $found = true;
            break;
        }
    }
    
    if (!$found) {
        $carriers[] = $carrier_data;
    }

    update_option('vmo_custom_carriers', $carriers);
    wp_send_json_success(['message' => 'Carrier saved', 'carrier' => $carrier_data]);
}

add_action('wp_ajax_vmo_get_custom_carriers', 'vmo_get_custom_carriers');
function vmo_get_custom_carriers() {
    check_ajax_referer('vmo_nonce', 'nonce');
    $carriers = get_option('vmo_custom_carriers', []);
    if (!is_array($carriers)) $carriers = [];
    wp_send_json_success($carriers);
}

add_action('wp_ajax_vmo_test_custom_carrier_api', 'vmo_test_custom_carrier_api');
function vmo_test_custom_carrier_api() {
    check_ajax_referer('vmo_nonce', 'nonce');
    $endpoint = esc_url_raw(wp_unslash($_POST['endpoint'] ?? ''));
    
    if (empty($endpoint)) {
        wp_send_json_error(['message' => 'No endpoint provided']);
    }

    // Basic connectivity check
    $response = wp_remote_get($endpoint, ['timeout' => 10]);
    
    if (is_wp_error($response)) {
        wp_send_json_error(['message' => $response->get_error_message()]);
    }
    
    wp_send_json_success(['message' => 'Connection successful']);
}

add_action('wp_ajax_vmo_toggle_custom_carrier', 'vmo_toggle_custom_carrier');
function vmo_toggle_custom_carrier() {
    check_ajax_referer('vmo_nonce', 'nonce');
    $carrier_id = sanitize_key(wp_unslash($_POST['carrier_id'] ?? ''));
    $enabled = sanitize_text_field(wp_unslash($_POST['enabled'] ?? '')) === 'true';
    
    $carriers = get_option('vmo_custom_carriers', []);
    if (!is_array($carriers)) $carriers = [];

    foreach ($carriers as &$c) {
        if ($c['id'] === $carrier_id) {
            $c['enabled'] = $enabled;
            update_option('vmo_custom_carriers', $carriers);
            wp_send_json_success();
        }
    }
    wp_send_json_error(['message' => 'Carrier not found']);
}

add_action('wp_ajax_vmo_delete_custom_carrier', 'vmo_delete_custom_carrier');
function vmo_delete_custom_carrier() {
    check_ajax_referer('vmo_nonce', 'nonce');
    $carrier_id = sanitize_key(wp_unslash($_POST['carrier_id'] ?? ''));
    
    $carriers = get_option('vmo_custom_carriers', []);
    if (!is_array($carriers)) $carriers = [];

    $new_carriers = [];
    foreach ($carriers as $c) {
        if ($c['id'] !== $carrier_id) {
            $new_carriers[] = $c;
        }
    }
    
    update_option('vmo_custom_carriers', $new_carriers);
    wp_send_json_success();
}

// Get Shipping Analytics
function vmo_get_shipping_analytics() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    
    global $wpdb;
    $user_id = get_current_user_id();
    
    $analytics = [
        'week_shipped' => 0,
        'avg_fulfillment_time' => '—',
        'ai_savings' => 0,
        'on_time_rate' => 0,
        'cost_breakdown' => []
    ];
    
    // Check for shipping analytics table
    $shipping_table = $wpdb->prefix . 'vmo_shipping_analytics';
    $table_exists = vmo_table_exists($shipping_table);
    
    if (!$table_exists) {
        // Create shipping analytics table
        vmo_create_shipping_analytics_table();
    }
    
    // Get this week's data
    $week_start = date('Y-m-d', strtotime('monday this week'));
    $week_end = date('Y-m-d', strtotime('sunday this week'));
    
    $week_data = $wpdb->get_row($wpdb->prepare("
        SELECT 
            COUNT(*) as shipped_count,
            AVG(fulfillment_hours) as avg_hours,
            SUM(ai_savings) as total_savings,
            AVG(on_time_delivery) as on_time_rate
        FROM {$shipping_table}
        WHERE user_id = %d 
        AND shipped_date BETWEEN %s AND %s
    ", $user_id, $week_start, $week_end));
    
    if ($week_data) {
        $analytics['week_shipped'] = (int) $week_data->shipped_count;
        $analytics['avg_fulfillment_time'] = $week_data->avg_hours ? round($week_data->avg_hours, 1) . ' hours' : '—';
        $analytics['ai_savings'] = round((float) $week_data->total_savings, 2);
        $analytics['on_time_rate'] = round((float) $week_data->on_time_rate * 100, 1);
    }
    
    // Get cost breakdown by carrier
    $cost_breakdown = $wpdb->get_results($wpdb->prepare("
        SELECT 
            carrier,
            SUM(shipping_cost) as total_cost
        FROM {$shipping_table}
        WHERE user_id = %d 
        AND shipped_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
        GROUP BY carrier
    ", $user_id), OBJECT_K);
    
    foreach ($cost_breakdown as $carrier => $data) {
        $analytics['cost_breakdown'][$carrier] = (float) $data->total_cost;
    }
    
    wp_send_json_success($analytics);
}

// Get AI Shipping Recommendations
function vmo_get_shipping_ai_recommendations() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    $user_id = get_current_user_id();
    global $wpdb;

    // Load saved recommendations from user meta (cache)
    $saved = get_user_meta($user_id, 'vmo_shipping_recommendations', true);
    if (!is_array($saved)) $saved = [];

    // If no saved recommendations, generate a fresh set
    if (empty($saved)) {
        $saved = vmo_build_shipping_recommendations($user_id);
        if (!empty($saved)) {
            update_user_meta($user_id, 'vmo_shipping_recommendations', $saved);
        }
    }

    wp_send_json_success([
        'recommendations' => $saved,
        'generated_at' => get_user_meta($user_id, 'vmo_shipping_recs_generated_at', true) ?: current_time('mysql'),
        'message' => ''
    ]);
}

// Generate New Shipping Recommendations (force re-generate)
function vmo_generate_shipping_recommendations() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    $user_id = get_current_user_id();
    $recs = vmo_build_shipping_recommendations($user_id);
    update_user_meta($user_id, 'vmo_shipping_recommendations', $recs);
    update_user_meta($user_id, 'vmo_shipping_recs_generated_at', current_time('mysql'));

    wp_send_json_success([
        'recommendations' => $recs,
        'generated_at' => current_time('mysql'),
        'message' => 'Recommendations regenerated based on current order and carrier data.'
    ]);
}

// Approve Shipping Recommendation
function vmo_approve_shipping_recommendation() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    $rec_id = sanitize_text_field($_POST['recommendation_id'] ?? '');
    if (empty($rec_id)) {
        wp_send_json_error(['message' => 'Recommendation ID required']);
    }

    $user_id = get_current_user_id();
    $recs = get_user_meta($user_id, 'vmo_shipping_recommendations', true);
    if (!is_array($recs)) {
        wp_send_json_error(['message' => 'No recommendations found']);
    }

    $found = false;
    foreach ($recs as &$rec) {
        if (($rec['id'] ?? '') === $rec_id) {
            $rec['status'] = 'approved';
            $rec['approved_at'] = current_time('mysql');
            $found = true;
            break;
        }
    }
    unset($rec);

    if (!$found) {
        wp_send_json_error(['message' => 'Recommendation not found']);
    }

    update_user_meta($user_id, 'vmo_shipping_recommendations', $recs);
    wp_send_json_success(['message' => 'Recommendation approved', 'recommendations' => $recs]);
}

// Apply All Shipping Optimizations (batch-approve all pending)
function vmo_apply_all_shipping_optimizations() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    $user_id = get_current_user_id();
    $recs = get_user_meta($user_id, 'vmo_shipping_recommendations', true);
    if (!is_array($recs) || empty($recs)) {
        wp_send_json_error(['message' => 'No recommendations to apply']);
    }

    $applied = 0;
    foreach ($recs as &$rec) {
        if (($rec['status'] ?? 'pending') === 'pending') {
            $rec['status'] = 'approved';
            $rec['approved_at'] = current_time('mysql');
            $applied++;
        }
    }
    unset($rec);

    update_user_meta($user_id, 'vmo_shipping_recommendations', $recs);
    wp_send_json_success([
        'message' => "{$applied} optimization(s) approved and applied.",
        'applied_count' => $applied,
        'recommendations' => $recs
    ]);
}

/**
 * Build shipping recommendations by analyzing real order data, carrier settings, and analytics.
 */
function vmo_build_shipping_recommendations($user_id) {
    global $wpdb;
    $recs = [];

    // ── Gather order data ──
    $orders_table = $wpdb->prefix . 'vmo_internal_store_orders';
    $has_orders = vmo_table_exists($orders_table);
    $pending_count = 0;
    $total_order_value = 0;
    $distinct_destinations = 0;
    $orders_without_carrier = 0;
    if ($has_orders) {
        $pending_count = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$orders_table} WHERE user_id = %d AND shipping_status = 'pending'", $user_id
        ));
        $total_order_value = (float) $wpdb->get_var($wpdb->prepare(
            "SELECT COALESCE(SUM(total_amount), 0) FROM {$orders_table} WHERE user_id = %d AND status IN ('processing','paid')", $user_id
        ));
        $distinct_destinations = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(DISTINCT shipping_address) FROM {$orders_table} WHERE user_id = %d AND status IN ('processing','paid') AND shipping_status = 'pending'", $user_id
        ));
        $orders_without_carrier = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$orders_table} WHERE user_id = %d AND shipping_status = 'pending' AND (carrier IS NULL OR carrier = '')", $user_id
        ));
    }

    // ── Gather analytics data ──
    $analytics_table = $wpdb->prefix . 'vmo_shipping_analytics';
    $has_analytics = vmo_table_exists($analytics_table);
    $avg_cost = 0;
    $avg_fulfillment_hrs = 0;
    $on_time_pct = 0;
    $top_carrier = '';
    if ($has_analytics) {
        $stats = $wpdb->get_row($wpdb->prepare(
            "SELECT AVG(shipping_cost) as avg_cost, AVG(fulfillment_hours) as avg_hrs, AVG(on_time_delivery)*100 as on_time
             FROM {$analytics_table} WHERE user_id = %d AND shipped_date >= DATE_SUB(CURDATE(), INTERVAL 90 DAY)", $user_id
        ));
        if ($stats) {
            $avg_cost = round((float) $stats->avg_cost, 2);
            $avg_fulfillment_hrs = round((float) $stats->avg_hrs, 1);
            $on_time_pct = round((float) $stats->on_time, 1);
        }
        $top_carrier_row = $wpdb->get_var($wpdb->prepare(
            "SELECT carrier FROM {$analytics_table} WHERE user_id = %d GROUP BY carrier ORDER BY COUNT(*) DESC LIMIT 1", $user_id
        ));
        $top_carrier = $top_carrier_row ?: '';
    }

    // ── Gather carrier settings ──
    $shipping_settings = get_user_meta($user_id, 'vmo_shipping_settings', true);
    $enabled_carriers = [];
    if (is_array($shipping_settings) && isset($shipping_settings['carriers'])) {
        foreach ($shipping_settings['carriers'] as $c) {
            if (!empty($c['enabled'])) $enabled_carriers[] = $c['name'] ?? $c['id'] ?? '';
        }
    }

    // ── Build recommendations based on data patterns ──
    $rec_id = 1;

    // 1. Pending orders need attention
    if ($pending_count > 0) {
        $recs[] = [
            'id' => 'ship_rec_' . $rec_id++,
            'type' => 'fulfillment',
            'priority' => $pending_count > 5 ? 'high' : 'medium',
            'title' => "Process {$pending_count} pending shipment(s)",
            'description' => "You have {$pending_count} order(s) awaiting shipment" . ($orders_without_carrier > 0 ? " ({$orders_without_carrier} without a carrier assigned)" : '') . '. Review and approve them to maintain delivery promises.',
            'estimated_savings' => 0,
            'status' => 'pending',
        ];
    }

    // 2. No carriers configured
    if (empty($enabled_carriers)) {
        $recs[] = [
            'id' => 'ship_rec_' . $rec_id++,
            'type' => 'setup',
            'priority' => 'high',
            'title' => 'Configure shipping carriers',
            'description' => 'No shipping carriers are enabled. Go to Shipping Settings and enable at least one carrier (FedEx, UPS, DHL, USPS, etc.) to start processing shipments.',
            'estimated_savings' => 0,
            'status' => 'pending',
        ];
    }

    // 3. Carrier diversification
    if (count($enabled_carriers) === 1 && $has_analytics && $avg_cost > 0) {
        $recs[] = [
            'id' => 'ship_rec_' . $rec_id++,
            'type' => 'cost_optimization',
            'priority' => 'medium',
            'title' => 'Diversify shipping carriers for better rates',
            'description' => "You're only using {$enabled_carriers[0]}. Adding a second carrier could save 10-20% on shipping costs. Your current average cost is \${$avg_cost} per shipment.",
            'estimated_savings' => round($avg_cost * 0.15 * max(1, $pending_count), 2),
            'status' => 'pending',
        ];
    }

    // 4. Fulfillment speed
    if ($avg_fulfillment_hrs > 48) {
        $recs[] = [
            'id' => 'ship_rec_' . $rec_id++,
            'type' => 'speed',
            'priority' => 'medium',
            'title' => 'Reduce fulfillment time',
            'description' => "Your average fulfillment time is {$avg_fulfillment_hrs} hours. Industry standard is under 24 hours. Consider batch-processing orders daily or exploring fulfillment partners (ShipBob, Easyship).",
            'estimated_savings' => 0,
            'status' => 'pending',
        ];
    }

    // 5. On-time delivery
    if ($has_analytics && $on_time_pct > 0 && $on_time_pct < 90) {
        $recs[] = [
            'id' => 'ship_rec_' . $rec_id++,
            'type' => 'reliability',
            'priority' => 'high',
            'title' => 'Improve on-time delivery rate',
            'description' => "Your on-time delivery rate is {$on_time_pct}% (target: 95%+). Consider upgrading shipping service tiers or switching carriers for underperforming routes.",
            'estimated_savings' => 0,
            'status' => 'pending',
        ];
    }

    // 6. Multiple destinations — consolidation
    if ($distinct_destinations > 3) {
        $recs[] = [
            'id' => 'ship_rec_' . $rec_id++,
            'type' => 'cost_optimization',
            'priority' => 'low',
            'title' => 'Consider regional fulfillment',
            'description' => "You ship to {$distinct_destinations} distinct destinations. Regional fulfillment centers or a multi-warehouse provider like ShipBob could cut transit times and costs.",
            'estimated_savings' => round($total_order_value * 0.02, 2),
            'status' => 'pending',
        ];
    }

    // 7. High-value orders need insurance
    if ($total_order_value > 500) {
        $recs[] = [
            'id' => 'ship_rec_' . $rec_id++,
            'type' => 'risk',
            'priority' => 'low',
            'title' => 'Add shipping insurance for high-value orders',
            'description' => 'You have $' . number_format($total_order_value, 2) . ' in pending order value. Shipping insurance typically costs 1-3% and protects against loss or damage.',
            'estimated_savings' => 0,
            'status' => 'pending',
        ];
    }

    // If nothing to recommend, give a positive status message
    if (empty($recs) && $has_orders) {
        $recs[] = [
            'id' => 'ship_rec_' . $rec_id++,
            'type' => 'status',
            'priority' => 'low',
            'title' => 'Shipping operations look good',
            'description' => 'No immediate optimizations needed. Your shipping workflow is running smoothly. Check back after processing more orders for data-driven suggestions.',
            'estimated_savings' => 0,
            'status' => 'info',
        ];
    }

    return $recs;
}

/* --------------------------------------------------------------
   SHIPPING HELPER FUNCTIONS
---------------------------------------------------------------- */

// Create shipping task for AI processing
function vmo_create_shipping_task($user_id, $order_id, $order) {
    global $wpdb;
    
    $tasks_table = $wpdb->prefix . 'vmo_ai_tasks';
    
    // Create AI tasks table if it doesn't exist
    $table_exists = vmo_table_exists($tasks_table);
    if (!$table_exists) {
        vmo_create_ai_tasks_table();
    }
    
    // Insert shipping task
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $wpdb->insert(
        $tasks_table,
        [
            'user_id' => $user_id,
            'task_type' => 'shipping',
            'task_data' => json_encode([
                'order_id' => $order_id,
                'customer_name' => $order->customer_name,
                'customer_email' => $order->customer_email,
                'amount' => $order->amount,
                'shipping_address' => $order->shipping_address ?? ''
            ]),
            'status' => 'pending',
            'priority' => 'high',
            'created_at' => current_time('mysql')
        ],
        ['%d', '%s', '%s', '%s', '%s', '%s']
    );
}

// Create AI tasks table
function vmo_create_ai_tasks_table() {
    global $wpdb;
    
    $table_name = $wpdb->prefix . 'vmo_ai_tasks';
    
    $charset_collate = $wpdb->get_charset_collate();
    
    $sql = "CREATE TABLE $table_name (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        user_id bigint(20) NOT NULL,
        task_type varchar(50) NOT NULL,
        task_data longtext NOT NULL,
        status varchar(20) DEFAULT 'pending',
        priority varchar(20) DEFAULT 'medium',
        results longtext NULL,
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        completed_at datetime NULL,
        PRIMARY KEY (id),
        KEY user_id (user_id),
        KEY task_type (task_type),
        KEY status (status)
    ) $charset_collate;";
    
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
}

// Create shipping analytics table
function vmo_create_shipping_analytics_table() {
    global $wpdb;
    
    $table_name = $wpdb->prefix . 'vmo_shipping_analytics';
    
    $charset_collate = $wpdb->get_charset_collate();
    
    $sql = "CREATE TABLE $table_name (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        user_id bigint(20) NOT NULL,
        order_id varchar(100) NOT NULL,
        carrier varchar(50) NOT NULL,
        service_type varchar(100) NOT NULL,
        shipping_cost decimal(10,2) NOT NULL,
        fulfillment_hours decimal(5,2) NOT NULL,
        on_time_delivery tinyint(1) DEFAULT 0,
        ai_savings decimal(10,2) DEFAULT 0,
        tracking_number varchar(255) NULL,
        shipped_date datetime NOT NULL,
        delivered_date datetime NULL,
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY user_id (user_id),
        KEY carrier (carrier),
        KEY shipped_date (shipped_date)
    ) $charset_collate;";
    
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
}

// Generate shipping recommendations based on user profile and patterns
function vmo_generate_ai_shipping_recommendations($user_id, $consultation_profile) {
    return vmo_build_shipping_recommendations($user_id);
}

// Apply shipping optimization
function vmo_apply_shipping_optimization($user_id, $recommendation_id) {
    return [
        'success' => false,
        'error' => 'Shipping optimizations are disabled until real carrier/fulfillment integrations are implemented.',
        'savings' => 0,
    ];
}

// ==========================================
// EMAIL TOKEN SYSTEM
// ==========================================

/**
 * Send email with token deduction
 * 
 * @param string $to Recipient email
 * @param string $subject Email subject
 * @param string $message Email body
 * @param array|string $headers Optional headers
 * @param array $attachments Optional attachments
 * @return bool True if sent, false otherwise
 */
function vmo_send_email_with_tokens($to, $subject, $message, $headers = '', $attachments = array()) {
    $user_id = get_current_user_id();
    if (!$user_id) {
        // Fallback for system emails or non-logged in users (optional: charge admin or skip)
        return wp_mail($to, $subject, $message, $headers, $attachments);
    }

    // Check token balance
    $balance = vmo_get_token_balance($user_id);
    $cost = defined('VMO_TOKENS_EMAIL') ? VMO_TOKENS_EMAIL : 50;

    if ($balance < $cost) {
        if (defined('WP_DEBUG') && WP_DEBUG) error_log("VMO Email Error: Insufficient tokens for user $user_id. Required: $cost, Available: $balance");
        return false;
    }

    // Deduct tokens
    if (vmo_deduct_tokens($user_id, $cost, 'email_sent', "Email to $to: $subject")) {
        return wp_mail($to, $subject, $message, $headers, $attachments);
    }

    return false;
}

// ==========================================
// SHIPPING AUTOMATION
// ==========================================

/**
 * Auto-configure shipping based on business type
 * 
 * @param array $business_data
 * @return array Configuration result
 */
function vmo_auto_configure_shipping($business_data) {
    $user_id = get_current_user_id();
    if (!$user_id) return ['success' => false, 'message' => 'No user logged in'];

    $type = strtolower($business_data['business_type'] ?? '');
    $industry = strtolower($business_data['industry'] ?? '');
    
    // Determine needs
    $needs_shipping = false;
    $carriers = [];
    
    if (strpos($type, 'retail') !== false || strpos($type, 'ecommerce') !== false || strpos($type, 'product') !== false || strpos($type, 'store') !== false) {
        $needs_shipping = true;
        $carriers = ['usps' => true, 'ups' => true]; // Standard for retail
    } elseif (strpos($type, 'service') !== false && strpos($industry, 'legal') !== false) {
        $needs_shipping = true;
        $carriers = ['fedex' => true]; // Documents often go FedEx
    }

    if ($needs_shipping) {
        $settings = [
            'carriers' => [],
            'fulfillment' => [],
            'updated_at' => current_time('mysql'),
            'auto_configured' => true
        ];

        $all_carriers = ['fedex' => 'FedEx', 'ups' => 'UPS', 'usps' => 'USPS', 'dhl' => 'DHL'];
        foreach ($all_carriers as $id => $name) {
            $settings['carriers'][] = [
                'id' => $id,
                'name' => $name,
                'enabled' => isset($carriers[$id])
            ];
        }
        
        update_user_meta($user_id, 'vmo_shipping_settings', $settings);
        update_user_meta($user_id, 'vmo_shipping_integration_active', false);
        
        return [
            'success' => true, 
            'message' => 'Shipping profile configured based on the business profile. Live carrier integration stays inactive until provider credentials and endpoints are added.',
            'carriers' => array_keys($carriers),
            'integration_active' => false,
        ];
    }

    return ['success' => true, 'message' => 'Shipping configuration not required for this business type.'];
}

// === Global Shipping Configuration ===

add_action('wp_ajax_vmo_save_global_shipping', 'vmo_save_global_shipping');
function vmo_save_global_shipping() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }

    $user_id = get_current_user_id();
    $settings_json = isset($_POST['settings']) ? stripslashes((string) $_POST['settings']) : '';
    $settings = json_decode($settings_json, true);
    
    if (!$settings) {
        wp_send_json_error(['message' => 'Invalid settings data']);
    }

    // Support lightweight updates from region toggles: {carrier_id, enabled}
    if (isset($settings['carrier_id'])) {
        $carrier_id = sanitize_text_field((string) $settings['carrier_id']);
        $enabled = !empty($settings['enabled']);
        $existing = get_user_meta($user_id, 'vmo_global_shipping_settings', true);
        if (!is_array($existing)) {
            $existing = ['carriers' => [], 'trade_settings' => []];
        }
        if (!isset($existing['carriers']) || !is_array($existing['carriers'])) {
            $existing['carriers'] = [];
        }
        $existing['carriers'][$carrier_id] = $enabled;
        update_user_meta($user_id, 'vmo_global_shipping_settings', $existing);
        wp_send_json_success(['message' => 'Global settings saved', 'settings' => $existing]);
    }

    update_user_meta($user_id, 'vmo_global_shipping_settings', $settings);
    wp_send_json_success(['message' => 'Global settings saved', 'settings' => $settings]);
}

add_action('wp_ajax_vmo_load_global_shipping', 'vmo_load_global_shipping');
function vmo_load_global_shipping() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    $user_id = get_current_user_id();
    $settings = get_user_meta($user_id, 'vmo_global_shipping_settings', true);
    if (!is_array($settings)) $settings = [];
    wp_send_json_success($settings);
}

add_action('wp_ajax_vmo_test_global_carriers', 'vmo_test_global_carriers');
function vmo_test_global_carriers() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (!vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Invalid nonce', 'status' => 'invalid_nonce'], 403);
    }
    $user_id = get_current_user_id();

    $settings = get_user_meta($user_id, 'vmo_global_shipping_settings', true);
    $carriers = $settings['carriers'] ?? [];

    $results = [];
    $regions = [
        'asia-sf-express' => 'Asia Pacific (SF Express)',
        'asia-japan-post' => 'Japan (Japan Post)',
        'asia-australia-post' => 'Oceania (Australia Post)',
        'africa-aramex' => 'Middle East (Aramex)',
        'africa-fastway' => 'Africa (Fastway)',
        'sa-correios' => 'Brazil (Correios)',
        'sa-mercado' => 'South America (MercadoEnvíos)'
    ];

    foreach ($regions as $key => $label) {
        if (!empty($carriers[$key])) {
            $results[$label] = [
                'status' => 'configured',
                'connected' => false,
                'latency' => '',
                'coverage' => 'Configured',
                'message' => 'Configured in global routing. Live carrier API testing is not available for this regional carrier.'
            ];
        } else {
            $results[$label] = [
                'status' => 'inactive',
                'connected' => false,
                'message' => 'Not configured'
            ];
        }
    }
    
    wp_send_json_success($results);
}

// Import Leads from CSV
add_action('wp_ajax_vmo_import_leads_csv', 'vmo_import_leads_csv');
function vmo_import_leads_csv() {
    check_ajax_referer('vmo_nonce', 'nonce');
    if (!current_user_can('read')) wp_send_json_error(['message' => 'Unauthorized']);
    
    $leads = map_deep(wp_unslash($_POST['leads'] ?? []), 'sanitize_text_field');
    if (empty($leads) || !is_array($leads)) {
        wp_send_json_error(['message' => 'No leads data provided']);
    }
    
    global $wpdb;
    $contacts_table = $wpdb->prefix . 'vmo_contacts';
    $has_contacts_table = vmo_table_exists($contacts_table);
    if (!$has_contacts_table) {
        wp_send_json_error(['message' => 'Contacts table missing. Please deactivate/reactivate the plugin to run database setup.']);
    }
    
    $user_id = get_current_user_id();
    $imported = 0;
    
    foreach ($leads as $lead) {
        if (!is_array($lead)) {
            continue;
        }

        $email = sanitize_email($lead['email'] ?? '');
        if (!$email) {
            continue;
        }

        // Deduplicate by email within this user's contacts
        $exists = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$contacts_table} WHERE user_id = %d AND email = %s",
            $user_id,
            $email
        ));
        if ($exists) {
            continue;
        }

        $full_name = sanitize_text_field($lead['name'] ?? '');
        $company = sanitize_text_field($lead['company'] ?? '');
        $phone = sanitize_text_field($lead['phone'] ?? '');

        if ($full_name === '' && $company !== '') {
            $full_name = $company;
        }

        $contact_uuid = function_exists('wp_generate_uuid4') ? wp_generate_uuid4() : ('c_' . $user_id . '_' . time() . '_' . wp_rand(1000, 9999));

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $ok = $wpdb->insert(
            $contacts_table,
            [
                'user_id' => $user_id,
                'contact_id' => $contact_uuid,
                'source' => 'manual_entry',
                'primary_category' => 'lead',
                'secondary_category' => null,
                'confidence_score' => 0,
                'priority_level' => 'medium',
                'lifecycle_stage' => 'new',
                'first_name' => null,
                'last_name' => null,
                'full_name' => $full_name ?: null,
                'email' => $email,
                'phone' => $phone ?: null,
                'company' => $company ?: null,
                'job_title' => null,
                'address' => null,
                'city' => null,
                'state' => null,
                'zip' => null,
                'country' => null,
                'lead_score' => 0,
                'engagement_level' => 'medium',
                'value_potential' => 50,
                'tags' => null,
                'notes' => null,
                'acquisition_details' => null,
                'categorization_analysis' => null,
                'engagement_strategy' => null,
                'created_at' => current_time('mysql'),
                'updated_at' => current_time('mysql'),
                'last_contact' => null,
            ]
        );
        if ($ok) {
            $imported++;
        }
    }
    
    wp_send_json_success(['message' => "Imported $imported leads", 'count' => $imported]);
}

// Retrieve User Business Phone for Calling
add_action('wp_ajax_vmo_get_user_business_phone', 'vmo_get_user_business_phone_callback');
function vmo_get_user_business_phone_callback() {
    // Basic security check
    if (!is_user_logged_in()) {
        wp_send_json_error(['message' => 'Unauthorized']);
    }
    
    $user_id = get_current_user_id();
    global $wpdb;
    $phone = '';

    // 1. Try dedicated table
    $table = $wpdb->prefix . 'vmo_manager_phone_numbers'; // Correcting from vmo_user_phone_numbers to vmo_manager_phone_numbers as commonly seen in grep? No, grep showed vmo_user_phone_numbers
    // Let's stick to vmo_user_phone_numbers as grepped.
    $table = $wpdb->prefix . 'vmo_user_phone_numbers';

    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from internal constant
    if ($wpdb->get_var("SHOW TABLES LIKE '$table'") === $table) {
        $phone = $wpdb->get_var($wpdb->prepare("SELECT phone_number FROM $table WHERE user_id = %d LIMIT 1", $user_id));
    }

    // 2. Try User Meta
    if (empty($phone)) {
        $phone = get_user_meta($user_id, 'vmo_twilio_number', true);
    }
    if (empty($phone)) {
        $phone = get_user_meta($user_id, 'vmo_business_phone', true);
    }

    if (!empty($phone)) {
        wp_send_json_success(['phone_number' => $phone]);
    } else {
        wp_send_json_error(['message' => 'No number found']);
    }
}

// ── Site Import: Fetch & extract content from an existing website ──
add_action('wp_ajax_vmo_import_site', 'vmo_import_site');
function vmo_import_site() {
    check_ajax_referer('vmo_nonce', 'nonce');
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    $url = esc_url_raw($_POST['url'] ?? '');
    if (empty($url) || !filter_var($url, FILTER_VALIDATE_URL)) {
        wp_send_json_error(['message' => 'Invalid URL provided']);
    }

    // Fetch the page
    $response = wp_remote_get($url, [
        'timeout' => 15,
        'user-agent' => 'VMO-SiteImporter/1.0 (WordPress; +https://getvmo.com)',
        'sslverify' => false,
    ]);

    if (is_wp_error($response)) {
        wp_send_json_error(['message' => 'Could not reach the website: ' . $response->get_error_message()]);
    }

    $status = wp_remote_retrieve_response_code($response);
    if ($status < 200 || $status >= 400) {
        wp_send_json_error(['message' => "Website returned HTTP {$status}"]);
    }

    $html = wp_remote_retrieve_body($response);
    if (empty($html)) {
        wp_send_json_error(['message' => 'Website returned empty content']);
    }

    // Suppress libxml errors
    libxml_use_internal_errors(true);
    $dom = new \DOMDocument();
    $dom->loadHTML('<?xml encoding="UTF-8">' . $html, LIBXML_NOWARNING | LIBXML_NOERROR);
    libxml_clear_errors();

    $xpath = new \DOMXPath($dom);

    // Extract title
    $title_nodes = $dom->getElementsByTagName('title');
    $title = $title_nodes->length > 0 ? trim($title_nodes->item(0)->textContent) : '';

    // Extract meta description
    $meta_desc = '';
    $meta_nodes = $xpath->query('//meta[@name="description"]');
    if ($meta_nodes->length > 0) {
        $meta_desc = $meta_nodes->item(0)->getAttribute('content');
    }

    // Extract OG data
    $og_image = '';
    $og_nodes = $xpath->query('//meta[@property="og:image"]');
    if ($og_nodes->length > 0) {
        $og_image = $og_nodes->item(0)->getAttribute('content');
    }

    // Extract headings (h1-h3)
    $headings = [];
    foreach (['h1', 'h2', 'h3'] as $tag) {
        $nodes = $dom->getElementsByTagName($tag);
        for ($i = 0; $i < min($nodes->length, 10); $i++) {
            $text = trim($nodes->item($i)->textContent);
            if (!empty($text)) {
                $headings[] = ['level' => $tag, 'text' => mb_substr($text, 0, 200)];
            }
        }
    }

    // Extract main text content (strip scripts/styles, get body text)
    $body_nodes = $dom->getElementsByTagName('body');
    $body_text = '';
    if ($body_nodes->length > 0) {
        // Remove script and style elements
        $scripts = $xpath->query('//script | //style | //noscript');
        foreach ($scripts as $script) {
            $script->parentNode->removeChild($script);
        }
        $body_text = trim($body_nodes->item(0)->textContent);
        // Collapse whitespace
        $body_text = preg_replace('/\s+/', ' ', $body_text);
        // Limit to 3000 chars for context
        $body_text = mb_substr($body_text, 0, 3000);
    }

    // Extract images (src + alt)
    $images = [];
    $img_nodes = $dom->getElementsByTagName('img');
    for ($i = 0; $i < min($img_nodes->length, 20); $i++) {
        $src = $img_nodes->item($i)->getAttribute('src');
        $alt = $img_nodes->item($i)->getAttribute('alt');
        if (!empty($src)) {
            // Make relative URLs absolute
            if (strpos($src, '//') === false && strpos($src, 'http') !== 0) {
                $parsed = parse_url($url);
                $base = $parsed['scheme'] . '://' . $parsed['host'];
                $src = $base . '/' . ltrim($src, '/');
            }
            $images[] = ['src' => $src, 'alt' => $alt];
        }
    }

    // Extract navigation links
    $nav_links = [];
    $nav_nodes = $xpath->query('//nav//a | //header//a');
    for ($i = 0; $i < min($nav_nodes->length, 15); $i++) {
        $link_text = trim($nav_nodes->item($i)->textContent);
        $link_href = $nav_nodes->item($i)->getAttribute('href');
        if (!empty($link_text) && !empty($link_href)) {
            $nav_links[] = ['text' => mb_substr($link_text, 0, 100), 'href' => $link_href];
        }
    }

    // Detect technologies / CMS hints
    $technologies = [];
    if (stripos($html, 'wp-content') !== false) $technologies[] = 'WordPress';
    if (stripos($html, 'shopify') !== false) $technologies[] = 'Shopify';
    if (stripos($html, 'wix.com') !== false) $technologies[] = 'Wix';
    if (stripos($html, 'squarespace') !== false) $technologies[] = 'Squarespace';
    if (stripos($html, 'bootstrap') !== false) $technologies[] = 'Bootstrap';
    if (stripos($html, 'jquery') !== false) $technologies[] = 'jQuery';

    // Store extracted data in user meta for later use
    $user_id = get_current_user_id();
    $import_data = [
        'url' => $url,
        'title' => $title,
        'meta_description' => $meta_desc,
        'og_image' => $og_image,
        'headings' => $headings,
        'body_text' => $body_text,
        'images' => array_slice($images, 0, 10),
        'nav_links' => $nav_links,
        'technologies' => $technologies,
        'imported_at' => current_time('mysql'),
    ];
    update_user_meta($user_id, 'vmo_imported_site_data', $import_data);

    wp_send_json_success([
        'title' => $title,
        'meta_description' => $meta_desc,
        'headings_count' => count($headings),
        'headings' => array_slice($headings, 0, 5),
        'images_count' => count($images),
        'nav_links' => $nav_links,
        'technologies' => $technologies,
        'content_length' => strlen($body_text),
        'message' => "Successfully imported content from {$url}",
    ]);
}


// ╔══════════════════════════════════════════════════════════════════════════╗
// ║                     SHORTCODES                                         ║
// ║  [vmo_consultation]  – Embeds the AI consultation wizard (frontend)    ║
// ║  [vmo_business_dashboard] – Embeds the strategic dashboard             ║
// ║  [vmo_pricing_table] – Renders the 5-tier pricing table                ║
// ╚══════════════════════════════════════════════════════════════════════════╝

// ─── [vmo_consultation] ───────────────────────────────────────────────────
// Renders the full AI Business Consultation wizard UI on any page/post.
// Requires a logged-in user with an active plan.
add_shortcode('vmo_consultation', function($atts) {
    if (!is_user_logged_in()) {
        return '<div class="vmo-login-required" style="text-align:center;padding:40px 20px;background:#0d1117;color:#c9d1d9;border-radius:12px;margin:20px 0;">'
             . '<h3 style="color:#58a6ff;">Sign In Required</h3>'
             . '<p>Please <a href="' . esc_url(wp_login_url(get_permalink())) . '" style="color:#58a6ff;">log in</a> to access the AI Business Consultation.</p>'
             . '</div>';
    }

    $user_id = get_current_user_id();
    $tokens  = (int) get_user_meta($user_id, 'vmo_token_balance', true);
    if ($tokens <= 0) {
        return '<div class="vmo-no-tokens" style="text-align:center;padding:40px 20px;background:#0d1117;color:#c9d1d9;border-radius:12px;margin:20px 0;">'
             . '<h3 style="color:#f0883e;">Active Plan Required</h3>'
             . '<p>You need an active VMO plan to start a consultation. <a href="' . esc_url(home_url('/pricing/')) . '" style="color:#58a6ff;">View Plans</a></p>'
             . '</div>';
    }

    // Enqueue consultation assets
    $base = VMO_AI_BUILDER_URL . 'assets/';
    $dir  = VMO_AI_BUILDER_DIR . 'assets/';
    wp_enqueue_style('vmo-chat-formatting',  $base . 'vmo-chat-formatting.css', [], filemtime($dir . 'vmo-chat-formatting.css'));
    wp_enqueue_style('vmo-media-integration', $base . 'vmo-media-integration.css', [], filemtime($dir . 'vmo-media-integration.css'));
    wp_enqueue_script('vmo-conversational',   $base . 'vmo-conversational.js',   [], filemtime($dir . 'vmo-conversational.js'), true);
    wp_enqueue_script('vmo-intelligent-consultation', $base . 'vmo-intelligent-consultation.js', ['jquery', 'vmo-conversational'], filemtime($dir . 'vmo-intelligent-consultation.js'), true);
    wp_enqueue_script('vmo-media-integration', $base . 'vmo-media-integration.js', ['jquery'], filemtime($dir . 'vmo-media-integration.js'), true);
    wp_enqueue_script('vmo-audio-coordinator', $base . 'vmo-audio-coordinator.js', [], filemtime($dir . 'vmo-audio-coordinator.js'), true);

    wp_localize_script('vmo-intelligent-consultation', 'vmo_consultation_data', [
        'ajaxurl'      => admin_url('admin-ajax.php'),
        'nonce'        => wp_create_nonce('vmo_nonce'),
        'user_id'      => $user_id,
        'token_balance' => $tokens,
        'rest_url'     => rest_url('vmo/v1/'),
        'is_frontend'  => true,
    ]);

    ob_start();
    ?>
    <div id="vmo-consultation-root" class="vmo-consultation-embed" data-user-id="<?php echo esc_attr($user_id); ?>">
        <div id="vmo-chat-container" class="vmo-consultation-chat"
             style="min-height:600px;background:#0d1117;border-radius:16px;overflow:hidden;border:1px solid #30363d;">
            <div id="vmo-chat-messages" class="vmo-messages-area" style="padding:24px;min-height:500px;overflow-y:auto;"></div>
            <div id="vmo-chat-input-area" class="vmo-input-area"
                 style="padding:16px 24px;border-top:1px solid #30363d;display:flex;gap:12px;align-items:center;">
                <input type="text" id="vmo-chat-input" placeholder="Describe your business vision..."
                       style="flex:1;padding:12px 16px;border-radius:12px;border:1px solid #30363d;background:#161b22;color:#c9d1d9;font-size:15px;"
                       autocomplete="off" />
                <button id="vmo-chat-send" class="vmo-send-btn"
                        style="padding:12px 24px;border-radius:12px;background:linear-gradient(135deg,#7c3aed,#2563eb);color:#fff;border:none;cursor:pointer;font-weight:600;">
                    Send
                </button>
            </div>
        </div>
    </div>
    <?php
    return ob_get_clean();
});

// ─── [vmo_business_dashboard] ─────────────────────────────────────────────
// Renders the strategic business dashboard for logged-in users.
add_shortcode('vmo_business_dashboard', function($atts) {
    if (!is_user_logged_in()) {
        return '<div class="vmo-login-required" style="text-align:center;padding:40px 20px;background:#0d1117;color:#c9d1d9;border-radius:12px;margin:20px 0;">'
             . '<h3 style="color:#58a6ff;">Sign In Required</h3>'
             . '<p>Please <a href="' . esc_url(wp_login_url(get_permalink())) . '" style="color:#58a6ff;">log in</a> to view your dashboard.</p>'
             . '</div>';
    }

    $user_id = get_current_user_id();

    // Enqueue dashboard script
    $js_file = VMO_AI_BUILDER_DIR . 'assets/vmo-business-dashboard.js';
    if (file_exists($js_file)) {
        wp_enqueue_script(
            'vmo-business-dashboard',
            VMO_AI_BUILDER_URL . 'assets/vmo-business-dashboard.js',
            ['jquery'],
            filemtime($js_file),
            true
        );
        wp_localize_script('vmo-business-dashboard', 'vmo_dashboard_data', [
            'ajaxurl'  => admin_url('admin-ajax.php'),
            'nonce'    => wp_create_nonce('vmo_nonce'),
            'user_id'  => $user_id,
            'rest_url' => rest_url('vmo/v1/'),
        ]);
    }

    // Enqueue dashboard styles
    $css_file = VMO_AI_BUILDER_DIR . 'assets/vmo-professional-summary.css';
    if (file_exists($css_file)) {
        wp_enqueue_style('vmo-professional-summary', VMO_AI_BUILDER_URL . 'assets/vmo-professional-summary.css', [], filemtime($css_file));
    }

    ob_start();
    // Try to load the strategic dashboard template
    $template = VMO_AI_BUILDER_DIR . 'templates/strategic-dashboard.php';
    if (file_exists($template)) {
        include $template;
    } else {
        ?>
        <div id="vmo-business-dashboard-root" class="vmo-dashboard-embed"
             data-user-id="<?php echo esc_attr($user_id); ?>"
             style="min-height:400px;background:#0d1117;border-radius:16px;padding:32px;border:1px solid #30363d;">
            <div class="vmo-dashboard-loading" style="text-align:center;color:#8b949e;padding:60px 0;">
                <div class="vmo-spinner" style="display:inline-block;width:40px;height:40px;border:3px solid #30363d;border-top-color:#58a6ff;border-radius:50%;animation:spin 1s linear infinite;"></div>
                <p style="margin-top:16px;">Loading your business dashboard...</p>
            </div>
        </div>
        <style>@keyframes spin { to { transform: rotate(360deg); } }</style>
        <?php
    }
    return ob_get_clean();
});

// ─── [vmo_pricing_table] ─────────────────────────────────────────────────
// Renders the 5-tier pricing grid with monthly/annual toggle.
// Supports attributes: highlight="pro" annual_discount="20"
add_shortcode('vmo_pricing_table', function($atts) {
    $a = shortcode_atts([
        'highlight'       => 'pro',
        'annual_discount' => 20,
    ], $atts, 'vmo_pricing_table');

    $discount = max(0, min(50, intval($a['annual_discount'])));
    $highlight_plan = strtolower($a['highlight']);

    $plans = [
        [
            'key'       => 'tester',
            'name'      => 'Tester',
            'price'     => 49.99,
            'tokens'    => 36034,
            'type'      => 'one-time',
            'features'  => ['Full AI consultation', 'Website generation', '108 min phone OR 127 pages', 'Email support', 'Basic analytics'],
            'cta'       => 'Get Started',
        ],
        [
            'key'       => 'starter',
            'name'      => 'Starter',
            'price'     => 99.99,
            'tokens'    => 72326,
            'type'      => 'monthly',
            'features'  => ['Everything in Tester', '217 min phone OR 255 pages', 'Custom domain', 'SEO optimization', 'Email marketing tools'],
            'cta'       => 'Start Building',
        ],
        [
            'key'       => 'pro',
            'name'      => 'Professional',
            'price'     => 199.99,
            'tokens'    => 144909,
            'type'      => 'monthly',
            'features'  => ['Everything in Starter', '435 min phone OR 512 pages', 'Deep research (29 chunks)', 'GrapesJS visual editor', 'Priority support', 'Video generation'],
            'cta'       => 'Go Professional',
        ],
        [
            'key'       => 'elite',
            'name'      => 'Elite',
            'price'     => 499.99,
            'tokens'    => 362659,
            'type'      => 'monthly',
            'features'  => ['Everything in Pro', '1,089 min phone OR 1,281 pages', 'White-glove onboarding', 'Multi-site management', 'Legal documents', 'Advanced analytics'],
            'cta'       => 'Go Elite',
        ],
        [
            'key'       => 'enterprise',
            'name'      => 'Enterprise',
            'price'     => 999.99,
            'tokens'    => 500000,
            'type'      => 'monthly',
            'features'  => ['Everything in Elite', '500K tokens/month', 'Dedicated account manager', 'Custom AI fine-tuning', 'SLA guarantee', 'API access'],
            'cta'       => 'Contact Sales',
        ],
    ];

    // Get Stripe price IDs from settings
    $stripe_prices = [];
    foreach (['tester', 'starter', 'pro', 'elite', 'enterprise'] as $plan_key) {
        $stripe_prices[$plan_key] = [
            'monthly' => get_option("vmo_price_{$plan_key}", ''),
            'annual'  => get_option("vmo_price_{$plan_key}_annual", ''),
        ];
    }

    // Enqueue payments JS for Stripe checkout
    $payments_js = VMO_AI_BUILDER_DIR . 'assets/vmo-payments.js';
    if (file_exists($payments_js)) {
        wp_enqueue_script('vmo-payments', VMO_AI_BUILDER_URL . 'assets/vmo-payments.js', ['jquery'], filemtime($payments_js), true);
        wp_localize_script('vmo-payments', 'vmo_payments_data', [
            'ajaxurl' => admin_url('admin-ajax.php'),
            'nonce'   => wp_create_nonce('vmo_nonce'),
        ]);
    }

    ob_start();
    ?>
    <div class="vmo-pricing-table-wrap" style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;max-width:1400px;margin:0 auto;padding:40px 20px;">

        <!-- Billing Toggle -->
        <div class="vmo-billing-toggle" style="text-align:center;margin-bottom:40px;">
            <span class="vmo-billing-label vmo-monthly-label" style="color:#c9d1d9;font-weight:600;font-size:16px;">Monthly</span>
            <label class="vmo-toggle-switch" style="position:relative;display:inline-block;width:60px;height:30px;margin:0 16px;vertical-align:middle;">
                <input type="checkbox" id="vmo-annual-toggle" style="opacity:0;width:0;height:0;" />
                <span style="position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#30363d;border-radius:30px;transition:.3s;">
                    <span class="vmo-toggle-knob" style="position:absolute;content:'';height:24px;width:24px;left:3px;bottom:3px;background:#58a6ff;border-radius:50%;transition:.3s;"></span>
                </span>
            </label>
            <span class="vmo-billing-label vmo-annual-label" style="color:#8b949e;font-weight:600;font-size:16px;">
                Annual <span style="background:linear-gradient(135deg,#7c3aed,#2563eb);color:#fff;padding:2px 8px;border-radius:20px;font-size:12px;margin-left:4px;">Save <?php echo esc_html($discount); ?>%</span>
            </span>
        </div>

        <!-- Plans Grid -->
        <div class="vmo-plans-grid" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:24px;">
        <?php foreach ($plans as $plan):
            $is_highlighted = ($plan['key'] === $highlight_plan);
            $monthly = $plan['price'];
            $annual_monthly = round($monthly * (1 - $discount / 100), 2);
            $border_color = $is_highlighted ? '#7c3aed' : '#30363d';
            $bg = $is_highlighted ? 'background:linear-gradient(180deg,#161b22 0%,#1a1040 100%)' : 'background:#0d1117';
            $stripe_month = esc_attr($stripe_prices[$plan['key']]['monthly'] ?? '');
            $stripe_annual = esc_attr($stripe_prices[$plan['key']]['annual'] ?? '');
        ?>
            <div class="vmo-plan-card" data-plan="<?php echo esc_attr($plan['key']); ?>"
                 data-stripe-monthly="<?php echo $stripe_month; ?>"
                 data-stripe-annual="<?php echo $stripe_annual; ?>"
                 style="<?php echo $bg; ?>;border:2px solid <?php echo $border_color; ?>;border-radius:16px;padding:32px 24px;position:relative;transition:transform .2s,box-shadow .2s;">
                <?php if ($is_highlighted): ?>
                    <div style="position:absolute;top:-14px;left:50%;transform:translateX(-50%);background:linear-gradient(135deg,#7c3aed,#2563eb);color:#fff;padding:4px 16px;border-radius:20px;font-size:13px;font-weight:600;">Most Popular</div>
                <?php endif; ?>
                <h3 style="color:#f0f6fc;font-size:22px;margin:0 0 8px;"><?php echo esc_html($plan['name']); ?></h3>
                <div class="vmo-plan-price" style="margin-bottom:8px;">
                    <span class="vmo-price-monthly" style="color:#f0f6fc;font-size:36px;font-weight:700;">$<?php echo number_format($monthly, 2); ?></span>
                    <span class="vmo-price-annual" style="color:#f0f6fc;font-size:36px;font-weight:700;display:none;">$<?php echo number_format($annual_monthly, 2); ?></span>
                    <?php if ($plan['type'] === 'monthly'): ?>
                        <span style="color:#8b949e;font-size:14px;">/month</span>
                    <?php else: ?>
                        <span style="color:#8b949e;font-size:14px;">one-time</span>
                    <?php endif; ?>
                </div>
                <p style="color:#8b949e;font-size:14px;margin:0 0 20px;"><?php echo number_format($plan['tokens']); ?> AI tokens</p>
                <ul style="list-style:none;padding:0;margin:0 0 24px;">
                    <?php foreach ($plan['features'] as $feat): ?>
                        <li style="color:#c9d1d9;font-size:14px;padding:6px 0;display:flex;align-items:center;gap:8px;">
                            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" style="flex-shrink:0;"><circle cx="8" cy="8" r="8" fill="#238636"/><path d="M5 8l2 2 4-4" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
                            <?php echo esc_html($feat); ?>
                        </li>
                    <?php endforeach; ?>
                </ul>
                <button class="vmo-plan-select-btn" data-plan="<?php echo esc_attr($plan['key']); ?>"
                        style="width:100%;padding:14px;border:none;border-radius:12px;font-size:15px;font-weight:600;cursor:pointer;transition:opacity .2s;
                        <?php echo $is_highlighted ? 'background:linear-gradient(135deg,#7c3aed,#2563eb);color:#fff;' : 'background:#21262d;color:#c9d1d9;border:1px solid #30363d;'; ?>">
                    <?php echo esc_html($plan['cta']); ?>
                </button>
            </div>
        <?php endforeach; ?>
        </div>
    </div>

    <script>
    (function(){
        var toggle = document.getElementById('vmo-annual-toggle');
        if (!toggle) return;
        toggle.addEventListener('change', function(){
            var annual = this.checked;
            document.querySelectorAll('.vmo-price-monthly').forEach(function(el){ el.style.display = annual ? 'none' : ''; });
            document.querySelectorAll('.vmo-price-annual').forEach(function(el){ el.style.display = annual ? '' : 'none'; });
            var knob = document.querySelector('.vmo-toggle-knob');
            if (knob) knob.style.transform = annual ? 'translateX(30px)' : 'translateX(0)';
            var labels = document.querySelectorAll('.vmo-billing-label');
            if (labels.length >= 2) {
                labels[0].style.color = annual ? '#8b949e' : '#c9d1d9';
                labels[1].style.color = annual ? '#c9d1d9' : '#8b949e';
            }
        });
        document.querySelectorAll('.vmo-plan-select-btn').forEach(function(btn){
            btn.addEventListener('click', function(){
                var card = this.closest('.vmo-plan-card');
                var annual = document.getElementById('vmo-annual-toggle').checked;
                var priceId = annual ? card.getAttribute('data-stripe-annual') : card.getAttribute('data-stripe-monthly');
                var planKey = card.getAttribute('data-plan');
                if (priceId && typeof jQuery !== 'undefined') {
                    jQuery.post(vmo_payments_data.ajaxurl, {
                        action: 'vmo_create_checkout_session',
                        nonce: vmo_payments_data.nonce,
                        price_id: priceId,
                        plan: planKey,
                        billing: annual ? 'annual' : 'monthly'
                    }, function(resp) {
                        if (resp.success && resp.data.url) {
                            window.location.href = resp.data.url;
                        } else {
                            alert(resp.data && resp.data.message ? resp.data.message : 'Unable to start checkout. Please try again.');
                        }
                    });
                } else if (!priceId) {
                    alert('This plan is not yet configured. Please contact support.');
                }
            });
        });
    })();
    </script>
    <?php
    return ob_get_clean();
});


// ╔══════════════════════════════════════════════════════════════════════════╗
// ║                  GrapesJS Save / Load AJAX Endpoints                   ║
// ╚══════════════════════════════════════════════════════════════════════════╝

// Save GrapesJS editor state (HTML, CSS, components, styles, assets)
add_action('wp_ajax_vmo_grapes_save', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Forbidden'], 403);
    }
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Security check failed'], 403);
    }

    $user_id    = get_current_user_id();
    $project_id = isset($_POST['project_id']) ? intval($_POST['project_id']) : 0;
    $html       = isset($_POST['html']) ? wp_kses_post($_POST['html']) : '';
    $css        = isset($_POST['css']) ? sanitize_textarea_field($_POST['css']) : '';
    $components = isset($_POST['components']) ? sanitize_textarea_field($_POST['components']) : '[]';
    $styles     = isset($_POST['styles']) ? sanitize_textarea_field($_POST['styles']) : '[]';
    $assets     = isset($_POST['assets']) ? sanitize_textarea_field($_POST['assets']) : '[]';
    $page_slug  = isset($_POST['page']) ? sanitize_key($_POST['page']) : 'index';

    // Validate JSON fields
    foreach (['components', 'styles', 'assets'] as $field) {
        $val = $$field;
        if (!empty($val) && json_decode($val) === null && json_last_error() !== JSON_ERROR_NONE) {
            wp_send_json_error(['message' => "Invalid JSON in {$field}"], 400);
        }
    }

    global $wpdb;
    $table = $wpdb->prefix . 'vmo_grapes_editor_state';

    // Ensure table exists
    if ($wpdb->get_var("SHOW TABLES LIKE '{$table}'") !== $table) {
        $charset = $wpdb->get_charset_collate();
        $wpdb->query("CREATE TABLE {$table} (
            id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            user_id BIGINT UNSIGNED NOT NULL,
            project_id BIGINT UNSIGNED DEFAULT 0,
            page_slug VARCHAR(100) DEFAULT 'index',
            html LONGTEXT,
            css LONGTEXT,
            components LONGTEXT,
            styles LONGTEXT,
            assets LONGTEXT,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            KEY idx_user_project (user_id, project_id),
            KEY idx_page (page_slug)
        ) {$charset};");
    }

    // Upsert: one row per user+project+page
    $existing = $wpdb->get_var($wpdb->prepare(
        "SELECT id FROM {$table} WHERE user_id = %d AND project_id = %d AND page_slug = %s",
        $user_id, $project_id, $page_slug
    ));

    $data = [
        'user_id'    => $user_id,
        'project_id' => $project_id,
        'page_slug'  => $page_slug,
        'html'       => $html,
        'css'        => $css,
        'components' => $components,
        'styles'     => $styles,
        'assets'     => $assets,
    ];

    if ($existing) {
        $wpdb->update($table, $data, ['id' => $existing]);
    } else {
        $wpdb->insert($table, $data);
    }

    wp_send_json_success([
        'message'    => 'Editor state saved successfully',
        'project_id' => $project_id,
        'page'       => $page_slug,
        'saved_at'   => current_time('mysql'),
    ]);
});

// Load GrapesJS editor state
add_action('wp_ajax_vmo_grapes_load', function() {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Forbidden'], 403);
    }
    if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
        wp_send_json_error(['message' => 'Security check failed'], 403);
    }

    $user_id    = get_current_user_id();
    $project_id = isset($_REQUEST['project_id']) ? intval($_REQUEST['project_id']) : 0;
    $page_slug  = isset($_REQUEST['page']) ? sanitize_key($_REQUEST['page']) : 'index';

    global $wpdb;
    $table = $wpdb->prefix . 'vmo_grapes_editor_state';

    if ($wpdb->get_var("SHOW TABLES LIKE '{$table}'") !== $table) {
        wp_send_json_success([
            'html'       => '',
            'css'        => '',
            'components' => '[]',
            'styles'     => '[]',
            'assets'     => '[]',
            'empty'      => true,
        ]);
        return;
    }

    $row = $wpdb->get_row($wpdb->prepare(
        "SELECT html, css, components, styles, assets, updated_at FROM {$table}
         WHERE user_id = %d AND project_id = %d AND page_slug = %s
         ORDER BY updated_at DESC LIMIT 1",
        $user_id, $project_id, $page_slug
    ), ARRAY_A);

    if (!$row) {
        wp_send_json_success([
            'html'       => '',
            'css'        => '',
            'components' => '[]',
            'styles'     => '[]',
            'assets'     => '[]',
            'empty'      => true,
        ]);
        return;
    }

    wp_send_json_success([
        'html'       => $row['html'] ?? '',
        'css'        => $row['css'] ?? '',
        'components' => $row['components'] ?? '[]',
        'styles'     => $row['styles'] ?? '[]',
        'assets'     => $row['assets'] ?? '[]',
        'updated_at' => $row['updated_at'],
        'empty'      => false,
    ]);
});


// ╔══════════════════════════════════════════════════════════════════════════╗
// ║                     CRON JOB HANDLERS                                  ║
// ╚══════════════════════════════════════════════════════════════════════════╝

// ─── Session Cleanup ──────────────────────────────────────────────────────
// Removes consultation sessions older than 30 days and orphaned temp data.
add_action('vmo_cleanup_expired_sessions', function() {
    global $wpdb;

    // Clean up consultation sessions older than 30 days
    $tables_to_clean = [
        $wpdb->prefix . 'vmo_consultation_sessions',
        $wpdb->prefix . 'vmo_conversation_history',
    ];
    foreach ($tables_to_clean as $table) {
        if ($wpdb->get_var("SHOW TABLES LIKE '{$table}'") === $table) {
            $wpdb->query($wpdb->prepare(
                "DELETE FROM {$table} WHERE created_at < %s",
                gmdate('Y-m-d H:i:s', strtotime('-30 days'))
            ));
        }
    }

    // Clean up orphaned transients related to VMO
    $wpdb->query(
        "DELETE FROM {$wpdb->options}
         WHERE option_name LIKE '_transient_vmo_%'
         AND option_name LIKE '_transient_timeout_vmo_%'"
    );

    // Clean up expired research chunks
    $research_table = $wpdb->prefix . 'vmo_deep_research';
    if ($wpdb->get_var("SHOW TABLES LIKE '{$research_table}'") === $research_table) {
        $wpdb->query($wpdb->prepare(
            "DELETE FROM {$research_table} WHERE created_at < %s AND status = 'completed'",
            gmdate('Y-m-d H:i:s', strtotime('-60 days'))
        ));
    }

    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log('[VMO Cron] Expired sessions cleanup completed at ' . current_time('mysql'));
    }
});

// ─── Email Queue Processor ─────────────────────────────────────────────────
// Processes pending outbound emails (Brevo bulk, transactional, follow-ups).
add_action('vmo_process_email_queue', function() {
    global $wpdb;
    $table = $wpdb->prefix . 'vmo_email_queue';

    if ($wpdb->get_var("SHOW TABLES LIKE '{$table}'") !== $table) {
        return;
    }

    // Grab up to 50 pending emails per run
    $pending = $wpdb->get_results($wpdb->prepare(
        "SELECT * FROM {$table} WHERE status = 'pending' AND scheduled_at <= %s ORDER BY priority DESC, created_at ASC LIMIT 50",
        current_time('mysql')
    ));

    if (empty($pending)) return;

    foreach ($pending as $email) {
        // Mark as processing
        $wpdb->update($table, ['status' => 'processing', 'attempts' => $email->attempts + 1], ['id' => $email->id]);

        $success = false;
        try {
            // Use Brevo API if class exists, otherwise wp_mail
            if (class_exists('VMO_Email_Engine')) {
                $engine  = VMO_Email_Engine::get_instance();
                $success = $engine->send_email([
                    'to'      => $email->to_email,
                    'subject' => $email->subject,
                    'body'    => $email->body,
                    'from'    => $email->from_email ?: '',
                    'user_id' => $email->user_id,
                ]);
            } else {
                $headers = ['Content-Type: text/html; charset=UTF-8'];
                if (!empty($email->from_email)) {
                    $headers[] = 'From: ' . sanitize_email($email->from_email);
                }
                $success = wp_mail($email->to_email, $email->subject, $email->body, $headers);
            }
        } catch (Throwable $e) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('[VMO Email Queue] Error sending email #' . $email->id . ': ' . $e->getMessage());
            }
        }

        if ($success) {
            $wpdb->update($table, [
                'status'  => 'sent',
                'sent_at' => current_time('mysql'),
            ], ['id' => $email->id]);
        } else {
            // Retry up to 3 times, then mark failed
            $new_status = ($email->attempts + 1 >= 3) ? 'failed' : 'pending';
            $wpdb->update($table, ['status' => $new_status], ['id' => $email->id]);
        }
    }

    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log('[VMO Cron] Email queue processed: ' . count($pending) . ' emails at ' . current_time('mysql'));
    }
});

// ─── Stripe Subscription Sync ──────────────────────────────────────────────
// Fallback sync for Stripe subscription status changes that may have been
// missed by webhooks (network issues, webhook failures, etc.).
add_action('vmo_sync_stripe_subscriptions', function() {
    $stripe_secret = get_option('vmo_stripe_secret_key', '');
    if (empty($stripe_secret)) return;

    global $wpdb;

    // Get all users with active Stripe subscriptions
    $users_with_subs = $wpdb->get_results(
        "SELECT user_id, meta_value as subscription_id
         FROM {$wpdb->usermeta}
         WHERE meta_key = 'vmo_stripe_subscription_id'
         AND meta_value != ''
         LIMIT 100"
    );

    if (empty($users_with_subs)) return;

    foreach ($users_with_subs as $user_sub) {
        try {
            $response = wp_remote_get(
                "https://api.stripe.com/v1/subscriptions/{$user_sub->subscription_id}",
                [
                    'headers' => [
                        'Authorization' => 'Bearer ' . $stripe_secret,
                    ],
                    'timeout' => 15,
                ]
            );

            if (is_wp_error($response)) continue;

            $body = json_decode(wp_remote_retrieve_body($response), true);
            if (empty($body) || !isset($body['status'])) continue;

            $stripe_status = $body['status'];
            $stored_status = get_user_meta($user_sub->user_id, 'vmo_subscription_status', true);

            if ($stripe_status !== $stored_status) {
                update_user_meta($user_sub->user_id, 'vmo_subscription_status', $stripe_status);

                // Handle cancellations/expirations
                if (in_array($stripe_status, ['canceled', 'unpaid', 'past_due'])) {
                    // Don't remove tokens immediately for past_due — give grace period
                    if ($stripe_status === 'canceled') {
                        update_user_meta($user_sub->user_id, 'vmo_plan_expired_at', current_time('mysql'));
                    }
                }

                // Log the status change
                if (defined('WP_DEBUG') && WP_DEBUG) {
                    error_log("[VMO Stripe Sync] User {$user_sub->user_id}: {$stored_status} → {$stripe_status}");
                }
            }
        } catch (Throwable $e) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('[VMO Stripe Sync] Error: ' . $e->getMessage());
            }
        }
    }
});

// ─── Weekly Analytics Report ────────────────────────────────────────────────
add_action('vmo_weekly_analytics_report', function() {
    if (class_exists('VMO_Analytics_Tracker')) {
        try {
            $tracker = VMO_Analytics_Tracker::get_instance();
            if (method_exists($tracker, 'generate_weekly_report')) {
                $tracker->generate_weekly_report();
            }
        } catch (Throwable $e) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('[VMO Cron] Weekly analytics report error: ' . $e->getMessage());
            }
        }
    }
});

// ─── Weekly SEO Report ──────────────────────────────────────────────────────
add_action('vmo_weekly_seo_report', function() {
    if (class_exists('VMO_SEO_Engine')) {
        try {
            $seo = new VMO_SEO_Engine();
            if (method_exists($seo, 'generate_weekly_report')) {
                $seo->generate_weekly_report();
            }
        } catch (Throwable $e) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('[VMO Cron] Weekly SEO report error: ' . $e->getMessage());
            }
        }
    }
});

// ─── Daily SEO Check ────────────────────────────────────────────────────────
add_action('vmo_daily_seo_check', function() {
    if (class_exists('VMO_SEO_Engine')) {
        try {
            $seo = new VMO_SEO_Engine();
            if (method_exists($seo, 'run_daily_check')) {
                $seo->run_daily_check();
            }
        } catch (Throwable $e) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('[VMO Cron] Daily SEO check error: ' . $e->getMessage());
            }
        }
    }
});

// ─── Daily Competitor Check ─────────────────────────────────────────────────
add_action('vmo_daily_competitor_check', function() {
    if (class_exists('VMO_Competitor_Monitor')) {
        try {
            $monitor = new VMO_Competitor_Monitor();
            if (method_exists($monitor, 'run_daily_check')) {
                $monitor->run_daily_check();
            }
        } catch (Throwable $e) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('[VMO Cron] Daily competitor check error: ' . $e->getMessage());
            }
        }
    }
});


// ─── VPS Teardown on Subscription Cancellation ─────────────────────────────
// Destroys the Vultr VPS instance 7 days after subscription cancellation.
// Scheduled by VMO_QuickLaunch::handle_subscription_deleted().
add_action('vmo_ql_teardown_vps', function($user_id, $domain) {
    // Double-check the subscription is still canceled (user may have reactivated)
    $status = get_user_meta($user_id, 'vmo_subscription_status', true);
    if ($status === 'active' || empty(get_user_meta($user_id, 'vmo_vps_teardown_scheduled', true))) {
        error_log("[VMO VPS Teardown] Skipped for user {$user_id} ({$domain}) — subscription is active or teardown was canceled.");
        return;
    }

    $vultr_key = get_option('vmo_vultr_api_key', '');
    if (empty($vultr_key)) {
        error_log("[VMO VPS Teardown] Cannot destroy VPS for {$domain} — Vultr API key not configured.");
        return;
    }

    // Find the Vultr instance by label matching the domain
    $list_response = wp_remote_get('https://api.vultr.com/v2/instances?tag=vmo-quicklaunch&per_page=100', [
        'headers' => ['Authorization' => 'Bearer ' . $vultr_key],
        'timeout' => 30,
    ]);

    if (is_wp_error($list_response)) {
        error_log("[VMO VPS Teardown] Vultr API error: " . $list_response->get_error_message());
        return;
    }

    $body = json_decode(wp_remote_retrieve_body($list_response), true);
    $instances = $body['instances'] ?? [];

    $instance_id = null;
    foreach ($instances as $inst) {
        $label = $inst['label'] ?? '';
        if (stripos($label, $domain) !== false) {
            $instance_id = $inst['id'];
            break;
        }
    }

    if (!$instance_id) {
        error_log("[VMO VPS Teardown] No Vultr instance found for domain {$domain}.");
        delete_user_meta($user_id, 'vmo_vps_teardown_scheduled');
        return;
    }

    // Destroy the instance
    $destroy_response = wp_remote_request("https://api.vultr.com/v2/instances/{$instance_id}", [
        'method'  => 'DELETE',
        'headers' => ['Authorization' => 'Bearer ' . $vultr_key],
        'timeout' => 30,
    ]);

    if (is_wp_error($destroy_response)) {
        error_log("[VMO VPS Teardown] Failed to destroy instance {$instance_id}: " . $destroy_response->get_error_message());
        return;
    }

    $status_code = wp_remote_retrieve_response_code($destroy_response);
    if ($status_code === 204 || $status_code === 200) {
        error_log("[VMO VPS Teardown] Successfully destroyed VPS {$instance_id} for user {$user_id} ({$domain}).");
        delete_user_meta($user_id, 'vmo_vps_teardown_scheduled');
        update_user_meta($user_id, 'vmo_vps_destroyed_at', current_time('mysql'));

        // Notify user
        $user = get_user_by('ID', $user_id);
        if ($user) {
            wp_mail(
                $user->user_email,
                'VMO Website Removed',
                "Hello,\n\nYour VMO website at {$domain} has been permanently removed due to subscription cancellation.\n\n"
                . "If you'd like to start fresh, you can sign up again at " . home_url('/get-started/') . "\n\n"
                . "— The VMO Team"
            );
        }
    } else {
        error_log("[VMO VPS Teardown] Unexpected response code {$status_code} when destroying instance {$instance_id}.");
    }
}, 10, 2);


// ╔══════════════════════════════════════════════════════════════════════════╗
// ║              AJAX ACTION ALIASES (Spec → Actual Mapping)               ║
// ║  Maps spec-documented action names to their actual handler methods     ║
// ╚══════════════════════════════════════════════════════════════════════════╝

// Research aliases
add_action('wp_ajax_vmo_deep_research_start', function() {
    // Alias → actual deep research trigger
    if (class_exists('VMO_Deep_Research')) {
        $dr = VMO_Deep_Research::get_instance();
        if (method_exists($dr, 'ajax_trigger_research')) {
            $dr->ajax_trigger_research();
            return;
        }
    }
    wp_send_json_error(['message' => 'Deep research handler not available'], 500);
});

add_action('wp_ajax_vmo_deep_research_poll', function() {
    if (class_exists('VMO_Deep_Research')) {
        $dr = VMO_Deep_Research::get_instance();
        if (method_exists($dr, 'ajax_poll_chunk')) {
            $dr->ajax_poll_chunk();
            return;
        }
    }
    wp_send_json_error(['message' => 'Deep research poll handler not available'], 500);
});

// Consultation message alias
add_action('wp_ajax_vmo_send_consultation_message', function() {
    if (class_exists('VMO_Enhanced_Business_Wizard')) {
        $wizard = VMO_Enhanced_Business_Wizard::get_instance();
        if (method_exists($wizard, 'ajax_process_consultation_message')) {
            $wizard->ajax_process_consultation_message();
            return;
        }
    }
    wp_send_json_error(['message' => 'Consultation handler not available'], 500);
});

// Website generation alias
add_action('wp_ajax_vmo_generate_website', function() {
    if (class_exists('VMO_Enhanced_Business_Wizard')) {
        $wizard = VMO_Enhanced_Business_Wizard::get_instance();
        if (method_exists($wizard, 'ajax_generate_business_website_enhanced')) {
            $wizard->ajax_generate_business_website_enhanced();
            return;
        }
    }
    wp_send_json_error(['message' => 'Website generation handler not available'], 500);
});

// Dashboard stats alias
add_action('wp_ajax_vmo_get_dashboard_stats', function() {
    if (class_exists('VMO_Enhanced_Business_Wizard')) {
        $wizard = VMO_Enhanced_Business_Wizard::get_instance();
        if (method_exists($wizard, 'ajax_get_dashboard_stats')) {
            $wizard->ajax_get_dashboard_stats();
            return;
        }
    }
    wp_send_json_error(['message' => 'Dashboard stats handler not available'], 500);
});

// SEO analysis alias
add_action('wp_ajax_vmo_run_seo_analysis', function() {
    if (class_exists('VMO_SEO_Engine')) {
        try {
            $seo = new VMO_SEO_Engine();
            if (method_exists($seo, 'ajax_run_analysis')) {
                $seo->ajax_run_analysis();
                return;
            }
        } catch (Throwable $e) {
            wp_send_json_error(['message' => $e->getMessage()], 500);
        }
    }
    wp_send_json_error(['message' => 'SEO engine not available'], 500);
});