The file /var/www/mssql.danmarcrm.com/dev1/extension2v1/public/api.php has been updated successfully.

        // ==========================================
        // AI Chat Sessions - List recent sessions
        // ==========================================
        case 'ai_chat_sessions':
            $ctxMongo = \EmailIntelligence\MongoDatabase::getInstance();
            $limit = (int)($_GET['limit'] ?? 20);
            $sessions = $ctxMongo->findAll('ai_chat_sessions', [], [
                'sort' => ['updated_at' => -1],
                'limit' => $limit,
            ]);
            // Strip full messages from list view (just return metadata)
            $sessionList = array_map(function($s) {
                return [
                    'session_id' => $s['session_id'] ?? '',
                    'title' => $s['title'] ?? 'Untitled',
                    'agent' => $s['agent'] ?? 'general',
                    'provider' => $s['provider'] ?? '',
                    'model' => $s['model'] ?? '',
                    'contact_name' => $s['contact_name'] ?? null,
                    'contact_id' => $s['contact_id'] ?? null,
                    'context_thread_ids' => $s['context_thread_ids'] ?? [],
                    'message_count' => count($s['messages'] ?? []),
                    'created_at' => $s['created_at'] ?? '',
                    'updated_at' => $s['updated_at'] ?? '',
                ];
            }, $sessions);
            jsonResponse(['success' => true, 'sessions' => $sessionList]);
            break;

        // ==========================================
        // AI Chat Session - Load a specific session
        // ==========================================
        case 'ai_chat_session':
            $sessionId = $_GET['session_id'] ?? '';
            if (!$sessionId) jsonError('session_id required');
            $ctxMongo = \EmailIntelligence\MongoDatabase::getInstance();
            $session = $ctxMongo->findOne('ai_chat_sessions', ['session_id' => $sessionId]);
            if (!$session) jsonError('Session not found', 404);
            jsonResponse(['success' => true, 'session' => [
                'session_id' => $session['session_id'],
                'title' => $session['title'] ?? 'Untitled',
                'agent' => $session['agent'] ?? 'general',
                'provider' => $session['provider'] ?? '',
                'model' => $session['model'] ?? '',
                'contact_id' => $session['contact_id'] ?? null,
                'contact_name' => $session['contact_name'] ?? null,
                'context_thread_ids' => $session['context_thread_ids'] ?? [],
                'messages' => $session['messages'] ?? [],
                'created_at' => $session['created_at'] ?? '',
                'updated_at' => $session['updated_at'] ?? '',
            ]]);
            break;

        // ==========================================
        // AI Chat Session Delete
        // ==========================================
        case 'ai_chat_session_delete':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') jsonError('POST required', 405);
            $jsonData = json_decode(file_get_contents('php://input'), true) ?? [];
            $sessionId = $jsonData['session_id'] ?? '';
            if (!$sessionId) jsonError('session_id required');
            $ctxMongo = \EmailIntelligence\MongoDatabase::getInstance();
            $deleted = $ctxMongo->delete('ai_chat_sessions', ['session_id' => $sessionId]);
            jsonResponse(['success' => true, 'deleted' => $deleted]);
            break;

        // ==========================================
        // AI Contact Memories - Get memories for a contact
        // ==========================================
        case 'ai_contact_memories':
            $contactId = (int)($_GET['contact_id'] ?? 0);
            if (!$contactId) jsonError('contact_id required');
            $ctxMongo = \EmailIntelligence\MongoDatabase::getInstance();
            $memDoc = $ctxMongo->findOne('ai_contact_memory', ['contact_id' => $contactId]);
            jsonResponse([
                'success' => true,
                'contact_id' => $contactId,
                'memories' => $memDoc ? ($memDoc['memories'] ?? []) : [],
            ]);
            break;

        // ==========================================
        // AI Contact Memory Add - Manually add a memory fact
        // ==========================================
        case 'ai_contact_memory_add':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') jsonError('POST required', 405);
            $jsonData = json_decode(file_get_contents('php://input'), true) ?? [];
            $contactId = (int)($jsonData['contact_id'] ?? 0);
            $fact = trim($jsonData['fact'] ?? '');
            if (!$contactId || !$fact) jsonError('contact_id and fact required');
            $ctxMongo = \EmailIntelligence\MongoDatabase::getInstance();
            $existing = $ctxMongo->findOne('ai_contact_memory', ['contact_id' => $contactId]);
            $memories = $existing ? ($existing['memories'] ?? []) : [];
            $newMem = [
                'id' => uniqid('mem_', true),
                'fact' => $fact,
                'source' => 'manual',
                'created_at' => date('Y-m-d H:i:s'),
            ];
            $memories[] = $newMem;
            $ctxMongo->insertOrUpdate('ai_contact_memory', [
                'contact_id' => $contactId,
                'memories' => $memories,
                'updated_at' => date('Y-m-d H:i:s'),
            ], ['contact_id']);
            jsonResponse(['success' => true, 'memory' => $newMem]);
            break;

        // ==========================================
        // AI Contact Memory Delete - Remove a memory fact
        // ==========================================
        case 'ai_contact_memory_delete':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') jsonError('POST required', 405);
            $jsonData = json_decode(file_get_contents('php://input'), true) ?? [];
            $contactId = (int)($jsonData['contact_id'] ?? 0);
            $memoryId = $jsonData['memory_id'] ?? '';
            if (!$contactId || !$memoryId) jsonError('contact_id and memory_id required');
            $ctxMongo = \EmailIntelligence\MongoDatabase::getInstance();
            $existing = $ctxMongo->findOne('ai_contact_memory', ['contact_id' => $contactId]);
            if ($existing && !empty($existing['memories'])) {
                $memories = array_values(array_filter($existing['memories'], function($m) use ($memoryId) {
                    return ($m['id'] ?? '') !== $memoryId;
                }));
                $ctxMongo->insertOrUpdate('ai_contact_memory', [
                    'contact_id' => $contactId,
                    'memories' => $memories,
                    'updated_at' => date('Y-m-d H:i:s'),
                ], ['contact_id']);
            }
            jsonResponse(['success' => true]);
            break;

        // ==========================================
        // Default
        // ==========================================
        default:
            jsonError('Unknown action: ' . $action, 400);
    }

<?php
/**
 * REST API Endpoint
 *
 * Provides JSON API for the web dashboard and external integrations.
 *
 * CALLING MAP:
 * - Entry point: AJAX requests from index.php
 * - Uses: All src/ classes depending on action
 * - Returns: JSON responses
 *
 * ENDPOINTS:
 *   GET  ?action=status           - System status and statistics
 *   GET  ?action=contacts         - List contacts with profiles
 *   GET  ?action=contact&id=X     - Single contact with full profile
 *   GET  ?action=emails&contact=X - Emails for a contact
 *   GET  ?action=email&id=X       - Single email with analysis
 *   GET  ?action=quotes           - All quotes
 *   GET  ?action=filters          - Email filters
 *   POST ?action=add_filter       - Add email filter
 *   POST ?action=remove_filter    - Remove email filter
 *   POST ?action=sync             - Trigger sync
 *   POST ?action=analyze          - Trigger analysis
 *   POST ?action=build_profile    - Build profile for contact
 *
 * @package EmailIntelligence
 */

if (!isset($_GET) and isset($_REQUEST)){
    $_GET = $_REQUEST;
}
ini_set("error_log", "error_logs.log");
file_put_contents('apiLogs.log', print_r($_REQUEST, true) . "\n", FILE_APPEND); /// DAN DO NOT REMOVE

// Error logging file path
define('PHP_ERROR_LOG_FILE', dirname(__DIR__) . '/logs/__php_error.log');

// Custom error handler for non-fatal errors
set_error_handler(function($errno, $errstr, $errfile, $errline) {
    $errorTypes = [
        E_ERROR => 'ERROR',
        E_WARNING => 'WARNING',
        E_PARSE => 'PARSE',
        E_NOTICE => 'NOTICE',
        E_CORE_ERROR => 'CORE_ERROR',
        E_CORE_WARNING => 'CORE_WARNING',
        E_COMPILE_ERROR => 'COMPILE_ERROR',
        E_COMPILE_WARNING => 'COMPILE_WARNING',
        E_USER_ERROR => 'USER_ERROR',
        E_USER_WARNING => 'USER_WARNING',
        E_USER_NOTICE => 'USER_NOTICE',
        E_STRICT => 'STRICT',
        E_RECOVERABLE_ERROR => 'RECOVERABLE_ERROR',
        E_DEPRECATED => 'DEPRECATED',
        E_USER_DEPRECATED => 'USER_DEPRECATED',
    ];

    $type = $errorTypes[$errno] ?? 'UNKNOWN';
    $timestamp = date('Y-m-d H:i:s');
    $logMessage = "[{$timestamp}] PHP {$type}: {$errstr} in {$errfile} on line {$errline}\n";

    file_put_contents(PHP_ERROR_LOG_FILE, $logMessage, FILE_APPEND | LOCK_EX);

    // For fatal-level errors, also output JSON
    if (in_array($errno, [E_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR])) {
        if (!headers_sent()) {
            header('Content-Type: application/json');
            http_response_code(500);
        }
        echo json_encode([
            'error' => true,
            'message' => 'Internal server error',
            'debug' => [
                'type' => $type,
                'message' => $errstr,
                'file' => basename($errfile),
                'line' => $errline
            ]
        ]);
        exit(1);
    }

    return false; // Let PHP handle the error as well
});

// Custom exception handler for uncaught exceptions
set_exception_handler(function($exception) {
    $timestamp = date('Y-m-d H:i:s');
    $logMessage = "[{$timestamp}] UNCAUGHT EXCEPTION: " . get_class($exception) . ": {$exception->getMessage()} in {$exception->getFile()} on line {$exception->getLine()}\n";
    $logMessage .= "Stack trace:\n{$exception->getTraceAsString()}\n\n";

    file_put_contents(PHP_ERROR_LOG_FILE, $logMessage, FILE_APPEND | LOCK_EX);

    if (!headers_sent()) {
        header('Content-Type: application/json');
        http_response_code(500);
    }
    echo json_encode([
        'error' => true,
        'message' => 'Internal server error',
        'debug' => [
            'type' => get_class($exception),
            'message' => $exception->getMessage(),
            'file' => basename($exception->getFile()),
            'line' => $exception->getLine()
        ]
    ]);
    exit(1);
});

// Shutdown function to catch fatal errors
register_shutdown_function(function() {
    $error = error_get_last();
    if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
        $errorTypes = [
            E_ERROR => 'FATAL ERROR',
            E_PARSE => 'PARSE ERROR',
            E_CORE_ERROR => 'CORE ERROR',
            E_COMPILE_ERROR => 'COMPILE ERROR',
        ];

        $type = $errorTypes[$error['type']] ?? 'FATAL';
        $timestamp = date('Y-m-d H:i:s');
        $logMessage = "[{$timestamp}] {$type}: {$error['message']} in {$error['file']} on line {$error['line']}\n";

        file_put_contents(PHP_ERROR_LOG_FILE, $logMessage, FILE_APPEND | LOCK_EX);

        // Clear any previous output
        if (ob_get_level()) {
            ob_end_clean();
        }

        if (!headers_sent()) {
            header('Content-Type: application/json');
            http_response_code(500);
        }
        echo json_encode([
            'error' => true,
            'message' => 'Internal server error',
            'debug' => [
                'type' => $type,
                'message' => $error['message'],
                'file' => basename($error['file']),
                'line' => $error['line']
            ]
        ]);
    }
});

// Load configuration
require_once dirname(__DIR__) . '/config/config.php';
require_once dirname(__DIR__) . '/vendor/autoload.php';

use EmailIntelligence\Database;
use EmailIntelligence\QueryAdapter;
use EmailIntelligence\GmailClient;
use EmailIntelligence\EmailSync;
use EmailIntelligence\AIAnalyzer;
use EmailIntelligence\ThreadAnalyzer;
use EmailIntelligence\ContactAnalyzer;
use EmailIntelligence\ContactProfiler;
use EmailIntelligence\VectorMemory;
use EmailIntelligence\PatternDetector;
use EmailIntelligence\AlertSystem;
use EmailIntelligence\ThreadIdCache;
use EmailIntelligence\ImportantContext;

// Include AI API Opus for multi-provider support
require_once dirname(__DIR__) . '/aiapi/api_opus/src/AIManager.php';

file_put_contents('apiLogs.log', print_r($_REQUEST, true) . "\n", FILE_APPEND);
#exit("test");

// Set JSON response headers
header('Content-Type: application/json');
header('Cache-Control: no-cache, must-revalidate');

/**
 * Send JSON response and exit
 */
function jsonResponse(array $data, int $status = 200): void {
    http_response_code($status);
    echo json_encode($data, JSON_PRETTY_PRINT);
    exit;
}

/**
 * Send error response
 */
function jsonError(string $message, int $status = 400, array $extraData = []): void {
    $response = ['error' => true, 'message' => $message];
    if (!empty($extraData)) {
        $response = array_merge($response, $extraData);
    }
    jsonResponse($response, $status);
}

/**
 * Log AI task execution (request, prompt, response)
 */
function logAITask(string $taskName, array $request, string $prompt, string $response): void {
    $logDir = dirname(__DIR__) . '/logs/ai';

    // Create directory if it doesn't exist
    if (!is_dir($logDir)) {
        @mkdir($logDir, 0755, true);
    }

    $logFile = $logDir . '/' . $taskName . '.log';

    $timestamp = date('Y-m-d H:i:s');
    $requestJson = json_encode($request, JSON_PRETTY_PRINT);
    $responseJson = json_encode($response, JSON_PRETTY_PRINT);

    $logEntry = <<<LOG
================================================================================
TIMESTAMP: {$timestamp}
TASK: {$taskName}
================================================================================

REQUEST:
{$requestJson}

PROMPT SENT TO AI:
{$prompt}

AI RESPONSE:
{$responseJson}

================================================================================

LOG;

    file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
}

/**
 * Resolve a thread ID from cache
 * If the thread ID is a long URL-style ID (FMfcgz...), look up the cached short hex ID.
 * If not in cache, returns the original ID (which may need resolution via API).
 *
 * @param string|null $threadId The thread ID to resolve
 * @return string|null The resolved thread ID (short hex) or original if not cached
 */
function resolveThreadId(?string $threadId): ?string {
    if (!$threadId) return null;

    $cache = ThreadIdCache::getInstance();
    return $cache->resolve($threadId);
}

try {
    $db = Database::getInstance();
    $action = $_GET['action'] ?? $_POST['action'] ?? '';

    switch ($action) {
        // ==========================================
        // System Status
        // ==========================================
        case 'status':
            $syncStatus = $db->fetchOne("SELECT * FROM sync_status WHERE id = 1");

            // Count statistics
            $stats = [
                'total_emails' => $db->fetchColumn("SELECT COUNT(*) FROM emails"),
                'total_contacts' => $db->fetchColumn("SELECT COUNT(*) FROM contacts"),
                'analyzed_emails' => $db->fetchColumn("SELECT COUNT(*) FROM emails WHERE is_analyzed = 1"),
                'pending_analysis' => $db->fetchColumn("SELECT COUNT(*) FROM emails WHERE is_analyzed = 0"),
                'total_quotes' => $db->fetchColumn("SELECT COUNT(*) FROM quotes"),
                'profiles_built' => $db->fetchColumn("SELECT COUNT(*) FROM contact_profiles"),
                'high_risk_contacts' => $db->fetchColumn("SELECT COUNT(*) FROM contact_profiles WHERE risk_level = 'high'"),
            ];

            // Gmail status
            $gmail = new GmailClient();
            $gmailStatus = $gmail->getAuthStatus();

            // Claude status
            $analyzer = new AIAnalyzer();
            $claudeConfigured = $analyzer->isConfigured();

            jsonResponse([
                'sync' => $syncStatus,
                'stats' => $stats,
                'gmail' => $gmailStatus,
                'claude' => ['configured' => $claudeConfigured],
                'timestamp' => date('c')
            ]);
            break;

        // ==========================================
        // List Contacts
        // ==========================================
        case 'contacts':
            $page = max(1, (int)($_GET['page'] ?? 1));
            $limit = min(100, max(1, (int)($_GET['limit'] ?? 20)));
            $offset = ($page - 1) * $limit;
            $search = $_GET['search'] ?? '';
            $sort = $_GET['sort'] ?? 'name';
            $order = strtoupper($_GET['order'] ?? 'DESC') === 'ASC' ? 'ASC' : 'DESC';

            // Convert wildcards: * and % to regex-friendly for QueryAdapter
            $cleanSearch = $search;
            if ($cleanSearch) {
                // Strip wildcard chars — QueryAdapter already does LIKE %search% / regex
                $cleanSearch = str_replace(['*', '%'], '', $cleanSearch);
                $cleanSearch = trim($cleanSearch);
            }

            // Use QueryAdapter for automatic MySQL/MongoDB routing
            $adapter = QueryAdapter::getInstance();
            $result = $adapter->getContactsWithProfiles($cleanSearch, $limit, $offset, $sort, $order);

            jsonResponse([
                'contacts' => $result['contacts'] ?? [],
                'pagination' => [
                    'page' => $page,
                    'limit' => $limit,
                    'total' => (int)($result['total'] ?? 0),
                    'pages' => ceil(($result['total'] ?? 0) / $limit)
                ]
            ]);
            break;

        // ==========================================
        // Single Contact with Full Profile
        // ==========================================
        case 'contact':
            $id = (int)($_GET['id'] ?? 0);
            if (!$id) jsonError('Contact ID required');

            // Use QueryAdapter for automatic MySQL/MongoDB routing
            $adapter = QueryAdapter::getInstance();
            $data = $adapter->getContactWithDetails($id);

            if (!$data || !$data['contact']) {
                jsonError('Contact not found', 404);
            }

            // Parse JSON fields (for MySQL string storage)
            $jsonFields = ['personality_traits', 'detected_inconsistencies', 'risk_factors',
                          'best_approach', 'topics_to_avoid', 'quote_history'];
            foreach ($jsonFields as $field) {
                if (isset($data['contact'][$field]) && is_string($data['contact'][$field])) {
                    $data['contact'][$field] = json_decode($data['contact'][$field], true);
                }
            }

            jsonResponse($data);
            break;

        // ==========================================
        // Email Threads (grouped by thread_id)
        // ==========================================
        case 'threads':
            $contactId = (int)($_GET['contact'] ?? 0);
            $search = trim($_GET['search'] ?? '');
            $page = max(1, (int)($_GET['page'] ?? 1));
            $limit = min(100, max(1, (int)($_GET['limit'] ?? 20)));
            $offset = ($page - 1) * $limit;
            $hasBusiness = isset($_GET['has_business']) && $_GET['has_business'];

            // Use QueryAdapter for automatic MySQL/MongoDB routing
            $adapter = QueryAdapter::getInstance();
            $result = $adapter->getThreads(
                $contactId ?: null,
                $search,
                $limit,
                $offset
            );

            // Note: has_business filtering is handled by frontend due to raw_analysis being stored as JSON string
            // Future: migrate raw_analysis to proper BSON objects for better querying

            jsonResponse([
                'threads' => $result['threads'],
                'pagination' => [
                    'page' => $page,
                    'limit' => $limit,
                    'total' => $result['total'],
                    'pages' => ceil($result['total'] / $limit)
                ]
            ]);
            break;

        // ==========================================
        // Single Thread with All Emails
        // ==========================================
        case 'thread':
            $threadId = $_GET['thread_id'] ?? '';
            if (!$threadId) jsonError('Thread ID required');

            // Auto-resolve long URL thread ID to short hex ID from cache
            $threadId = resolveThreadId($threadId);

            // Use QueryAdapter for automatic MySQL/MongoDB routing
            $adapter = QueryAdapter::getInstance();
            $threadData = $adapter->getThreadDetails($threadId);

            if (empty($threadData['emails'])) {
                jsonError('Thread not found', 404);
            }

            $emails = $threadData['emails'];
            $analysis = $threadData['analysis'];

            // Parse JSON fields in analysis (for MySQL string storage)
            if ($analysis && isset($analysis['raw_analysis']) && is_string($analysis['raw_analysis'])) {
                // Strip markdown code block markers if present (```json ... ```)
                $rawStr = trim($analysis['raw_analysis']);
                if (preg_match('/^```(?:json)?\s*([\s\S]*?)\s*```$/s', $rawStr, $matches)) {
                    $rawStr = $matches[1];
                }
                $analysis['raw_analysis'] = json_decode($rawStr, true);
            }
            if ($analysis && isset($analysis['key_points']) && is_string($analysis['key_points'])) {
                $analysis['key_points'] = json_decode($analysis['key_points'], true);
            }

            // Ensure raw_analysis is an array for safe access
            if ($analysis && !isset($analysis['raw_analysis'])) {
                $analysis['raw_analysis'] = [];
            }

            // Extract participants_analysis from raw_analysis for easier access
            $participantsAnalysis = [];
            if ($analysis && isset($analysis['raw_analysis']['participants_analysis'])) {
                $participantsAnalysis = $analysis['raw_analysis']['participants_analysis'];
            }

            // Build response with extracted analysis fields
            $rawAnalysis = $analysis['raw_analysis'] ?? [];

            $response = [
                'thread_id' => $threadId,
                'emails' => $emails,
                'email_count' => count($emails),
                'analysis' => $analysis ? [
                    'summary' => $analysis['summary'] ?? $rawAnalysis['thread_summary'] ?? null,
                    'trust_score' => $rawAnalysis['deception_indicators']['trust_score'] ?? 0.5,
                    'deception_score' => $analysis['deception_score'] ?? $rawAnalysis['deception_indicators']['deception_score'] ?? 0,
                    'risk_level' => $rawAnalysis['recommendations']['overall_risk_level'] ?? 'unknown',
                    'key_points' => $analysis['key_points'] ?? $rawAnalysis['key_points'] ?? [],
                    'warnings' => $rawAnalysis['deception_indicators']['red_flags'] ?? [],
                    'participants_analysis' => $participantsAnalysis,
                    'sentiment' => $rawAnalysis['sentiment_analysis'] ?? null,
                    'negotiation' => $rawAnalysis['negotiation_analysis'] ?? null,
                    'action_items' => $rawAnalysis['action_items'] ?? [],
                    'recommendations' => $rawAnalysis['recommendations'] ?? null,
                    // New fields for unified AI response
                    'reply_suggestions' => $rawAnalysis['reply_suggestions'] ?? [],
                    'business_opportunities' => $rawAnalysis['business_opportunities'] ?? null,
                    'protective_analysis' => $rawAnalysis['protective_analysis'] ?? null,
                    'raw' => $rawAnalysis
                ] : null,
                'status' => [
                    'is_analyzed' => $analysis ? (($analysis['is_analyzed'] ?? 0) == 1) : false,
                    'needs_reanalysis' => $analysis ? (($analysis['needs_reanalysis'] ?? 0) == 1) : false
                ]
            ];

            jsonResponse($response);
            break;

        // ==========================================
        // Thread Participants - Get all contacts in a thread
        // ==========================================
        case 'thread_participants':

            $threadId = $_GET['thread_id'] ?? '';
            if (!$threadId) jsonError('Thread ID required');

            // Auto-resolve long URL thread ID to short hex ID from cache
            $threadId = resolveThreadId($threadId);

            // Use QueryAdapter for automatic MySQL/MongoDB routing

            $adapter = QueryAdapter::getInstance();

            $data = $adapter->getThreadParticipants($threadId, GMAIL_USER_EMAIL);
            $participants = $data['participants'];
            $threadSubject = $data['subject'];

            // Enrich with thread_participants analysis data if available
            // Use MongoDB-compatible lookup
            $useMongo = $adapter->getActiveDatabase() === 'mongodb';

            foreach ($participants as &$p) {
                if ($p['contact_id']) {
                    if ($useMongo) {
                        $mongo = \EmailIntelligence\MongoDatabase::getInstance();
                        $analysis = $mongo->findOne('thread_participants', [
                            'thread_id' => $threadId,
                            'contact_id' => $p['contact_id']
                        ]);
                    } else {
                        $analysis = $db->fetchOne(
                            "SELECT trust_score, deception_score, manipulation_score, reliability_score,
                                    risk_level, verdict, overall_assessment, red_flags, positive_signals,
                                    psychological_profile, behavioral_indicators
                             FROM thread_participants
                             WHERE thread_id = ? AND contact_id = ?",
                            [$threadId, $p['contact_id']]
                        );
                    }

                    if ($analysis) {
                        $p['analysis'] = [
                            'trust_score' => $analysis['trust_score'] ?? null,
                            'deception_score' => $analysis['deception_score'] ?? null,
                            'manipulation_score' => $analysis['manipulation_score'] ?? null,
                            'reliability_score' => $analysis['reliability_score'] ?? null,
                            'risk_level' => $analysis['risk_level'] ?? null,
                            'verdict' => $analysis['verdict'] ?? null,
                            'overall_assessment' => $analysis['overall_assessment'] ?? null,
                            'red_flags' => isset($analysis['red_flags']) && is_string($analysis['red_flags'])
                                ? json_decode($analysis['red_flags'], true)
                                : ($analysis['red_flags'] ?? []),
                            'positive_signals' => isset($analysis['positive_signals']) && is_string($analysis['positive_signals'])
                                ? json_decode($analysis['positive_signals'], true)
                                : ($analysis['positive_signals'] ?? []),
                            'psychological_profile' => isset($analysis['psychological_profile']) && is_string($analysis['psychological_profile'])
                                ? json_decode($analysis['psychological_profile'], true)
                                : ($analysis['psychological_profile'] ?? []),
                            'behavioral_indicators' => isset($analysis['behavioral_indicators']) && is_string($analysis['behavioral_indicators'])
                                ? json_decode($analysis['behavioral_indicators'], true)
                                : ($analysis['behavioral_indicators'] ?? [])
                        ];
                    }
                }
            }

            jsonResponse([
                'thread_id' => $threadId,
                'subject' => $threadSubject,
                'participants' => $participants,
                'participant_count' => count($participants),
                'external_count' => count(array_filter($participants, fn($p) => $p['participant_type'] === 'external'))
            ]);
            break;

        // ==========================================
        // API Logs (for extension log viewer)
        // ==========================================
        case 'logs':
            $threadId = $_GET['thread_id'] ?? null;
            $level = $_GET['level'] ?? null;
            $limit = min(200, max(10, (int)($_GET['limit'] ?? 50)));

            // Auto-resolve long URL thread ID to short hex ID from cache
            if ($threadId) {
                $threadId = resolveThreadId($threadId);
            }

            // Use QueryAdapter for automatic MySQL/MongoDB routing
            $adapter = QueryAdapter::getInstance();
            $result = $adapter->getLogs($threadId, $level, $limit);

            jsonResponse($result);
            break;

        // ==========================================
        // Learned Behaviors for a contact
        // ==========================================
        case 'learned_behaviors':
            $contactId = (int)($_GET['contact_id'] ?? 0);
            $type = $_GET['type'] ?? null;

            $where = 'is_active = 1';
            $params = [];

            if ($contactId) {
                $where .= ' AND contact_id = ?';
                $params[] = $contactId;
            }
            if ($type) {
                $where .= ' AND behavior_type = ?';
                $params[] = $type;
            }

            $behaviors = $db->fetchAll(
                "SELECT lb.*, c.name as contact_name, c.email as contact_email
                 FROM learned_behaviors lb
                 LEFT JOIN contacts c ON lb.contact_id = c.id
                 WHERE {$where}
                 ORDER BY frequency DESC, severity DESC, updated_at DESC
                 LIMIT 100",
                $params
            );

            jsonResponse([
                'behaviors' => $behaviors,
                'count' => count($behaviors)
            ]);
            break;

        // ==========================================
        // Emails for Contact (kept for compatibility)
        // ==========================================
        case 'emails':
            $contactId = (int)($_GET['contact'] ?? 0);
            $page = max(1, (int)($_GET['page'] ?? 1));
            $limit = min(100, max(10, (int)($_GET['limit'] ?? 20)));
            $offset = ($page - 1) * $limit;

            $where = '1=1';
            $params = [];

            if ($contactId) {
                $where = 'e.contact_id = ?';
                $params[] = $contactId;
            }

            $total = $db->fetchColumn(
                "SELECT COUNT(*) FROM emails e WHERE {$where}",
                $params
            );

            $emails = $db->fetchAll(
                "SELECT e.*, ea.sentiment_label, ea.urgency_level, ea.summary,
                        ea.deception_score, ea.contains_quote,
                        c.name as contact_name
                 FROM emails e
                 LEFT JOIN email_analysis ea ON e.id = ea.email_id
                 LEFT JOIN contacts c ON e.contact_id = c.id
                 WHERE {$where}
                 ORDER BY e.date DESC
                 LIMIT ? OFFSET ?",
                array_merge($params, [$limit, $offset])
            );

            jsonResponse([
                'emails' => $emails,
                'pagination' => [
                    'page' => $page,
                    'limit' => $limit,
                    'total' => (int)$total,
                    'pages' => ceil($total / $limit)
                ]
            ]);
            break;

        // ==========================================
        // Single Email with Full Analysis
        // ==========================================
        case 'email':
            $id = (int)($_GET['id'] ?? 0);
            if (!$id) jsonError('Email ID required');

            $email = $db->fetchOne(
                "SELECT e.*, ea.*,
                        c.name as contact_name, c.email as contact_email
                 FROM emails e
                 LEFT JOIN email_analysis ea ON e.id = ea.email_id
                 LEFT JOIN contacts c ON e.contact_id = c.id
                 WHERE e.id = ?",
                [$id]
            );

            if (!$email) jsonError('Email not found', 404);

            // Parse JSON fields in analysis
            $jsonFields = ['key_entities', 'commitments', 'requests', 'action_items',
                          'deception_flags', 'quote_amounts', 'key_points', 'labels'];
            foreach ($jsonFields as $field) {
                if (isset($email[$field]) && is_string($email[$field])) {
                    $email[$field] = json_decode($email[$field], true);
                }
            }

            // Get thread emails if thread_id exists
            $threadEmails = [];
            if ($email['thread_id']) {
                $threadEmails = $db->fetchAll(
                    "SELECT id, subject, date, direction, snippet FROM emails
                     WHERE thread_id = ? AND id != ?
                     ORDER BY date",
                    [$email['thread_id'], $id]
                );
            }

            jsonResponse([
                'email' => $email,
                'thread' => $threadEmails
            ]);
            break;

        // ==========================================
        // Quotes
        // ==========================================
        case 'quotes':
            $contactId = (int)($_GET['contact'] ?? 0);
            $outcome = $_GET['outcome'] ?? '';

            // Use MongoDB
            $mongo = \EmailIntelligence\MongoDatabase::getInstance();

            // Build filter
            $filter = [];
            if ($contactId) {
                $filter['contact_id'] = $contactId;
            }
            if ($outcome && in_array($outcome, ['accepted', 'rejected', 'negotiated', 'pending'])) {
                $filter['outcome'] = $outcome;
            }

            // Fetch quotes from MongoDB
            $quotes = $mongo->findAll('quotes', $filter, [
                'sort' => ['quote_date' => -1],
                'limit' => 100
            ]);

            // Enrich with contact info
            foreach ($quotes as &$quote) {
                if (!empty($quote['contact_id'])) {
                    $contact = $mongo->findOne('contacts', ['id' => $quote['contact_id']]);
                    $quote['contact_email'] = $contact['email'] ?? null;
                    $quote['contact_name'] = $contact['name'] ?? null;
                }
            }

            // Statistics using aggregation
            $statsPipeline = [
                ['$group' => [
                    '_id' => null,
                    'total' => ['$sum' => 1],
                    'accepted' => ['$sum' => ['$cond' => [['$eq' => ['$outcome', 'accepted']], 1, 0]]],
                    'rejected' => ['$sum' => ['$cond' => [['$eq' => ['$outcome', 'rejected']], 1, 0]]],
                    'negotiated' => ['$sum' => ['$cond' => [['$eq' => ['$outcome', 'negotiated']], 1, 0]]],
                    'pending' => ['$sum' => ['$cond' => [['$eq' => ['$outcome', 'pending']], 1, 0]]],
                    'avg_amount' => ['$avg' => '$amount']
                ]]
            ];
            $statsResult = $mongo->aggregate('quotes', $statsPipeline);
            $stats = !empty($statsResult) ? $statsResult[0] : [
                'total' => 0, 'accepted' => 0, 'rejected' => 0,
                'negotiated' => 0, 'pending' => 0, 'avg_amount' => 0
            ];

            jsonResponse([
                'quotes' => $quotes,
                'stats' => $stats
            ]);
            break;

        // ==========================================
        // Email Filters
        // ==========================================
        case 'filters':
            $filters = $db->fetchAll(
                "SELECT * FROM email_filters ORDER BY is_active DESC, email_pattern"
            );

            // Also return config filters
            $configFilters = defined('EMAIL_FILTER_ADDRESSES') ? EMAIL_FILTER_ADDRESSES : [];

            jsonResponse([
                'database_filters' => $filters,
                'config_filters' => $configFilters
            ]);
            break;

        // ==========================================
        // Add Filter
        // ==========================================
        case 'add_filter':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $email = trim($_POST['email'] ?? '');
            $description = trim($_POST['description'] ?? '');

            if (empty($email)) {
                jsonError('Email address or domain required');
            }

            $gmail = new GmailClient();
            $sync = new EmailSync($gmail);
            $filterId = $sync->addFilter($email, $description);

            jsonResponse([
                'success' => true,
                'filter_id' => $filterId,
                'message' => "Filter added: {$email}"
            ]);
            break;

        // ==========================================
        // Remove Filter
        // ==========================================
        case 'remove_filter':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $email = trim($_POST['email'] ?? '');

            if (empty($email)) {
                jsonError('Email address or domain required');
            }

            $gmail = new GmailClient();
            $sync = new EmailSync($gmail);
            $removed = $sync->removeFilter($email);

            jsonResponse([
                'success' => $removed > 0,
                'message' => $removed > 0 ? "Filter removed: {$email}" : "Filter not found"
            ]);
            break;

        // ==========================================
        // Trigger Sync (supports multi-account)
        // ==========================================
        case 'sync':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $accountId = isset($_POST['account_id']) ? (int)$_POST['account_id'] : null;
            $syncAll = isset($_POST['all']) && $_POST['all'];
            $mode = $_POST['mode'] ?? 'incremental';
            $maxEmails = (int)($_POST['max'] ?? 100);

            // Sync all accounts
            if ($syncAll) {
                $stats = EmailSync::syncAllAccounts($mode, $maxEmails);
                jsonResponse([
                    'success' => true,
                    'multi_account' => true,
                    'stats' => $stats
                ]);
                break;
            }

            // Sync specific account or default
            $gmail = new GmailClient($accountId);

            if (!$gmail->isAuthenticated()) {
                jsonError('Gmail not authenticated. Please connect your Gmail account first.');
            }

            $sync = new EmailSync($gmail);

            if ($mode === 'full') {
                $stats = $sync->fullSync($maxEmails);
            } else {
                $stats = $sync->incrementalSync();
            }

            jsonResponse([
                'success' => true,
                'account_id' => $gmail->getAccountId(),
                'account_email' => $gmail->getUserEmail(),
                'stats' => $stats
            ]);
            break;

        // ==========================================
        // Trigger Analysis
        // ==========================================
        case 'analyze':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                //jsonError('POST required', 405);
            }

            $analyzer = new ThreadAnalyzer();

            if (!$analyzer->isConfigured()) {
                jsonError('Claude API not configured. Please set CLAUDE_API_KEY in config.php');
            }

            $batchSize = min(20, max(1, (int)($_POST['batch'] ?? 10)));

            // Analyze threads (not individual emails)
            $stats = [
                'analyzed' => 0,
                'failed' => 0,
                'skipped' => 0,
                'quotes_extracted' => 0
            ];

            $threads = $db->fetchAll(
                "SELECT thread_id
                 FROM thread_analysis
                 WHERE (is_analyzed = 0 OR needs_reanalysis = 1)
                   AND thread_id IS NOT NULL
                 ORDER BY needs_reanalysis DESC, last_email_date DESC
                 LIMIT ?",
                [$batchSize]
            );

            foreach ($threads as $thread) {
                try {
                    $result = $analyzer->analyzeThread($thread['thread_id']);
                    file_put_contents("logs/__ddbgA.log","\n tid: ".$thread['thread_id'], FILE_APPEND);
                    if ($result) {
                        $stats['analyzed']++;
                        // Count quotes from the analysis
                        if (!empty($result['quotes'])) {
                            $stats['quotes_extracted'] += count($result['quotes']);
                        }
                    } else {
                        $stats['skipped']++;
                    }
                } catch (Exception $e) {
                    $stats['failed']++;
                    error_log("Thread analysis failed: " . $e->getMessage());
                }
            }

            jsonResponse([
                'success' => true,
                'stats' => $stats,
                'message' => "Analyzed {$stats['analyzed']} threads, {$stats['failed']} failed, {$stats['skipped']} skipped"
            ]);
            break;

        // ==========================================
        // Analyze Single Thread
        // ==========================================
        case 'analyze_thread':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $threadId = $_POST['thread_id'] ?? '';
            if (empty($threadId)) {
                jsonError('thread_id is required');
            }

            // Auto-resolve long URL thread ID to short hex ID from cache
            $threadId = resolveThreadId($threadId);

            $analyzer = new ThreadAnalyzer();

            if (!$analyzer->isConfigured()) {
                jsonError('Claude API not configured. Please set CLAUDE_API_KEY in config.php');
            }

            try {
                $result = $analyzer->analyzeThread($threadId);
                if ($result) {
                    jsonResponse([
                        'success' => true,
                        'thread_id' => $threadId,
                        'summary' => $result['thread_summary'] ?? '',
                        'message' => 'Thread analyzed successfully'
                    ]);
                } else {
                    jsonError('Failed to analyze thread');
                }
            } catch (Exception $e) {
                jsonError('Analysis failed: ' . $e->getMessage());
            }
            break;

        // ==========================================
        // Analysis Status
        // ==========================================
        case 'analysis_status':
            $totalThreads = $db->fetchColumn(
                "SELECT COUNT(*) FROM thread_analysis WHERE thread_id IS NOT NULL"
            );
            $analyzedThreads = $db->fetchColumn(
                "SELECT COUNT(*) FROM thread_analysis WHERE is_analyzed = 1"
            );
            $pendingThreads = $db->fetchColumn(
                "SELECT COUNT(*) FROM thread_analysis WHERE is_analyzed = 0 OR needs_reanalysis = 1"
            );
            $needsReanalysis = $db->fetchColumn(
                "SELECT COUNT(*) FROM thread_analysis WHERE needs_reanalysis = 1"
            );

            // Get recent analysis results
            $recentAnalysis = $db->fetchAll(
                "SELECT
                    ta.thread_id,
                    ta.analyzed_at,
                    ta.email_count,
                    e.subject,
                    c.name as contact_name
                 FROM thread_analysis ta
                 LEFT JOIN emails e ON ta.last_email_id = e.id
                 LEFT JOIN contacts c ON ta.contact_id = c.id
                 WHERE ta.is_analyzed = 1
                 ORDER BY ta.analyzed_at DESC
                 LIMIT 5"
            );

            // Get analysis details for each thread
            foreach ($recentAnalysis as &$thread) {
                $analysis = $db->fetchOne(
                    "SELECT summary, sentiment_label, deception_score, raw_analysis
                     FROM email_analysis ea
                     JOIN emails e ON ea.email_id = e.id
                     WHERE e.thread_id = ?
                     ORDER BY ea.analyzed_at DESC
                     LIMIT 1",
                    [$thread['thread_id']]
                );
                if ($analysis) {
                    $thread['summary'] = $analysis['summary'];
                    $thread['sentiment'] = $analysis['sentiment_label'];
                    $thread['deception_score'] = $analysis['deception_score'];
                    // Extract trust score from raw_analysis if available
                    if ($analysis['raw_analysis']) {
                        $raw = json_decode($analysis['raw_analysis'], true);
                        $thread['trust_score'] = $raw['deception_indicators']['trust_score'] ?? 0.5;
                    } else {
                        $thread['trust_score'] = 0.5;
                    }
                }
            }
            unset($thread);

            jsonResponse([
                'total_threads' => (int)$totalThreads,
                'analyzed' => (int)$analyzedThreads,
                'pending' => (int)$pendingThreads,
                'needs_reanalysis' => (int)$needsReanalysis,
                'percent_complete' => $totalThreads > 0 ? round(($analyzedThreads / $totalThreads) * 100, 1) : 0,
                'recent_analysis' => $recentAnalysis
            ]);
            break;

        // ==========================================
        // Analyze Contact (Multi-Thread Analysis)
        // ==========================================
        case 'analyze_contact':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $contactId = (int)($_POST['contact_id'] ?? 0);
            if (!$contactId) jsonError('Contact ID required');

            $includeRaw = (bool)($_POST['include_raw'] ?? true);

            $analyzer = new ContactAnalyzer();

            if (!$analyzer->isConfigured()) {
                jsonError('Claude API not configured. Please set CLAUDE_API_KEY in config.php');
            }

            try {
                $analysis = $analyzer->analyzeContact($contactId, $includeRaw);
                $stats = $analyzer->getStats();

                jsonResponse([
                    'success' => true,
                    'contact_id' => $contactId,
                    'analysis' => $analysis,
                    'stats' => $stats,
                    'message' => "Analyzed {$stats['threads_analyzed']} threads ({$stats['emails_processed']} emails) using {$stats['tokens_used']} tokens"
                ]);
            } catch (Exception $e) {
                jsonError('Contact analysis failed: ' . $e->getMessage());
            }
            break;

        // ==========================================
        // Get Contact Profile (stored analysis)
        // ==========================================
        case 'contact_profile':
            $contactId = (int)($_GET['contact_id'] ?? 0);
            if (!$contactId) jsonError('Contact ID required');

            // Get contact basic info
            $contact = $db->fetchOne(
                "SELECT * FROM contacts WHERE id = ?",
                [$contactId]
            );

            if (!$contact) jsonError('Contact not found', 404);

            // Get profile if exists
            $profile = $db->fetchOne(
                "SELECT * FROM contact_profiles WHERE contact_id = ?",
                [$contactId]
            );

            // Get thread stats
            $threadStats = $db->fetchOne(
                "SELECT
                    COUNT(*) as total_threads,
                    SUM(CASE WHEN is_analyzed = 1 THEN 1 ELSE 0 END) as analyzed_threads,
                    SUM(email_count) as total_emails
                 FROM thread_analysis
                 WHERE contact_id = ?",
                [$contactId]
            );

            // Get recent threads
            $recentThreads = $db->fetchAll(
                "SELECT ta.thread_id,
                        (SELECT subject FROM emails WHERE thread_id = ta.thread_id LIMIT 1) as subject,
                        ta.analyzed_at,
                        ta.email_count
                 FROM thread_analysis ta
                 WHERE ta.contact_id = ?
                 ORDER BY ta.analyzed_at DESC
                 LIMIT 5",
                [$contactId]
            );

            jsonResponse([
                'contact' => $contact,
                'profile' => $profile,
                'thread_stats' => $threadStats,
                'recent_threads' => $recentThreads,
                'thread_count' => $threadStats['total_threads'] ?? 0,
                'email_count' => $threadStats['total_emails'] ?? 0
            ]);
            break;

        // ==========================================
        // Build Profile (legacy)
        // ==========================================
        case 'build_profile':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $contactId = (int)($_POST['contact_id'] ?? 0);
            if (!$contactId) jsonError('Contact ID required');

            $profiler = new ContactProfiler();
            $profile = $profiler->buildProfile($contactId, true);

            jsonResponse([
                'success' => true,
                'profile' => $profile
            ]);
            break;

        // ==========================================
        // Semantic Search (Vector Memory)
        // ==========================================
        case 'semantic_search':
            $query = $_GET['q'] ?? $_POST['query'] ?? '';
            if (empty($query)) {
                jsonError('Search query required');
            }

            $limit = min(50, max(1, (int)($_GET['limit'] ?? 10)));
            $type = $_GET['type'] ?? ''; // email, thread_summary, pattern
            $contactId = (int)($_GET['contact_id'] ?? 0);

            $memory = new VectorMemory();
            //print_r($memory);
            if (!$memory->isHealthy()) {
                jsonError('Vector memory service unavailable');
            }

            $filters = [];
            if ($type) $filters['type'] = $type;
            if ($contactId) $filters['contact_id'] = $contactId;

            $results = $memory->search($query, $limit, $filters);

            jsonResponse([
                'query' => $query,
                'results' => $results,
                'count' => count($results)
            ]);
            break;

        // ==========================================
        // Find Similar Patterns
        // ==========================================
        case 'find_patterns':
            $query = $_GET['q'] ?? '';
            if (empty($query)) {
                jsonError('Pattern query required');
            }

            $memory = new VectorMemory();

            if (!$memory->isHealthy()) {
                jsonError('Vector memory service unavailable');
            }

            $deceptionPatterns = $memory->findSimilarDeception($query, 5);
            $behaviors = $memory->findSimilarBehaviors($query, 5);

            jsonResponse([
                'query' => $query,
                'deception_patterns' => $deceptionPatterns,
                'similar_behaviors' => $behaviors
            ]);
            break;

        // ==========================================
        // Index Emails to Vector Memory
        // ==========================================
        case 'index_vectors':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $memory = new VectorMemory();

            if (!$memory->isHealthy()) {
                jsonError('Vector memory service unavailable');
            }

            $contactId = (int)($_POST['contact_id'] ?? 0);
            $indexType = $_POST['type'] ?? 'all'; // emails, threads, all

            $stats = ['emails_indexed' => 0, 'threads_indexed' => 0];

            if ($indexType === 'emails' || $indexType === 'all') {
                if ($contactId) {
                    $stats['emails_indexed'] = $memory->indexContactEmails($contactId);
                } else {
                    // Index all contacts
                    $contacts = $db->fetchAll("SELECT id FROM contacts");
                    foreach ($contacts as $contact) {
                        $stats['emails_indexed'] += $memory->indexContactEmails($contact['id']);
                    }
                }
            }

            if ($indexType === 'threads' || $indexType === 'all') {
                $stats['threads_indexed'] = $memory->indexAllThreads();
            }

            jsonResponse([
                'success' => true,
                'emails_indexed' => $stats['emails_indexed'],
                'threads_indexed' => $stats['threads_indexed'],
                'message' => "Indexed {$stats['emails_indexed']} emails and {$stats['threads_indexed']} threads"
            ]);
            break;

        // ==========================================
        // Vector Memory Status
        // ==========================================
        case 'vector_status':
            $memory = new VectorMemory();

            jsonResponse([
                'healthy' => $memory->isHealthy(),
                'stats' => $memory->getStats()
            ]);
            break;

        // ==========================================
        // Pattern Detection - Scan Contact
        // ==========================================
        case 'scan_patterns':
            $contactId = (int)($_GET['contact_id'] ?? 0);
            if (!$contactId) jsonError('Contact ID required');

            $threshold = (int)($_GET['threshold'] ?? 25);

            $detector = new PatternDetector();
            $patterns = $detector->scanContact($contactId, $threshold);

            jsonResponse([
                'contact_id' => $contactId,
                'patterns' => $patterns,
                'pattern_count' => count($patterns)
            ]);
            break;

        // ==========================================
        // Pattern Detection - Single Pattern
        // ==========================================
        case 'detect_pattern':
            $patternKey = $_GET['pattern'] ?? '';
            $contactId = (int)($_GET['contact_id'] ?? 0);

            if (!$patternKey) jsonError('Pattern key required');

            $detector = new PatternDetector();
            $filters = $contactId ? ['contact_id' => $contactId] : [];
            $matches = $detector->detectPattern($patternKey, $filters);

            jsonResponse([
                'pattern' => $patternKey,
                'matches' => $matches,
                'match_count' => count($matches)
            ]);
            break;

        // ==========================================
        // Pattern Stats - All Patterns
        // ==========================================
        case 'pattern_stats':
            $forceRefresh = isset($_GET['refresh']) && $_GET['refresh'] === '1';
            $detector = new PatternDetector();
            $stats = $detector->getPatternStats($forceRefresh);

            jsonResponse([
                'stats' => $stats,
                'pattern_count' => count($stats),
                'cached' => !$forceRefresh
            ]);
            break;

        // ==========================================
        // Find Similar Contacts by Pattern
        // ==========================================
        case 'similar_contacts':
            $contactId = (int)($_GET['contact_id'] ?? 0);
            $patternKey = $_GET['pattern'] ?? '';
            $limit = min(10, (int)($_GET['limit'] ?? 5));

            if (!$contactId) jsonError('Contact ID required');
            if (!$patternKey) jsonError('Pattern key required');

            $detector = new PatternDetector();
            $similar = $detector->findSimilarContacts($contactId, $patternKey, $limit);

            jsonResponse([
                'contact_id' => $contactId,
                'pattern' => $patternKey,
                'similar_contacts' => $similar,
                'count' => count($similar)
            ]);
            break;

        // ==========================================
        // Pricing Behavior Analysis
        // ==========================================
        case 'pricing_behavior':
            $contactId = (int)($_GET['contact_id'] ?? 0);
            if (!$contactId) jsonError('Contact ID required');

            $detector = new PatternDetector();
            $behavior = $detector->analyzePricingBehavior($contactId);

            jsonResponse([
                'contact_id' => $contactId,
                'pricing_behavior' => $behavior
            ]);
            break;

        // ==========================================
        // Gmail Auth URL (supports multi-account)
        // ==========================================
        case 'gmail_auth_url':
            $accountId = isset($_GET['account_id']) ? (int)$_GET['account_id'] : null;

            $gmail = new GmailClient($accountId);

            if (!$gmail->hasCredentials()) {
                jsonError('Gmail OAuth credentials not configured');
            }

            jsonResponse([
                'auth_url' => $gmail->getAuthUrl($accountId),
                'account_id' => $gmail->getAccountId(),
                'account_email' => $gmail->getUserEmail()
            ]);
            break;

        // ==========================================
        // Gmail Accounts - List
        // ==========================================
        case 'gmail_accounts':
            $accountManager = new \EmailIntelligence\GmailAccount();
            $stats = $accountManager->getStats();

            jsonResponse([
                'accounts' => $stats['accounts'],
                'total' => $stats['total_accounts'],
                'active' => $stats['active_accounts'],
                'authenticated' => $stats['authenticated_accounts'],
                'total_emails' => $stats['total_emails']
            ]);
            break;

        // ==========================================
        // Gmail Account - Add
        // ==========================================
        case 'gmail_account_add':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $email = trim($_POST['email'] ?? '');
            $name = trim($_POST['name'] ?? '');
            $isPrimary = isset($_POST['is_primary']) && $_POST['is_primary'];

            if (!$email) {
                jsonError('Email address required');
            }

            if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
                jsonError('Invalid email format');
            }

            $accountManager = new \EmailIntelligence\GmailAccount();

            try {
                $accountId = $accountManager->create($email, $name, $isPrimary);
                $gmail = new GmailClient($accountId);

                jsonResponse([
                    'success' => true,
                    'account_id' => $accountId,
                    'email' => $email,
                    'auth_url' => $gmail->getAuthUrl($accountId),
                    'message' => "Account created. Please authorize via the auth_url."
                ]);
            } catch (Exception $e) {
                jsonError($e->getMessage());
            }
            break;

        // ==========================================
        // Gmail Account - Remove
        // ==========================================
        case 'gmail_account_remove':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $accountId = (int)($_POST['account_id'] ?? 0);
            if (!$accountId) {
                jsonError('Account ID required');
            }

            $accountManager = new \EmailIntelligence\GmailAccount();

            try {
                $accountManager->delete($accountId, true);
                jsonResponse([
                    'success' => true,
                    'message' => 'Account removed'
                ]);
            } catch (Exception $e) {
                jsonError($e->getMessage());
            }
            break;

        // ==========================================
        // Gmail Account - Set Primary
        // ==========================================
        case 'gmail_account_primary':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $accountId = (int)($_POST['account_id'] ?? 0);
            if (!$accountId) {
                jsonError('Account ID required');
            }

            $accountManager = new \EmailIntelligence\GmailAccount();
            $accountManager->update($accountId, ['is_primary' => 1]);

            jsonResponse([
                'success' => true,
                'message' => 'Primary account updated'
            ]);
            break;

        // ==========================================
        // Gmail Account - Toggle Active
        // ==========================================
        case 'gmail_account_toggle':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $accountId = (int)($_POST['account_id'] ?? 0);
            $isActive = isset($_POST['is_active']) ? (bool)$_POST['is_active'] : true;

            if (!$accountId) {
                jsonError('Account ID required');
            }

            $accountManager = new \EmailIntelligence\GmailAccount();
            $accountManager->update($accountId, ['is_active' => $isActive ? 1 : 0]);

            jsonResponse([
                'success' => true,
                'is_active' => $isActive,
                'message' => $isActive ? 'Account activated' : 'Account deactivated'
            ]);
            break;

        // ==========================================
        // Update Quote Outcome
        // ==========================================
        case 'update_quote':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $quoteId = (int)($_POST['quote_id'] ?? 0);
            $outcome = $_POST['outcome'] ?? '';
            $finalAmount = isset($_POST['final_amount']) ? (float)$_POST['final_amount'] : null;

            if (!$quoteId) jsonError('Quote ID required');
            if (!in_array($outcome, ['accepted', 'rejected', 'negotiated', 'pending', 'expired'])) {
                jsonError('Invalid outcome');
            }

            $updateData = ['outcome' => $outcome];
            if ($finalAmount !== null) {
                $updateData['final_amount'] = $finalAmount;
            }

            $db->update('quotes', $updateData, 'id = ?', [$quoteId]);

            jsonResponse(['success' => true]);
            break;

        // ==========================================
        // Update Contact Type
        // ==========================================
        case 'update_contact':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $contactId = (int)($_POST['contact_id'] ?? 0);
            if (!$contactId) jsonError('Contact ID required');

            $updateData = [];

            if (isset($_POST['type']) && in_array($_POST['type'], ['client', 'supplier', 'other'])) {
                $updateData['type'] = $_POST['type'];
            }

            if (isset($_POST['company'])) {
                $updateData['company'] = trim($_POST['company']);
            }

            if (isset($_POST['notes'])) {
                $updateData['notes'] = trim($_POST['notes']);
            }

            if (empty($updateData)) {
                jsonError('No update data provided');
            }

            $db->update('contacts', $updateData, 'id = ?', [$contactId]);

            jsonResponse(['success' => true]);
            break;

        // ==========================================
        // Alerts - Get Alerts
        // ==========================================
        case 'alerts':
            $alertSystem = new AlertSystem();

            $filters = [
                'contact_id' => (int)($_GET['contact_id'] ?? 0) ?: null,
                'severity' => $_GET['severity'] ?? null,
                'pattern_key' => $_GET['pattern'] ?? null,
                'limit' => (int)($_GET['limit'] ?? 50),
                'offset' => (int)($_GET['offset'] ?? 0)
            ];

            if (isset($_GET['is_read'])) {
                $filters['is_read'] = (bool)$_GET['is_read'];
            }
            if (isset($_GET['is_dismissed'])) {
                $filters['is_dismissed'] = (bool)$_GET['is_dismissed'];
            }

            $alerts = $alertSystem->getAlerts(array_filter($filters));

            jsonResponse([
                'alerts' => $alerts,
                'count' => count($alerts)
            ]);
            break;

        // ==========================================
        // Alerts - Get Stats
        // ==========================================
        case 'alert_stats':
            $alertSystem = new AlertSystem();
            $stats = $alertSystem->getStats();

            jsonResponse($stats);
            break;

        // ==========================================
        // Alerts - Scan Contact
        // ==========================================
        case 'scan_alerts':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $contactId = (int)($_POST['contact_id'] ?? 0);
            if (!$contactId) jsonError('Contact ID required');

            $alertSystem = new AlertSystem();
            $alerts = $alertSystem->scanContactForAlerts($contactId);

            jsonResponse([
                'success' => true,
                'alerts_created' => count($alerts),
                'alerts' => $alerts
            ]);
            break;

        // ==========================================
        // Alerts - Mark as Read
        // ==========================================
        case 'alert_read':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $alertId = (int)($_POST['alert_id'] ?? 0);
            if (!$alertId) jsonError('Alert ID required');

            $alertSystem = new AlertSystem();
            $success = $alertSystem->markAsRead($alertId);

            jsonResponse(['success' => $success]);
            break;

        // ==========================================
        // Alerts - Dismiss
        // ==========================================
        case 'alert_dismiss':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $alertId = (int)($_POST['alert_id'] ?? 0);
            if (!$alertId) jsonError('Alert ID required');

            $alertSystem = new AlertSystem();
            $success = $alertSystem->dismiss($alertId);

            jsonResponse(['success' => $success]);
            break;

        // ==========================================
        // Batch Analysis - Multiple Threads
        // ==========================================
        case 'analyze_batch':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $threadIds = $_POST['thread_ids'] ?? [];
            if (empty($threadIds) || !is_array($threadIds)) {
                jsonError('thread_ids array required');
            }

            $analyzer = new ThreadAnalyzer();
            $results = [];
            $errors = [];

            foreach ($threadIds as $threadId) {
                try {
                    $analysis = $analyzer->analyzeThread($threadId);
                    $results[] = [
                        'thread_id' => $threadId,
                        'success' => true,
                        'analysis' => $analysis
                    ];
                } catch (Exception $e) {
                    $errors[] = [
                        'thread_id' => $threadId,
                        'error' => $e->getMessage()
                    ];
                }
            }

            jsonResponse([
                'success' => true,
                'analyzed' => count($results),
                'failed' => count($errors),
                'results' => $results,
                'errors' => $errors
            ]);
            break;

        // ==========================================
        // Resolve Message ID to Thread ID via Gmail API
        // Also caches the URL thread ID -> real thread ID mapping
        // ==========================================
        case 'resolve_message_thread':
            $messageId = $_GET['message_id'] ?? '';
            $urlThreadId = $_GET['url_thread_id'] ?? '';  // Long URL-style ID (FMfcgz...)
            $originalMessageId = $messageId;  // Debug: keep original for logging

            // Initialize cache
            $threadCache = ThreadIdCache::getInstance();

            // PRIORITY 1: If we have a URL thread ID, check cache first (before any message ID parsing)
            if ($urlThreadId) {
                $cachedThreadId = $threadCache->get($urlThreadId);
                if ($cachedThreadId) {
                    error_log("resolve_message_thread: Found cached resolution {$urlThreadId} -> {$cachedThreadId}");
                    jsonResponse([
                        'success' => true,
                        'message_id' => $originalMessageId,
                        'thread_id' => $cachedThreadId,
                        'from_cache' => true
                    ]);
                }
            }

            // Validate message_id is provided
            if (!$messageId) {
                jsonError('message_id required');
            }

            // Check for draft/compose message IDs (#msg-a:r-XXXX format)
            // These cannot be resolved via Gmail API - they're local drafts
            if (preg_match('/#msg-a:/', $messageId)) {
                error_log("resolve_message_thread: Draft message ID detected: {$originalMessageId}");
                jsonError('Cannot resolve draft message ID. Click on a received email in this thread to sync.', 400, [
                    'is_draft' => true,
                    'message_id' => $originalMessageId,
                    'url_thread_id' => $urlThreadId,
                    'hint' => 'Click on an existing email in this thread (not the compose/reply box) to get a valid message ID'
                ]);
            }

            // Clean up message ID - extract numeric ID from #msg-f:XXXXXX format
            // Gmail DOM stores message IDs as decimal, but API expects hexadecimal
            $validMessageId = null;
            if (preg_match('/#msg-f:(\d+)/', $messageId, $matches)) {
                $decimalId = $matches[1];
                // Convert decimal to hexadecimal (Gmail API format)
                // Use sprintf for reliable conversion of large numbers
                $validMessageId = sprintf('%x', $decimalId);
            } elseif (preg_match('/^[0-9a-f]{16}$/i', $messageId)) {
                // Already a valid hex message ID
                $validMessageId = $messageId;
            }

            if (!$validMessageId) {
                error_log("resolve_message_thread: Invalid message ID format: {$originalMessageId}");
                jsonError('Invalid message ID format. Expected #msg-f:XXXX format.', 400, [
                    'message_id' => $originalMessageId,
                    'url_thread_id' => $urlThreadId
                ]);
            }

            $messageId = $validMessageId;
            error_log("resolve_message_thread: Original='{$originalMessageId}' Cleaned='{$messageId}' UrlThreadId='{$urlThreadId}'");

            try {
                $gmail = new GmailClient();

                if (!$gmail->isAuthenticated()) {
                    jsonError('Gmail not connected. Please authenticate first.', 401);
                }

                // Fetch the message to get its thread ID
                error_log("resolve_message_thread: Calling Gmail API for message ID: {$messageId}");
                $messageData = $gmail->getMessage($messageId);
                error_log("resolve_message_thread: Got response: " . json_encode($messageData));

                if (!$messageData || empty($messageData['thread_id'])) {
                    error_log("resolve_message_thread: No thread_id in response");
                    jsonError("Could not resolve thread ID for message: {$messageId}");
                }

                $resolvedThreadId = $messageData['thread_id'];
                $subject = $messageData['subject'] ?? '';
                error_log("resolve_message_thread: Success! thread_id={$resolvedThreadId}");

                // Save to cache if we have a URL thread ID
                if ($urlThreadId && $threadCache->isUrlStyleThreadId($urlThreadId)) {
                    $threadCache->save($urlThreadId, $resolvedThreadId, $messageId, $subject);
                    error_log("resolve_message_thread: Cached {$urlThreadId} -> {$resolvedThreadId}");
                }

                jsonResponse([
                    'success' => true,
                    'message_id' => $messageId,
                    'thread_id' => $resolvedThreadId,
                    'subject' => $subject,
                    'cached' => !empty($urlThreadId)  // Indicates if this resolution was saved to cache
                ]);
            } catch (Exception $e) {
                error_log("resolve_message_thread: Exception: " . $e->getMessage());
                jsonError("Failed to resolve thread: " . $e->getMessage());
            }
            break;

        // ==========================================
        // Sync Thread via Gmail API (server-side fetch)
        // ==========================================
        case 'sync_thread':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
            if (strpos($contentType, 'application/json') !== false) {
                $jsonData = json_decode(file_get_contents('php://input'), true);
                $threadId = $jsonData['thread_id'] ?? '';
                $subject = $jsonData['subject'] ?? '';
                $messageId = $jsonData['message_id'] ?? '';  // NEW: Accept message_id for reliable resolution
            } else {
                $threadId = $_POST['thread_id'] ?? '';
                $subject = $_POST['subject'] ?? '';
                $messageId = $_POST['message_id'] ?? '';
            }

            if (!$threadId && !$messageId) {
                jsonError('thread_id or message_id required');
            }

            // Initialize thread ID cache
            $threadCache = ThreadIdCache::getInstance();

            // Store original URL-style thread ID for caching
            $originalThreadId = $threadId;

            // PRIORITY 0: Check cache for URL-style thread ID
            if ($threadId && $threadCache->isUrlStyleThreadId($threadId)) {
                $cachedId = $threadCache->get($threadId);
                if ($cachedId) {
                    error_log("sync_thread: Found cached thread ID {$cachedId} for URL ID {$threadId}");
                    $threadId = $cachedId;
                }
            }

            // Clean up message ID - extract numeric ID from #msg-f:XXXXXX format
            // Gmail DOM stores message IDs as decimal, but API expects hexadecimal
            if ($messageId && preg_match('/#msg-f:(\d+)/', $messageId, $matches)) {
                $decimalId = $matches[1];
                // Convert decimal to hexadecimal (Gmail API format)
                $messageId = sprintf('%x', $decimalId);
            }

            try {
                $gmail = new GmailClient();

                if (!$gmail->isAuthenticated()) {
                    jsonError('Gmail not connected. Please authenticate first.', 401);
                }

                // First try direct thread fetch
                $threadData = null;

                // PRIORITY 1: If we have a message_id, use it to get the REAL thread ID first
                // This is the most reliable method as message IDs are unique
                if ($messageId) {
                    try {
                        $messageData = $gmail->getMessage($messageId);
                        if (!empty($messageData['thread_id'])) {
                            $resolvedId = $messageData['thread_id'];
                            error_log("sync_thread: Resolved thread ID {$resolvedId} from message ID {$messageId}");

                            // Cache the resolution if original was URL-style
                            if ($threadCache->isUrlStyleThreadId($originalThreadId) &&
                                $threadCache->isRealGmailThreadId($resolvedId)) {
                                $threadCache->save($originalThreadId, $resolvedId, $messageId, $subject);
                                error_log("sync_thread: Cached {$originalThreadId} -> {$resolvedId}");
                            }

                            $threadId = $resolvedId;
                        }
                    } catch (Exception $e) {
                        error_log("sync_thread: Failed to resolve message_id {$messageId}: " . $e->getMessage());
                        // Continue with other methods
                    }
                }

                // PRIORITY 2: Try direct thread fetch with the (possibly resolved) thread ID
                if ($threadId) {
                    try {
                        $threadData = $gmail->getThread($threadId);
                    } catch (Exception $e) {
                        // Thread ID from URL might not match Gmail's internal ID
                        // Gmail URL IDs (FMfcgz...) are different from API thread IDs (hex)
                        error_log("sync_thread: Direct thread fetch failed for {$threadId}: " . $e->getMessage());
                    }
                }

                // PRIORITY 3 (FALLBACK): Try searching by subject if we still don't have thread data
                if (!$threadData && $subject) {
                    $cleanSubject = preg_replace('/^(Re:|Fwd:|RE:|FWD:)\s*/i', '', $subject);

                    // Get multiple results and find the thread with most messages
                    $searchResults = $gmail->searchThreads('subject:"' . $cleanSubject . '"', 10);

                    if (!empty($searchResults)) {
                        // Find the thread with the most messages
                        $bestThread = null;
                        $maxMessages = 0;

                        foreach ($searchResults as $result) {
                            try {
                                $candidateThread = $gmail->getThread($result['id']);
                                $msgCount = $candidateThread['message_count'] ?? count($candidateThread['messages'] ?? []);

                                if ($msgCount > $maxMessages) {
                                    $maxMessages = $msgCount;
                                    $bestThread = $candidateThread;
                                    $threadId = $result['id'];
                                }
                            } catch (Exception $ex) {
                                // Skip threads we can't fetch
                                continue;
                            }
                        }

                        $threadData = $bestThread;

                        if ($threadData) {
                            error_log("sync_thread: Found best thread {$threadId} with {$maxMessages} messages (searched by subject: {$cleanSubject})");
                        }
                    }
                }

                if (!$threadData || empty($threadData['messages'])) {
                    jsonError("Thread not found in Gmail. URL ID: {$originalThreadId}, Subject: {$subject}. Make sure you are logged into the correct Gmail account.");
                }

                // Save all emails from the thread
                $saved = 0;
                $contactId = null;
                $contactsCreated = [];
                $userEmail = strtolower(GMAIL_USER_EMAIL);

                foreach ($threadData['messages'] as $emailData) {
                    try {
                        $fromEmail = strtolower($emailData['from_email'] ?? '');
                        $fromName = $emailData['from_name'] ?? '';

                        // Find or create contact for external senders (not our own email)
                        $emailContactId = null;
                        if ($fromEmail && $fromEmail !== $userEmail) {
                            if (isset($contactsCreated[$fromEmail])) {
                                $emailContactId = $contactsCreated[$fromEmail];
                            } else {
                                $existingContact = $db->fetchOne(
                                    "SELECT id FROM contacts WHERE LOWER(email) = ?",
                                    [$fromEmail]
                                );

                                if ($existingContact) {
                                    $emailContactId = $existingContact['id'];
                                } else {
                                    $db->insert('contacts', [
                                        'name' => $fromName ?: ucfirst(explode('@', $fromEmail)[0]),
                                        'email' => $fromEmail,
                                        'company' => '',
                                        'created_at' => date('Y-m-d H:i:s')
                                    ]);
                                    $emailContactId = $db->lastInsertId();
                                }
                                $contactsCreated[$fromEmail] = $emailContactId;
                            }

                            // Use the first external contact as the main contact for the thread
                            if (!$contactId) {
                                $contactId = $emailContactId;
                            }
                        }

                        // Also create contacts for TO and CC recipients (they are participants too)
                        $allRecipients = [];
                        if (!empty($emailData['to_email'])) {
                            $allRecipients[] = [
                                'email' => strtolower($emailData['to_email']),
                                'name' => $emailData['to_name'] ?? ''
                            ];
                        }
                        if (!empty($emailData['cc_email'])) {
                            $allRecipients[] = [
                                'email' => strtolower($emailData['cc_email']),
                                'name' => $emailData['cc_name'] ?? ''
                            ];
                        }

                        foreach ($allRecipients as $recipient) {
                            $recipientEmail = $recipient['email'];
                            $recipientName = $recipient['name'];

                            if ($recipientEmail && $recipientEmail !== $userEmail && !isset($contactsCreated[$recipientEmail])) {
                                $existingContact = $db->fetchOne(
                                    "SELECT id FROM contacts WHERE LOWER(email) = ?",
                                    [$recipientEmail]
                                );

                                if (!$existingContact) {
                                    $db->insert('contacts', [
                                        'name' => $recipientName ?: ucfirst(explode('@', $recipientEmail)[0]),
                                        'email' => $recipientEmail,
                                        'company' => '',
                                        'created_at' => date('Y-m-d H:i:s')
                                    ]);
                                    $contactsCreated[$recipientEmail] = $db->lastInsertId();
                                } else {
                                    $contactsCreated[$recipientEmail] = $existingContact['id'];
                                }
                            }
                        }

                        // Save email to database
                        $db->insertOrUpdate('emails', [
                            'gmail_id' => $emailData['gmail_id'],
                            'thread_id' => $threadId,
                            'contact_id' => $emailContactId ?: $contactId,
                            'subject' => $emailData['subject'] ?? '',
                            'from_email' => $emailData['from_email'] ?? '',
                            'from_name' => $fromName,
                            'to_email' => $emailData['to_email'] ?? '',
                            'to_name' => $emailData['to_name'] ?? '',
                            'cc_email' => $emailData['cc_email'] ?? '',
                            'cc_name' => $emailData['cc_name'] ?? '',
                            'body_text' => $emailData['body_text'] ?? '',
                            'body_html' => $emailData['body_html'] ?? '',
                            'date' => $emailData['date'] ?? date('Y-m-d H:i:s'),
                            'direction' => $emailData['direction'] ?? 'inbound',
                            'has_attachments' => !empty($emailData['has_attachments']) ? 1 : 0
                        ], ['gmail_id']);

                        $saved++;
                    } catch (Exception $e) {
                        error_log("Failed to save email in sync_thread: " . $e->getMessage() . " - Email: " . ($emailData['gmail_id'] ?? 'unknown'));
                    }
                }

                // Create or update thread_analysis record
                $db->insertOrUpdate('thread_analysis', [
                    'thread_id' => $threadId,
                    'contact_id' => $contactId,
                    'email_count' => $saved,
                    'last_email_date' => $threadData['messages'][count($threadData['messages']) - 1]['date'] ?? date('Y-m-d H:i:s'),
                    'is_analyzed' => 0,
                    'needs_reanalysis' => 1
                ], ['thread_id']);

                jsonResponse([
                    'success' => true,
                    'thread_id' => $threadId,
                    'original_thread_id' => $originalThreadId,
                    'emails_synced' => $saved,
                    'emails_in_thread' => count($threadData['messages']),
                    'contact_id' => $contactId,
                    'contacts_created' => array_keys($contactsCreated),
                    'message' => "Synced {$saved} emails from Gmail API (thread has " . count($threadData['messages']) . " messages)"
                ]);

            } catch (Exception $e) {
                jsonError('Gmail sync failed: ' . $e->getMessage());
            }
            break;

        // ==========================================
        // Save Gmail Thread (legacy - from DOM scraping)
        // ==========================================
        case 'save_gmail_thread':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            // Handle both JSON and form-encoded data
            $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
            if (strpos($contentType, 'application/json') !== false) {
                $jsonData = json_decode(file_get_contents('php://input'), true);
                $threadId = $jsonData['thread_id'] ?? '';
                $emails = $jsonData['emails'] ?? [];
            } else {
                $threadId = $_POST['thread_id'] ?? '';
                $emails = $_POST['emails'] ?? [];
            }

            if (!$threadId || empty($emails)) {
                jsonError('thread_id and emails required');
            }

            // Auto-resolve long URL thread ID to short hex ID from cache
            $threadId = resolveThreadId($threadId);

            $saved = 0;
            $contactId = null;

            foreach ($emails as $emailData) {
                try {
                    // Try to find or create contact based on sender email
                    $fromEmail = $emailData['from'] ?? '';
                    $fromName = $emailData['from_name'] ?? '';

                    if ($fromEmail && !$contactId) {
                        // Check if contact exists
                        $existingContact = $db->fetchOne(
                            "SELECT id FROM contacts WHERE email = ?",
                            [$fromEmail]
                        );

                        if ($existingContact) {
                            $contactId = $existingContact['id'];
                        } elseif ($fromName || $fromEmail) {
                            // Create new contact
                            $db->insert('contacts', [
                                'name' => $fromName ?: explode('@', $fromEmail)[0],
                                'email' => $fromEmail,
                                'company' => '',
                                'created_at' => date('Y-m-d H:i:s')
                            ]);
                            $contactId = $db->lastInsertId();
                        }
                    }

                    // Parse date if needed
                    $emailDate = $emailData['date'] ?? date('Y-m-d H:i:s');
                    if (!preg_match('/^\d{4}-\d{2}-\d{2}/', $emailDate)) {
                        // Try to parse human-readable date
                        $timestamp = strtotime($emailDate);
                        if ($timestamp) {
                            $emailDate = date('Y-m-d H:i:s', $timestamp);
                        } else {
                            $emailDate = date('Y-m-d H:i:s');
                        }
                    }

                    // Save email to database
                    $db->insertOrUpdate('emails', [
                        'gmail_id' => $emailData['id'] ?? uniqid('gmail_'),
                        'thread_id' => $threadId,
                        'contact_id' => $contactId,
                        'subject' => $emailData['subject'] ?? '',
                        'from_email' => $fromEmail,
                        'to_email' => $emailData['to'] ?? '',
                        'body_text' => $emailData['body_text'] ?? '',
                        'body_html' => $emailData['body_html'] ?? '',
                        'date' => $emailDate,
                        'direction' => $emailData['direction'] ?? 'inbound',
                        'has_attachments' => !empty($emailData['attachments']) ? 1 : 0
                    ], ['gmail_id']);
                    $saved++;
                } catch (Exception $e) {
                    error_log("Failed to save email: " . $e->getMessage());
                    // Return error for debugging
                    jsonResponse([
                        'success' => false,
                        'error' => 'Failed to save email: ' . $e->getMessage(),
                        'thread_id' => $threadId
                    ]);
                    return;
                }
            }

            jsonResponse([
                'success' => true,
                'thread_id' => $threadId,
                'emails_saved' => $saved,
                'contact_id' => $contactId
            ]);
            break;

        // ==========================================
        // Ask AI - Question about Thread/Contact
        // ==========================================
        case 'ask_ai':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            // Handle JSON or form data
            $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
            if (strpos($contentType, 'application/json') !== false) {
                $jsonData = json_decode(file_get_contents('php://input'), true);
                $question = trim($jsonData['question'] ?? '');
                $threadId = $jsonData['thread_id'] ?? null;
                $contactId = (int)($jsonData['contact_id'] ?? 0);
            } else {
                $question = trim($_POST['question'] ?? '');
                $threadId = $_POST['thread_id'] ?? null;
                $contactId = (int)($_POST['contact_id'] ?? 0);
            }

            if (!$question) jsonError('question required');

            // Auto-resolve long URL thread ID to short hex ID from cache
            if ($threadId) {
                $threadId = resolveThreadId($threadId);
            }

            // Build context
            $context = [];

            if ($threadId) {
                $emails = $db->fetchAll(
                    "SELECT * FROM emails WHERE thread_id = ? ORDER BY date ASC",
                    [$threadId]
                );
                $context['thread'] = $emails;
            }

            if ($contactId) {
                $contact = $db->fetchOne("SELECT * FROM contacts WHERE id = ?", [$contactId]);
                $profile = $db->fetchOne("SELECT * FROM contact_profiles WHERE contact_id = ?", [$contactId]);
                $context['contact'] = $contact;
                $context['profile'] = $profile;
            }

            // Build prompt with context
            $contextText = "";

            // Add Important Context
            $impCtx = new ImportantContext();
            $contextText .= $impCtx->getFormattedContext($contactId, $threadId);

            if (!empty($context['thread'])) {
                $contextText .= "EMAIL THREAD:\n";
                foreach ($context['thread'] as $email) {
                    $dir = $email['direction'] === 'inbound' ? 'FROM THEM' : 'FROM ME';
                    $contextText .= "{$dir} ({$email['date']}): {$email['subject']}\n";
                    $contextText .= substr($email['body_text'], 0, 500) . "\n\n";
                }
            }
            if (!empty($context['contact'])) {
                $contextText .= "CONTACT: {$context['contact']['name']} ({$context['contact']['email']})\n";
            }
            if (!empty($context['profile'])) {
                $contextText .= "PROFILE: Trust score: " . ($context['profile']['overall_trust_score'] ?? 'N/A') . "\n";
            }

            $prompt = <<<PROMPT
Based on this context:

{$contextText}

User question: {$question}

Provide a helpful, concise answer based on the email content and any available profile information.
Focus on actionable insights and specific observations from the emails.
PROMPT;

            // Call Claude
            $analyzer = new ThreadAnalyzer();

            try {
                $response = $analyzer->askClaude($prompt, $threadId);
                jsonResponse([
                    'success' => true,
                    'answer' => $response
                ]);
            } catch (Exception $e) {
                jsonError('AI request failed: ' . $e->getMessage());
            }
            break;

        // ==========================================
        // AI Chat - Multi-model chat with full context, agents & memory
        // ==========================================
        case 'ai_chat':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') jsonError('POST required', 405);

            $jsonData = json_decode(file_get_contents('php://input'), true) ?? [];
            $chatMessage   = trim($jsonData['message'] ?? '');
            $chatModel     = $jsonData['model'] ?? 'gpt-4o';
            $chatProvider  = $jsonData['provider'] ?? null;       // explicit provider from frontend
            $chatMode      = $jsonData['mode'] ?? 'general';
            $chatAgent     = $jsonData['agent'] ?? null;          // agent preset ID
            $chatThreadId  = $jsonData['thread_id'] ?? null;      // single thread (backward compat)
            $chatThreadIds = $jsonData['thread_ids'] ?? [];       // multi-thread context
            $chatContactId = (int)($jsonData['contact_id'] ?? 0);
            $chatHistory   = $jsonData['history'] ?? [];          // [{role,content}]
            $chatEmailId   = (int)($jsonData['email_id'] ?? 0);
            $chatSessionId = $jsonData['session_id'] ?? null;     // session persistence

            if (!$chatMessage) jsonError('message required');

            // Resolve thread IDs
            if ($chatThreadId) $chatThreadId = resolveThreadId($chatThreadId);
            $resolvedThreadIds = [];
            foreach ($chatThreadIds as $tid) {
                $resolved = resolveThreadId($tid);
                if ($resolved) $resolvedThreadIds[] = $resolved;
            }
            // Single thread_id fallback into the array
            if ($chatThreadId && empty($resolvedThreadIds)) {
                $resolvedThreadIds = [$chatThreadId];
            }

            // --- Agent presets ---
            $agentPresets = [
                'general' => [
                    'system' => "You are a business intelligence assistant with access to email and contact data. Answer questions, provide analysis, and help with business decisions.",
                    'autoMemory' => false,
                ],
                'quote_analyst' => [
                    'system' => "You are a pricing and quote analyst. Analyze email threads to find all pricing mentions, quotes sent, counter-offers, and final agreed amounts. Identify negotiation patterns, price sensitivity, and recommend optimal pricing strategies. Be specific with numbers and dates.",
                    'autoMemory' => true,
                ],
                'deception_detector' => [
                    'system' => "You are a deception and inconsistency analyst. Analyze email threads for contradictions between statements made at different times, tone shifts that suggest dishonesty, pressure tactics, evasive responses, broken promises, and manipulative language. Rate deception risk on a scale of 1-10 and cite specific evidence from the emails.",
                    'autoMemory' => true,
                ],
                'contact_profiler' => [
                    'system' => "You are a contact intelligence profiler. Analyze email threads to build a comprehensive profile of the contact: communication style, decision-making speed, priorities, negotiation habits, reliability, cultural cues, and relationship dynamics. Provide actionable insights for working with this person.",
                    'autoMemory' => true,
                ],
                'email_composer' => [
                    'system' => "You are a professional email composer. Based on the email thread context, compose a ready-to-send email reply. Match the appropriate tone and formality level. Consider the negotiation position and relationship dynamics. Write only the email text with greeting and sign-off.",
                    'autoMemory' => false,
                ],
            ];

            // Mode-specific system instructions (used when no agent selected)
            $modeInstructions = [
                'thread'  => "You are a business intelligence assistant analyzing an email thread. Provide insights about the conversation, relationships, risks, and opportunities. Be direct and specific.",
                'contact' => "You are a CRM intelligence assistant analyzing a contact's profile and history. Help understand their behavior, communication patterns, and how best to work with them.",
                'quote'   => "You are a business consultant helping estimate project costs and create quotes. Based on the email context, suggest realistic pricing, scope, and approach for software/CRM projects.",
                'reply'   => "You are an email writing assistant. Compose professional, ready-to-send email replies based on the thread context. Write only the reply text with greeting and sign-off.",
                'general' => "You are a business intelligence assistant with access to email and contact data. Answer questions, provide analysis, and help with business decisions.",
            ];

            // Select system prompt: agent takes priority over mode
            $activeAgent = ($chatAgent && isset($agentPresets[$chatAgent])) ? $agentPresets[$chatAgent] : null;
            $sysMsg = $activeAgent ? $activeAgent['system'] : ($modeInstructions[$chatMode] ?? $modeInstructions['general']);

            // --- Build context block ---
            $ctxParts = [];
            $ctxAdapter = QueryAdapter::getInstance();
            $ctxMongo = \EmailIntelligence\MongoDatabase::getInstance();
            $useMongoDB = $ctxAdapter->getActiveDatabase() === 'mongodb';
            $contextStats = ['threads' => 0, 'emails' => 0, 'estimated_tokens' => 0, 'truncated' => false];

            // Token budget per model (context window sizes)
            $modelTokenLimits = [
                'gpt-4o' => 128000, 'gpt-4o-mini' => 128000, 'o1' => 200000, 'o1-mini' => 128000,
                'gpt-5.2' => 128000, 'gpt-5.2-pro' => 128000, 'gpt-5-mini' => 128000, 'gpt-5-nano' => 128000, 'gpt-5' => 128000, 'gpt-4.1' => 128000,
                'claude-sonnet-4-5-20250929' => 200000, 'claude-opus-4-5-20251101' => 200000, 'claude-haiku-4-5-20251001' => 200000,
                'gemini-2.5-flash' => 1000000, 'gemini-3-pro' => 1000000, 'gemini-2.5-flash-lite' => 1000000,
                'grok-4' => 131000, 'grok-3' => 131000, 'grok-3-mini' => 131000,
                'deepseek-chat' => 64000, 'deepseek-reasoner' => 64000,
                'qwen-max-latest' => 128000, 'qwen-turbo-latest' => 128000,
                'mistral-large-latest' => 128000, 'mistral-small-latest' => 128000,
                'command-a-03-2025' => 128000, 'command-r7b-12-2024' => 128000,
            ];
            $tokenLimit = $modelTokenLimits[$chatModel] ?? 128000;
            $contextTokenBudget = (int)($tokenLimit * 0.6); // 60% for context, 40% for response + history

            // Multi-thread context
            $totalEmailCount = 0;
            $threadCount = count($resolvedThreadIds);
            foreach ($resolvedThreadIds as $idx => $tid) {
                if ($useMongoDB) {
                    $ctxEmails = $ctxMongo->findAll('emails', ['thread_id' => $tid], ['sort' => ['date' => 1]]);
                } else {
                    $ctxEmails = $db->fetchAll(
                        "SELECT direction, date, from_email, to_email, subject, body_text FROM emails WHERE thread_id = ? ORDER BY date ASC",
                        [$tid]
                    );
                }
                if ($ctxEmails) {
                    $threadLabel = $threadCount > 1 ? "THREAD " . ($idx + 1) . " OF {$threadCount}" : "EMAIL THREAD";
                    $ctxParts[] = "=== {$threadLabel} (" . count($ctxEmails) . " emails) ===";
                    foreach ($ctxEmails as $i => $em) {
                        $dir = ($em['direction'] ?? '') === 'inbound' ? 'FROM THEM' : 'FROM US';
                        // Strip quoted/duplicate content from replies, then truncate
                        $body = ThreadAnalyzer::stripQuotedContent($em['body_text'] ?? '');
                        $maxBody = $threadCount > 3 ? 500 : 800;
                        if (strlen($body) > $maxBody) $body = substr($body, 0, $maxBody) . '...';
                        $ctxParts[] = "Email #".($i+1)." {$dir} ({$em['date']})\nSubject: {$em['subject']}\n{$body}";
                    }
                    $totalEmailCount += count($ctxEmails);

                    // Include existing thread analysis summary if available
                    if ($useMongoDB) {
                        $ctxAnalysis = $ctxMongo->findOne('thread_analysis', ['thread_id' => $tid]);
                    } else {
                        $ctxAnalysis = $db->fetchOne("SELECT summary FROM thread_analysis WHERE thread_id = ? AND is_analyzed=1", [$tid]);
                    }
                    if ($ctxAnalysis && !empty($ctxAnalysis['summary'])) {
                        $ctxParts[] = "Analysis: " . $ctxAnalysis['summary'];
                    }
                }
            }

            // Add multi-thread summary header
            if ($threadCount > 1) {
                array_unshift($ctxParts, "=== MULTI-THREAD CONTEXT: {$totalEmailCount} total emails across {$threadCount} threads ===");
            }
            $contextStats['threads'] = $threadCount;
            $contextStats['emails'] = $totalEmailCount;

            // Contact context
            if ($chatContactId) {
                if ($useMongoDB) {
                    $ctxContact = $ctxMongo->findOne('contacts', ['id' => $chatContactId]);
                    $ctxProfile = $ctxMongo->findOne('contact_profiles', ['contact_id' => $chatContactId]);
                } else {
                    $ctxContact = $db->fetchOne("SELECT name, email, type, company FROM contacts WHERE id = ?", [$chatContactId]);
                    $ctxProfile = $db->fetchOne("SELECT * FROM contact_profiles WHERE contact_id = ?", [$chatContactId]);
                }
                if ($ctxContact) {
                    $ctxParts[] = "=== CONTACT: {$ctxContact['name']} ({$ctxContact['email']}) ===";
                    if ($ctxProfile) {
                        $ctxParts[] = "Trust score: " . ($ctxProfile['overall_trust_score'] ?? 'N/A') .
                            " | Communication style: " . ($ctxProfile['communication_style'] ?? 'N/A');
                    }
                }

                // Inject contact memory (persistent facts)
                $contactMemory = $ctxMongo->findOne('ai_contact_memory', ['contact_id' => $chatContactId]);
                if ($contactMemory && !empty($contactMemory['memories'])) {
                    $memFacts = array_map(function($m) { return "- " . $m['fact']; }, $contactMemory['memories']);
                    $ctxParts[] = "=== KNOWN FACTS ABOUT THIS CONTACT (from previous analyses) ===\n" . implode("\n", $memFacts);
                }
            }

            // Single email context (for reply mode)
            if ($chatEmailId) {
                if ($useMongoDB) {
                    $ctxEmail = $ctxMongo->findOne('emails', ['id' => $chatEmailId]);
                } else {
                    $ctxEmail = $db->fetchOne("SELECT * FROM emails WHERE id = ?", [$chatEmailId]);
                }
                if ($ctxEmail) {
                    $replyBody = ThreadAnalyzer::stripQuotedContent($ctxEmail['body_text'] ?? '');
                    $ctxParts[] = "=== SPECIFIC EMAIL TO REPLY TO ===\nFrom: {$ctxEmail['from_email']}\nSubject: {$ctxEmail['subject']}\n{$replyBody}";
                }
            }

            // Assemble context and apply token budget
            $contextBlock = implode("\n\n", $ctxParts);
            $estimatedTokens = (int)(strlen($contextBlock) / 3.5);
            $contextStats['estimated_tokens'] = $estimatedTokens;

            if ($estimatedTokens > $contextTokenBudget && $contextTokenBudget > 0) {
                // Truncate context to fit budget, keeping the end (most recent) content
                $maxChars = (int)($contextTokenBudget * 3.5);
                $contextBlock = substr($contextBlock, -$maxChars);
                $contextBlock = "[... Earlier context omitted to fit model token limit ({$estimatedTokens} tokens estimated, budget: {$contextTokenBudget}) ...]\n\n" . $contextBlock;
                $contextStats['truncated'] = true;
                $contextStats['estimated_tokens'] = $contextTokenBudget;
            }

            if ($contextBlock) {
                $sysMsg .= "\n\nCONTEXT:\n" . $contextBlock;
            }

            // Auto-memory instruction for agents that support it
            if ($activeAgent && $activeAgent['autoMemory'] && $chatContactId) {
                $sysMsg .= "\n\nIMPORTANT: If you discover important new facts about this contact (pricing patterns, contradictions, preferences, behavioral traits), include them at the end of your response in this exact format:\n[MEMORY]fact 1[/MEMORY]\n[MEMORY]fact 2[/MEMORY]\nKeep each fact concise (one sentence). Only include genuinely new, important insights.";
            }

            // Build messages array with history
            $messages = [];
            foreach ($chatHistory as $h) {
                if (isset($h['role'], $h['content'])) {
                    $messages[] = ['role' => $h['role'], 'content' => $h['content']];
                }
            }
            $messages[] = ['role' => 'user', 'content' => $chatMessage];

            // Determine provider (explicit field takes priority, then infer from model name)
            if (!$chatProvider) {
                $chatProvider = 'openai';
                if (strpos($chatModel, 'claude') !== false) $chatProvider = 'claude';
                elseif (strpos($chatModel, 'gemini') !== false) $chatProvider = 'gemini';
                elseif (strpos($chatModel, 'grok') !== false) $chatProvider = 'grok';
                elseif (strpos($chatModel, 'deepseek') !== false) $chatProvider = 'deepseek';
                elseif (strpos($chatModel, 'qwen') !== false) $chatProvider = 'qwen';
                elseif (strpos($chatModel, 'mistral') !== false || strpos($chatModel, 'codestral') !== false) $chatProvider = 'mistral';
                elseif (strpos($chatModel, 'command') !== false) $chatProvider = 'cohere';
            }

            // Route through proxy for multi-model support
            $proxyUrl = 'https://mssql.danmarcrm.com/dev1/extension2/aiapi/api_codex51/api/index.php';
            $proxyPayload = json_encode([
                'action'       => 'ask',
                'user_content' => $chatMessage,
                'system'       => $sysMsg,
                'models'       => [$chatProvider => $chatModel],  // keyed by provider code
                'providers'    => [$chatProvider],
                'history'      => array_slice($chatHistory, -10),
            ]);

            $ch = curl_init($proxyUrl);
            curl_setopt_array($ch, [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_POST => true,
                CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
                CURLOPT_POSTFIELDS => $proxyPayload,
                CURLOPT_TIMEOUT => 120,
            ]);
            $proxyResponse = curl_exec($ch);
            $curlError = curl_error($ch);
            curl_close($ch);

            if ($curlError) {
                // Fallback to direct ThreadAnalyzer if proxy fails
                try {
                    $analyzer = new ThreadAnalyzer($chatProvider === 'openai' ? 'openai' : 'claude', $chatModel);
                    $fullPrompt = $sysMsg . "\n\nUser: " . $chatMessage;
                    $answer = $analyzer->askClaude($fullPrompt);
                    jsonResponse(['success' => true, 'answer' => $answer, 'model' => $chatModel, 'via' => 'direct', 'context_stats' => $contextStats]);
                } catch (Exception $e) {
                    jsonError('AI request failed: ' . $e->getMessage());
                }
                break;
            }

            $proxyResult = json_decode($proxyResponse, true);
            if (!$proxyResult || !$proxyResult['success']) {
                jsonError('AI proxy error: ' . ($proxyResult['error'] ?? 'Unknown error'));
            }

            // Extract answer from proxy response
            $answer = '';
            $usedModel = $chatModel;
            $responses = $proxyResult['responses'] ?? [];
            if (!empty($responses)) {
                $firstResponse = reset($responses);
                $answer = $firstResponse['response'] ?? $firstResponse['content'] ?? '';
                $usedModel = $firstResponse['model'] ?? $chatModel;
            }
            if (!$answer) {
                $answer = $proxyResult['content'] ?? $proxyResult['response'] ?? '';
            }
            if (!$answer) {
                jsonError('AI proxy returned empty response. Raw: ' . substr($proxyResponse, 0, 200));
            }

            // Auto-extract memories from AI response
            $extractedMemories = [];
            if ($activeAgent && $activeAgent['autoMemory'] && $chatContactId) {
                if (preg_match_all('/\[MEMORY\](.*?)\[\/MEMORY\]/s', $answer, $memMatches)) {
                    foreach ($memMatches[1] as $memFact) {
                        $memFact = trim($memFact);
                        if ($memFact) $extractedMemories[] = $memFact;
                    }
                    // Strip [MEMORY] tags from the displayed answer
                    $answer = preg_replace('/\s*\[MEMORY\].*?\[\/MEMORY\]\s*/s', '', $answer);
                    $answer = trim($answer);

                    // Save extracted memories to ai_contact_memory
                    if (!empty($extractedMemories)) {
                        $existingMemory = $ctxMongo->findOne('ai_contact_memory', ['contact_id' => $chatContactId]);
                        $memories = $existingMemory ? ($existingMemory['memories'] ?? []) : [];
                        foreach ($extractedMemories as $fact) {
                            $memories[] = [
                                'id' => uniqid('mem_', true),
                                'fact' => $fact,
                                'source' => 'auto',
                                'agent' => $chatAgent,
                                'session_id' => $chatSessionId,
                                'created_at' => date('Y-m-d H:i:s'),
                            ];
                        }
                        $ctxMongo->insertOrUpdate('ai_contact_memory', [
                                'contact_id' => $chatContactId,
                                'contact_email' => $ctxContact['email'] ?? '',
                                'memories' => $memories,
                                'updated_at' => date('Y-m-d H:i:s'),
                            ], ['contact_id']);
                    }
                }
            }

            // Auto-save chat session
            if (!$chatSessionId) {
                $chatSessionId = uniqid('sess_', true);
            }
            $sessionMessages = [];
            // Load existing session messages if continuing
            $existingSession = $ctxMongo->findOne('ai_chat_sessions', ['session_id' => $chatSessionId]);
            if ($existingSession && !empty($existingSession['messages'])) {
                $sessionMessages = $existingSession['messages'];
            }
            // Append new message pair
            $sessionMessages[] = ['role' => 'user', 'content' => $chatMessage, 'timestamp' => date('Y-m-d H:i:s')];
            $sessionMessages[] = ['role' => 'assistant', 'content' => $answer, 'timestamp' => date('Y-m-d H:i:s'), 'model' => $usedModel];

            $sessionTitle = $existingSession['title'] ?? substr($chatMessage, 0, 60);
            $ctxMongo->insertOrUpdate('ai_chat_sessions', [
                    'session_id' => $chatSessionId,
                    'created_at' => $existingSession['created_at'] ?? date('Y-m-d H:i:s'),
                    'updated_at' => date('Y-m-d H:i:s'),
                    'title' => $sessionTitle,
                    'agent' => $chatAgent ?? $chatMode,
                    'provider' => $chatProvider,
                    'model' => $usedModel,
                    'context_thread_ids' => $resolvedThreadIds,
                    'contact_id' => $chatContactId ?: null,
                    'contact_name' => $ctxContact['name'] ?? null,
                    'messages' => $sessionMessages,
                ], ['session_id']);

            jsonResponse([
                'success'     => true,
                'answer'      => $answer,
                'model'       => $usedModel,
                'provider'    => $chatProvider,
                'via'         => 'proxy',
                'session_id'  => $chatSessionId,
                'context_stats' => $contextStats,
                'extracted_memories' => $extractedMemories,
            ]);
            break;

        // ==========================================
        // Generate Reply Suggestions (with ChromaDB context)
        // ==========================================
        case 'generate_reply':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            // Handle JSON or form data
            $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
            if (strpos($contentType, 'application/json') !== false) {
                $jsonData = json_decode(file_get_contents('php://input'), true);
                $threadId = $jsonData['thread_id'] ?? '';
                $tone = $jsonData['tone'] ?? 'professional';
                $intent = $jsonData['intent'] ?? '';
                $customInstructions = $jsonData['custom_instructions'] ?? '';
                $aiModel = $jsonData['ai_model'] ?? 'openai';
            } else {
                $threadId = $_POST['thread_id'] ?? '';
                $tone = $_POST['tone'] ?? 'professional';
                $intent = $_POST['intent'] ?? '';
                $customInstructions = $_POST['custom_instructions'] ?? '';
                $aiModel = $_POST['ai_model'] ?? 'openai';
            }

            if (!$threadId) jsonError('thread_id required');

            // Auto-resolve long URL thread ID to short hex ID from cache
            $threadId = resolveThreadId($threadId);

            // Get thread context from MongoDB
            $mongo = \EmailIntelligence\MongoDatabase::getInstance();
            $emails = $mongo->findAll('emails', ['thread_id' => $threadId], [
                'sort' => ['date' => 1]
            ]);

            if (empty($emails)) {
                jsonError('Thread not found');
            }

            // Get contact ID from the first email
            $contactId = $emails[0]['contact_id'] ?? null;

            // Get contact profile if available
            $profile = null;
            $profileStr = '';
            if ($contactId) {
                $profile = $mongo->findOne('contact_profiles', ['contact_id' => (int)$contactId]);
                if ($profile) {
                    $profileStr = "\n\nCLIENT PROFILE:
- Communication Style: " . ($profile['communication_style'] ?? 'Unknown') . "
- Honesty Score: " . ($profile['honesty_score'] ?? 'N/A') . "%
- Best Approach: " . implode(', ', $profile['best_approach'] ?? []);
                }
            }

            // Build thread summary from MongoDB emails
            $threadSummary = "";
            foreach ($emails as $email) {
                $direction = ($email['direction'] ?? '') === 'inbound' ? 'FROM THEM' : 'FROM US';
                $body = $email['body_text'] ?? $email['snippet'] ?? '';
                $threadSummary .= "{$direction} ({$email['date']}): " . substr($body, 0, 500) . "\n\n";
            }

            // Add Important Context
            $impCtx = new ImportantContext();
            $importantContext = $impCtx->getFormattedContext($contactId, $threadId);

            // ========================================
            // ChromaDB: Get relevant context for reply
            // ========================================
            $vectorContext = '';
            $patternsContext = '';

            try {
                $vectorMemory = new \EmailIntelligence\VectorMemory();

                if ($vectorMemory->isHealthy()) {
                    // Get the last email content for semantic search
                    $lastEmail = end($emails);
                    $lastEmailContent = $lastEmail['subject'] . ' ' . substr($lastEmail['body_text'] ?? '', 0, 500);

                    // 1. Find similar past exchanges to inform reply style
                    $similarExchanges = $vectorMemory->search(
                        $lastEmailContent,
                        3,
                        [
                            'collection' => \EmailIntelligence\VectorMemory::COLLECTION_THREADS,
                            'contact_id' => $contactId
                        ]
                    );

                    if (!empty($similarExchanges)) {
                        $vectorContext = "\n\nRELEVANT CONTEXT FROM PAST INTERACTIONS:\n";
                        foreach ($similarExchanges as $i => $result) {
                            $vectorContext .= "- " . substr($result['content'] ?? '', 0, 300) . "\n";
                        }
                    }

                    // 2. Get behavioral patterns for this contact
                    if ($contactId) {
                        $patterns = $vectorMemory->search("patterns behaviors", 3, [
                            'collection' => \EmailIntelligence\VectorMemory::COLLECTION_PATTERNS,
                            'contact_id' => $contactId
                        ]);

                        if (!empty($patterns)) {
                            $patternsContext = "\n\nCLIENT BEHAVIORAL PATTERNS (adapt your response accordingly):\n";
                            foreach ($patterns as $pattern) {
                                $patternsContext .= "- " . substr($pattern['content'] ?? '', 0, 200) . "\n";
                            }
                        }
                    }
                }
            } catch (Exception $e) {
                // ChromaDB not available - continue without vector context
                error_log("ChromaDB context fetch failed: " . $e->getMessage());
            }

            $customSection = $customInstructions ? "\nCustom instructions: {$customInstructions}" : "";
            $intentSection = $intent ? "\n- Intent: {$intent}" : "";

            // Determine provider from model name
            // Claude models contain 'claude' in the name, OpenAI models don't
            $provider = (strpos($aiModel, 'claude') !== false) ? 'claude' : 'openai';
            $analyzer = new ThreadAnalyzer($provider, $aiModel);

            try {
                // If custom instructions provided, generate a single tailored reply
                if ($customInstructions) {
                    $prompt = <<<PROMPT
Given this email thread:

{$threadSummary}
{$profileStr}
{$importantContext}
{$vectorContext}
{$patternsContext}

Generate a professional email reply with these requirements:
- Tone: {$tone}
- Instructions: {$customInstructions}{$intentSection}

Consider the client's behavioral patterns and past interactions when crafting your response.
Write ONLY the reply text, ready to copy and send. Do not include any explanation or metadata.
Start directly with the greeting (e.g., "Hi [name]," or "Dear [name],") and end with a professional sign-off.
PROMPT;

                    $response = $analyzer->askClaude($prompt, $threadId);
                    $replyText = trim($response);

                    // Log the AI task
                    $requestData = [
                        'thread_id' => $threadId,
                        'contact_id' => $contactId,
                        'tone' => $tone,
                        'custom_instructions' => $customInstructions,
                        'intent' => $intent,
                        'ai_model' => $aiModel,
                        'ai_provider' => $provider,
                        'context_sources' => [
                            'emails_in_thread' => count($emails ?? []),
                            'used_vector_context' => !empty($vectorContext),
                            'used_patterns' => !empty($patternsContext)
                        ]
                    ];

                    $responseData = [
                        'success' => true,
                        'reply' => $replyText,
                        'used_vector_context' => !empty($vectorContext)
                    ];

                    logAITask('generate_reply', $requestData, $prompt, json_encode($responseData, JSON_PRETTY_PRINT));

                    jsonResponse($responseData);
                } else {
                    // Generate 3 options when no custom instructions
                    $prompt = <<<PROMPT
Given this email thread:

{$threadSummary}
{$profileStr}
{$importantContext}
{$vectorContext}
{$patternsContext}

Generate 3 professional reply options with these characteristics:
- Tone: {$tone}{$intentSection}

Consider the client's behavioral patterns and past interactions when crafting options.

For each option, provide:
1. A brief label (e.g., "Firm but polite", "Friendly follow-up", etc.)
2. The full reply text ready to send

Respond ONLY with valid JSON in this exact format:
{
  "options": [
    {"label": "Label 1", "text": "Full reply text here..."},
    {"label": "Label 2", "text": "Full reply text here..."},
    {"label": "Label 3", "text": "Full reply text here..."}
  ]
}
PROMPT;

                    $response = $analyzer->askClaude($prompt, $threadId);
                    // Try to extract JSON from response
                    $jsonMatch = preg_match('/\{[\s\S]*"options"[\s\S]*\}/', $response, $matches);
                    if ($jsonMatch) {
                        $parsed = json_decode($matches[0], true);
                    } else {
                        $parsed = json_decode($response, true);
                    }

                    // Log the AI task
                    $requestData = [
                        'thread_id' => $threadId,
                        'contact_id' => $contactId,
                        'tone' => $tone,
                        'intent' => $intent,
                        'generation_mode' => 'multiple_options',
                        'ai_model' => $aiModel,
                        'ai_provider' => $provider,
                        'context_sources' => [
                            'emails_in_thread' => count($emails ?? []),
                            'used_vector_context' => !empty($vectorContext),
                            'used_patterns' => !empty($patternsContext)
                        ]
                    ];

                    $responseData = [
                        'success' => true,
                        'options' => $parsed['options'] ?? [],
                        'used_vector_context' => !empty($vectorContext),
                        'option_count' => count($parsed['options'] ?? [])
                    ];

                    logAITask('generate_reply', $requestData, $prompt, json_encode($responseData, JSON_PRETTY_PRINT));

                    jsonResponse([
                        'success' => true,
                        'options' => $parsed['options'] ?? [],
                        'used_vector_context' => !empty($vectorContext)
                    ]);
                }
            } catch (Exception $e) {
                jsonError('Reply generation failed: ' . $e->getMessage());
            }
            break;

        // ==========================================
        // Suggest Quick Replies (5 AI-generated options with ChromaDB)
        // ==========================================
        case 'suggest_replies':
            $threadId = $_GET['thread_id'] ?? '';
            $aiModel = $_GET['ai_model'] ?? 'openai';
            if (!$threadId) jsonError('thread_id required');

            // Auto-resolve long URL thread ID to short hex ID from cache
            $threadId = resolveThreadId($threadId);

            // Get thread emails from MongoDB
            $mongo = \EmailIntelligence\MongoDatabase::getInstance();
            $emails = $mongo->findAll('emails', ['thread_id' => $threadId], [
                'sort' => ['date' => 1]
            ]);

            if (empty($emails)) {
                jsonError('Thread not found');
            }

            // Get contact ID and profile
            $contactId = $emails[0]['contact_id'] ?? null;
            $profileContext = '';
            if ($contactId) {
                $profile = $mongo->findOne('contact_profiles', ['contact_id' => (int)$contactId]);
                if ($profile) {
                    $profileContext = "\n\nCLIENT PROFILE:
- Communication Style: " . ($profile['communication_style'] ?? 'Unknown') . "
- Best Approach: " . implode(', ', $profile['best_approach'] ?? []);
                }
            }

            // Get the last email to understand context
            $lastEmail = end($emails);
            $isLastInbound = ($lastEmail['direction'] ?? '') === 'inbound';

            // Build thread summary (last 5 emails for context)
            $recentEmails = array_slice($emails, -5);
            $threadSummary = "";
            foreach ($recentEmails as $email) {
                $direction = ($email['direction'] ?? '') === 'inbound' ? 'FROM CLIENT' : 'FROM ME';
                $body = strip_tags($email['body_text'] ?? $email['snippet'] ?? '');
                $body = substr($body, 0, 800);
                $threadSummary .= "{$direction} ({$email['from_name']} - {$email['date']}):\n{$body}\n\n---\n\n";
            }

            // ========================================
            // ChromaDB: Get relevant context
            // ========================================
            $vectorContext = '';

            try {
                $vectorMemory = new \EmailIntelligence\VectorMemory();

                if ($vectorMemory->isHealthy() && $contactId) {
                    // Get behavioral patterns for this contact
                    $patterns = $vectorMemory->search("patterns behaviors communication", 3, [
                        'collection' => \EmailIntelligence\VectorMemory::COLLECTION_PATTERNS,
                        'contact_id' => $contactId
                    ]);

                    if (!empty($patterns)) {
                        $vectorContext = "\n\nKNOWN CLIENT PATTERNS:\n";
                        foreach ($patterns as $pattern) {
                            $vectorContext .= "- " . substr($pattern['content'] ?? '', 0, 150) . "\n";
                        }
                    }
                }
            } catch (Exception $e) {
                error_log("ChromaDB context fetch failed: " . $e->getMessage());
            }

            // Get AI persona from config
            $persona = defined('AI_PERSONA') ? AI_PERSONA : 'I am a professional responding to client emails.';

            // Determine provider from model name
            // Claude models contain 'claude' in the name, OpenAI models don't
            $provider = (strpos($aiModel, 'claude') !== false) ? 'claude' : 'openai';
            $analyzer = new ThreadAnalyzer($provider, $aiModel);

            try {
                $prompt = <<<PROMPT
{$persona}

Here is my client's email thread:

{$threadSummary}
{$profileContext}
{$vectorContext}

Based on this conversation and the client's known patterns/profile, suggest 5 different reply options I could send. Each option should be a complete, ready-to-send email reply.

The options should cover different approaches:
1. A positive/agreeable response
2. A clarifying question or request for more info
3. A polite way to set boundaries or decline if needed
4. A solution-focused response addressing their needs
5. A brief acknowledgment/follow-up scheduling response

Tailor your suggestions based on the client's communication style and patterns.

Respond ONLY with valid JSON in this exact format (no markdown, no explanation):
{
  "suggestions": [
    {"label": "Short descriptive label", "preview": "First 50 chars of reply...", "text": "Full reply text here with greeting and sign-off"},
    {"label": "Label 2", "preview": "Preview...", "text": "Full text..."},
    {"label": "Label 3", "preview": "Preview...", "text": "Full text..."},
    {"label": "Label 4", "preview": "Preview...", "text": "Full text..."},
    {"label": "Label 5", "preview": "Preview...", "text": "Full text..."}
  ]
}
PROMPT;

                $response = $analyzer->askClaude($prompt, $threadId);

                // Extract JSON from response
                $jsonMatch = preg_match('/\{[\s\S]*"suggestions"[\s\S]*\}/', $response, $matches);
                if ($jsonMatch) {
                    $parsed = json_decode($matches[0], true);
                } else {
                    $parsed = json_decode($response, true);
                }

                if (!$parsed || !isset($parsed['suggestions'])) {
                    // Fallback: try to parse as-is
                    $parsed = ['suggestions' => []];
                }

                // Log the AI task
                $requestData = [
                    'thread_id' => $threadId,
                    'contact_id' => $contactId,
                    'last_email_from' => $lastEmail['from_name'] ?? $lastEmail['from_email'] ?? 'Unknown',
                    'ai_model' => $aiModel,
                    'ai_provider' => $provider,
                    'context_sources' => [
                        'emails_in_thread' => count($emails ?? []),
                        'used_vector_context' => !empty($vectorContext)
                    ]
                ];

                $responseData = [
                    'success' => true,
                    'thread_id' => $threadId,
                    'suggestion_count' => count($parsed['suggestions'] ?? []),
                    'last_email_direction' => $lastEmail['direction'] ?? 'unknown',
                    'used_vector_context' => !empty($vectorContext)
                ];

                logAITask('suggest_replies', $requestData, $prompt, json_encode($responseData, JSON_PRETTY_PRINT));

                jsonResponse([
                    'success' => true,
                    'thread_id' => $threadId,
                    'suggestions' => $parsed['suggestions'] ?? [],
                    'last_email_direction' => $lastEmail['direction'] ?? 'unknown',
                    'last_email_from' => $lastEmail['from_name'] ?? $lastEmail['from_email'] ?? 'Unknown',
                    'used_vector_context' => !empty($vectorContext)
                ]);

            } catch (Exception $e) {
                jsonError('Suggestion generation failed: ' . $e->getMessage());
            }
            break;

        // ==========================================
        // Client Intelligence - Ask questions about a client
        // ==========================================
        case 'client_insights':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            // Handle JSON or form data
            $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
            if (strpos($contentType, 'application/json') !== false) {
                $jsonData = json_decode(file_get_contents('php://input'), true);
                $contactId = (int)($jsonData['contact_id'] ?? 0);
                $question = trim($jsonData['question'] ?? '');
                $template = $jsonData['template'] ?? null;
            } else {
                $contactId = (int)($_POST['contact_id'] ?? 0);
                $question = trim($_POST['question'] ?? '');
                $template = $_POST['template'] ?? null;
            }

            if (!$contactId) {
                jsonError('contact_id required');
            }
            if (!$question) {
                jsonError('question required');
            }

            $insightsAnalyzer = new \EmailIntelligence\ClientInsightsAnalyzer();

            if (!$insightsAnalyzer->isConfigured()) {
                jsonError('AI not configured. Please set API key in config.php');
            }

            try {
                $result = $insightsAnalyzer->analyzeClient($contactId, $question, $template);
                jsonResponse($result);
            } catch (Exception $e) {
                jsonError('Analysis failed: ' . $e->getMessage());
            }
            break;

        // ==========================================
        // Global Insights - Questions about all clients
        // ==========================================
        case 'global_insights':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            // Handle JSON or form data
            $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
            if (strpos($contentType, 'application/json') !== false) {
                $jsonData = json_decode(file_get_contents('php://input'), true);
                $question = trim($jsonData['question'] ?? '');
            } else {
                $question = trim($_POST['question'] ?? '');
            }

            if (!$question) {
                jsonError('question required');
            }

            $insightsAnalyzer = new \EmailIntelligence\ClientInsightsAnalyzer();

            if (!$insightsAnalyzer->isConfigured()) {
                jsonError('AI not configured. Please set API key in config.php');
            }

            try {
                $result = $insightsAnalyzer->analyzeGlobal($question);
                jsonResponse($result);
            } catch (Exception $e) {
                jsonError('Analysis failed: ' . $e->getMessage());
            }
            break;

        // ==========================================
        // Client Insights - Get templates and contacts
        // ==========================================
        case 'insights_meta':
            $insightsAnalyzer = new \EmailIntelligence\ClientInsightsAnalyzer();

            $search = $_GET['search'] ?? '';
            $contacts = $search
                ? $insightsAnalyzer->searchContacts($search)
                : $insightsAnalyzer->getContactList();

            jsonResponse([
                'templates' => $insightsAnalyzer->getQueryTemplates(),
                'contacts' => $contacts,
                'ai_configured' => $insightsAnalyzer->isConfigured()
            ]);
            break;

        // ==========================================
        // Ask Contact AI - Comprehensive contact analysis with full context
        // ==========================================
        case 'ask_contact_ai':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            // Handle JSON or form data
            $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
            if (strpos($contentType, 'application/json') !== false) {
                $jsonData = json_decode(file_get_contents('php://input'), true);
                $contactId = (int)($jsonData['contact_id'] ?? 0);
                $threadId = $jsonData['thread_id'] ?? null;
                $question = trim($jsonData['question'] ?? '');
                $provider = $jsonData['provider'] ?? 'claude';
                $model = $jsonData['model'] ?? 'claude-sonnet-4-5-20250929';
                $conversationHistory = $jsonData['conversation_history'] ?? '';
                $savedContext = $jsonData['saved_context'] ?? '';
            } else {
                $contactId = (int)($_POST['contact_id'] ?? 0);
                $threadId = $_POST['thread_id'] ?? null;
                $question = trim($_POST['question'] ?? '');
                $provider = $_POST['provider'] ?? 'claude';
                $model = $_POST['model'] ?? 'claude-sonnet-4-5-20250929';
                $conversationHistory = $_POST['conversation_history'] ?? '';
                $savedContext = $_POST['saved_context'] ?? '';
            }

            if (!$contactId) jsonError('contact_id required');
            if (!$question) jsonError('question required');

            // Auto-resolve long URL thread ID to short hex ID from cache
            if ($threadId) {
                $threadId = resolveThreadId($threadId);
            }

            // Check AI configuration
            $analyzer = new ThreadAnalyzer();
            if (!$analyzer->isConfigured()) {
                jsonError('AI not configured. Please set API keys in config.php');
            }

            try {
                // Get contact info
                $contact = $db->fetchOne("SELECT * FROM contacts WHERE id = ?", [$contactId]);
                if (!$contact) jsonError('Contact not found', 404);

                // Layer 1: Structured Data (~1,000 tokens)
                $profile = $db->fetchOne("SELECT * FROM contact_profiles WHERE contact_id = ?", [$contactId]);

                // Get quote stats from MongoDB
                $mongo = \EmailIntelligence\MongoDatabase::getInstance();
                $quotes = $mongo->findAll('quotes', ['contact_id' => $contactId], [
                    'sort' => ['quote_date' => -1],
                    'limit' => 50
                ]);

                $quoteStats = [
                    'total_quotes' => count($quotes),
                    'total_value' => array_sum(array_column($quotes, 'amount')),
                    'avg_value' => count($quotes) > 0 ? array_sum(array_column($quotes, 'amount')) / count($quotes) : 0,
                    'accepted' => count(array_filter($quotes, fn($q) => ($q['outcome'] ?? '') === 'accepted')),
                    'rejected' => count(array_filter($quotes, fn($q) => ($q['outcome'] ?? '') === 'rejected')),
                    'recent_quotes' => array_slice($quotes, 0, 5)
                ];

                // Get payment behavior & patterns
                $behaviors = $db->fetchAll(
                    "SELECT * FROM learned_behaviors WHERE contact_id = ? AND is_active = 1 ORDER BY severity DESC LIMIT 20",
                    [$contactId]
                );

                $alerts = $db->fetchAll(
                    "SELECT * FROM alerts WHERE contact_id = ? AND is_dismissed = 0 ORDER BY created_at DESC LIMIT 10",
                    [$contactId]
                );

                // PREVIOUS THREAD ANALYSIS - Include deception/trust scores from past threads
                $previousAnalysis = $db->fetchAll(
                    "SELECT tp.thread_id, tp.trust_score, tp.deception_score, tp.manipulation_score,
                            tp.reliability_score, tp.risk_level, tp.verdict, tp.overall_assessment, tp.red_flags,
                            ta.summary as thread_summary, ta.last_email_date,
                            (SELECT subject FROM emails WHERE thread_id = tp.thread_id LIMIT 1) as subject
                     FROM thread_participants tp
                     JOIN thread_analysis ta ON tp.thread_id = ta.thread_id
                     WHERE tp.contact_id = ? AND ta.is_analyzed = 1
                     ORDER BY ta.last_email_date DESC
                     LIMIT 10",
                    [$contactId]
                );

                // Layer 2: Recent Emails (~6,000 tokens)
                $recentEmails = $db->fetchAll(
                    "SELECT * FROM emails WHERE contact_id = ? ORDER BY date DESC LIMIT 20",
                    [$contactId]
                );

                // Layer 3: Historical Summaries (~2,000 tokens)
                $threadSummaries = $db->fetchAll(
                    "SELECT ta.thread_id, ta.summary, ta.email_count, ta.last_email_date,
                            (SELECT subject FROM emails WHERE thread_id = ta.thread_id LIMIT 1) as subject
                     FROM thread_analysis ta
                     WHERE ta.contact_id = ? AND ta.is_analyzed = 1
                     ORDER BY ta.last_email_date DESC
                     LIMIT 30",
                    [$contactId]
                );

                // Layer 4: Vector Search for relevant emails (~3,000 tokens)
                $relevantEmails = [];
                $vectorMemory = new VectorMemory();
                if ($vectorMemory->isHealthy()) {
                    // Refine search query to include subject if available
                    $searchQuery = $question;
                    if ($threadId && !empty($threadEmails)) {
                        $subject = $threadEmails[0]['subject'] ?? '';
                        if ($subject) {
                            $searchQuery .= " " . $subject;
                        }
                    }

                    $searchResults = $vectorMemory->search($searchQuery, 10, [
                        'collection' => VectorMemory::COLLECTION_EMAILS,
                        'contact_id' => $contactId
                    ]);

                    foreach ($searchResults as $result) {
                        $emailId = $result['metadata']['email_id'] ?? null;
                        // Skip emails from current thread to avoid duplication
                        if ($emailId && $threadId && isset($result['metadata']['thread_id']) && $result['metadata']['thread_id'] === $threadId) {
                            continue;
                        }

                        if ($emailId) {
                            $email = $db->fetchOne("SELECT * FROM emails WHERE id = ?", [$emailId]);
                            if ($email) {
                                $relevantEmails[] = array_merge($email, [
                                    'relevance_score' => $result['similarity'] ?? 0
                                ]);
                            }
                        }
                    }
                }

                // Build comprehensive AI prompt
                $structuredContext = "CONTACT PROFILE:\n";
                $structuredContext .= "Name: {$contact['name']}\n";
                $structuredContext .= "Email: {$contact['email']}\n";
                $structuredContext .= "Company: " . ($contact['company'] ?? 'N/A') . "\n";
                $structuredContext .= "Total Emails: {$contact['total_emails']}\n\n";

                // Add Important Context
                $impCtx = new ImportantContext();
                $structuredContext .= $impCtx->getFormattedContext($contactId, $threadId);

                // Add Current Thread Context (High Priority)
                $currentThreadContext = "";
                if ($threadId) {
                    $threadEmails = $db->fetchAll(
                        "SELECT * FROM emails WHERE thread_id = ? ORDER BY date ASC",
                        [$threadId]
                    );
                    if (!empty($threadEmails)) {
                        $currentThreadContext = "CURRENT THREAD (Focus on this context):\n";
                        foreach ($threadEmails as $email) {
                            $dir = $email['direction'] === 'inbound' ? 'FROM THEM' : 'FROM US';
                            $currentThreadContext .= "{$dir} ({$email['date']}): {$email['subject']}\n";
                            $currentThreadContext .= substr($email['body_text'], 0, 800) . "\n\n";
                        }
                    }
                }

                if ($profile) {
                    $structuredContext .= "BEHAVIORAL SCORES:\n";
                    $structuredContext .= "- Trust Score: " . ($profile['overall_trust_score'] ?? 'N/A') . "\n";
                    $structuredContext .= "- Reliability Score: " . ($profile['reliability_score'] ?? 'N/A') . "\n";
                    $structuredContext .= "- Honesty Score: " . ($profile['honesty_score'] ?? 'N/A') . "%\n";
                    $structuredContext .= "- Risk Level: " . ($profile['risk_level'] ?? 'N/A') . "\n";
                    $structuredContext .= "- Communication Style: " . ($profile['communication_style'] ?? 'N/A') . "\n";
                    $structuredContext .= "- Pricing Sensitivity: " . ($profile['pricing_sensitivity'] ?? 'N/A') . "\n\n";
                }

                $structuredContext .= "QUOTE HISTORY:\n";
                $structuredContext .= "- Total Quotes: {$quoteStats['total_quotes']}\n";
                $structuredContext .= "- Total Value: $" . number_format($quoteStats['total_value'], 2) . "\n";
                $structuredContext .= "- Average Quote: $" . number_format($quoteStats['avg_value'], 2) . "\n";
                $structuredContext .= "- Acceptance Rate: " . ($quoteStats['total_quotes'] > 0 ? round(($quoteStats['accepted'] / $quoteStats['total_quotes']) * 100) : 0) . "%\n";

                if (!empty($quoteStats['recent_quotes'])) {
                    $structuredContext .= "\nRecent Quotes:\n";
                    foreach ($quoteStats['recent_quotes'] as $q) {
                        $amount = isset($q['amount']) ? '$' . number_format($q['amount'], 2) : 'N/A';
                        $date = $q['quote_date'] ?? $q['date'] ?? 'N/A';
                        $outcome = $q['outcome'] ?? 'pending';
                        $desc = substr($q['description'] ?? $q['subject'] ?? '', 0, 100);
                        $structuredContext .= "  - {$date}: {$desc} - {$amount} ({$outcome})\n";
                    }
                }

                if (!empty($behaviors)) {
                    $structuredContext .= "\nBEHAVIORAL PATTERNS:\n";
                    foreach ($behaviors as $b) {
                        $structuredContext .= "  - {$b['behavior_type']}: {$b['description']} (frequency: {$b['frequency']})\n";
                    }
                }

                if (!empty($alerts)) {
                    $structuredContext .= "\nACTIVE ALERTS:\n";
                    foreach ($alerts as $a) {
                        $structuredContext .= "  - [{$a['severity']}] {$a['message']}\n";
                    }
                }

                // PREVIOUS THREAD ANALYSIS SCORES (Critical for understanding history)
                if (!empty($previousAnalysis)) {
                    $structuredContext .= "\n=== PREVIOUS THREAD ANALYSIS (IMPORTANT) ===\n";
                    $structuredContext .= "These are analysis results from past email threads with this contact:\n\n";
                    foreach ($previousAnalysis as $pa) {
                        $deceptionPct = round(($pa['deception_score'] ?? 0) * 100);
                        $trustPct = round(($pa['trust_score'] ?? 0.5) * 100);
                        $structuredContext .= "Thread: {$pa['subject']} ({$pa['last_email_date']})\n";
                        $structuredContext .= "  - DECEPTION SCORE: {$deceptionPct}%\n";
                        $structuredContext .= "  - Trust Score: {$trustPct}%\n";
                        if ($pa['risk_level']) {
                            $structuredContext .= "  - Risk Level: {$pa['risk_level']}\n";
                        }
                        if ($pa['verdict']) {
                            $structuredContext .= "  - Verdict: {$pa['verdict']}\n";
                        }
                        if ($pa['overall_assessment']) {
                            $structuredContext .= "  - Assessment: " . substr($pa['overall_assessment'], 0, 200) . "\n";
                        }
                        if ($pa['red_flags']) {
                            $redFlags = is_string($pa['red_flags']) ? json_decode($pa['red_flags'], true) : $pa['red_flags'];
                            if (!empty($redFlags)) {
                                $structuredContext .= "  - RED FLAGS: " . implode('; ', array_slice($redFlags, 0, 3)) . "\n";
                            }
                        }
                        if ($pa['thread_summary']) {
                            $structuredContext .= "  - Summary: " . substr($pa['thread_summary'], 0, 150) . "\n";
                        }
                        $structuredContext .= "\n";
                    }
                    $structuredContext .= "=== END PREVIOUS ANALYSIS ===\n";
                }

                // Recent emails context
                $recentContext = "\n\nRECENT EMAILS (Last 20):\n";
                foreach ($recentEmails as $email) {
                    $dir = $email['direction'] === 'inbound' ? 'FROM THEM' : 'FROM US';
                    $recentContext .= "\n{$dir} - {$email['date']}\n";
                    $recentContext .= "Subject: {$email['subject']}\n";
                    $recentContext .= substr($email['body_text'], 0, 500) . "...\n";
                }

                // Historical thread summaries
                $historyContext = "\n\nHISTORICAL THREAD SUMMARIES:\n";
                foreach ($threadSummaries as $thread) {
                    $historyContext .= "\n[{$thread['last_email_date']}] {$thread['subject']} ({$thread['email_count']} emails)\n";
                    if ($thread['summary']) {
                        $historyContext .= "Summary: " . substr($thread['summary'], 0, 200) . "...\n";
                    }
                }

                // Relevant emails from vector search
                $vectorContext = "";
                if (!empty($relevantEmails)) {
                    $vectorContext = "\n\nMOST RELEVANT EMAILS TO YOUR QUESTION:\n";
                    foreach ($relevantEmails as $email) {
                        $relevance = round($email['relevance_score'] * 100);
                        $vectorContext .= "\n[Relevance: {$relevance}%] {$email['date']} - {$email['subject']}\n";
                        $vectorContext .= substr($email['body_text'], 0, 600) . "...\n";
                    }
                }

                // Helper to sanitize text for AI (remove invalid UTF-8, null bytes, etc.)
                $sanitizeForAI = function($text) {
                    if (!is_string($text)) return '';
                    // Remove null bytes and other control characters (except newlines/tabs)
                    $text = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $text);
                    // Ensure valid UTF-8
                    $text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');
                    return $text;
                };

                // Sanitize all context parts that might contain email content
                $structuredContext = $sanitizeForAI($structuredContext);
                $currentThreadContext = $sanitizeForAI($currentThreadContext);
                $recentContext = $sanitizeForAI($recentContext);
                $historyContext = $sanitizeForAI($historyContext);
                $vectorContext = $sanitizeForAI($vectorContext);
                $question = $sanitizeForAI($question);
                $conversationHistory = $sanitizeForAI($conversationHistory);
                $savedContext = $sanitizeForAI($savedContext);

                // Final prompt
                $prompt = <<<PROMPT
You are an expert business analyst helping evaluate a client relationship. You have access to comprehensive data about this contact.

{$structuredContext}
{$currentThreadContext}
{$recentContext}
{$historyContext}
{$vectorContext}
{$savedContext}
{$conversationHistory}
USER QUESTION: {$question}

Provide a detailed, actionable answer based on the data above.

CRITICAL INSTRUCTIONS:
1. FOCUS ON THE CURRENT THREAD/SUBJECT if one is provided above.
2. IGNORE unrelated people or contexts from history unless they are directly relevant to the user's question.
3. If the user asks about a specific person (e.g., "Caroline"), focus ONLY on that person and their interactions. Do not conflate with other people (e.g., "Etienne") unless they are working together on this specific topic.
4. Provide specific evidence from the emails.

Structure your answer:
1. Direct Answer
2. Evidence from Current Thread (if applicable)
3. Relevant History/Patterns (only if related to this subject/person)
4. Recommendations
PROMPT;

                // Call AI using the selected provider
                $aiManager = new \AIApiOpus\AIManager();

                // Split prompt into system prompt and user content
                $systemPrompt = "You are an expert business analyst helping evaluate a client relationship. Provide detailed, actionable answers based on the data provided.";

                $aiResult = $aiManager->queryProvider($provider, $prompt, $model, $systemPrompt);

                if (!$aiResult['success']) {
                    // Fallback to Claude if selected provider fails
                    if ($provider !== 'claude') {
                        $aiResult = $aiManager->queryProvider('claude', $prompt, 'claude-sonnet-4-5-20250929', $systemPrompt);
                    }

                    if (!$aiResult['success']) {
                        throw new Exception($aiResult['error'] ?? 'AI provider failed');
                    }
                }

                $response = $aiResult['response'] ?? $aiResult['content'] ?? 'No response from AI';

                // Log the task
                logAITask('ask_contact_ai', [
                    'contact_id' => $contactId,
                    'contact_name' => $contact['name'],
                    'question' => $question,
                    'provider' => $provider,
                    'model' => $model,
                    'context_layers' => [
                        'profile' => !empty($profile),
                        'quotes' => count($quotes),
                        'recent_emails' => count($recentEmails),
                        'thread_summaries' => count($threadSummaries),
                        'vector_search_results' => count($relevantEmails),
                        'behaviors' => count($behaviors),
                        'alerts' => count($alerts)
                    ]
                ], $prompt, $response);

                jsonResponse([
                    'success' => true,
                    'answer' => $response,
                    'provider' => $provider,
                    'model' => $model,
                    'context_summary' => [
                        'total_emails' => $contact['total_emails'],
                        'quotes_analyzed' => count($quotes),
                        'recent_emails_included' => count($recentEmails),
                        'threads_summarized' => count($threadSummaries),
                        'relevant_emails_found' => count($relevantEmails),
                        'behaviors_detected' => count($behaviors),
                        'active_alerts' => count($alerts)
                    ]
                ]);

            } catch (Exception $e) {
                jsonError('Contact AI analysis failed: ' . $e->getMessage());
            }
            break;

        // ==========================================
        // Database Status - MySQL and MongoDB
        // ==========================================
        case 'db_status':
            $result = $db->testConnection();
            $result['dual_write_active'] = $db->isDualWriteActive();
            jsonResponse($result);
            break;

        // ==========================================
        // Thread ID Cache Status and Operations
        // ==========================================
        case 'thread_id_cache':
            $operation = $_GET['op'] ?? 'stats';
            $cache = ThreadIdCache::getInstance();

            switch ($operation) {
                case 'stats':
                    jsonResponse([
                        'success' => true,
                        'cache' => $cache->getStats()
                    ]);
                    break;

                case 'lookup':
                    $urlId = $_GET['url_id'] ?? '';
                    if (!$urlId) jsonError('url_id required');

                    $resolved = $cache->get($urlId);
                    jsonResponse([
                        'success' => true,
                        'url_id' => $urlId,
                        'resolved_id' => $resolved,
                        'is_cached' => $resolved !== null
                    ]);
                    break;

                case 'list':
                    jsonResponse([
                        'success' => true,
                        'mappings' => $cache->getAllMappings()
                    ]);
                    break;

                case 'clear_expired':
                    $cleared = $cache->clearExpired();
                    jsonResponse([
                        'success' => true,
                        'cleared_count' => $cleared
                    ]);
                    break;

                default:
                    jsonError('Unknown operation: ' . $operation . '. Valid: stats, lookup, list, clear_expired');
            }
            break;

        // ==========================================
        // Quote Assistant - Natural Language Queries
        // ==========================================
        case 'quote_assistant':
            $query = $_GET['query'] ?? $_POST['query'] ?? '';
            if (!$query) {
                jsonError('Query parameter required');
            }

            $contactId = isset($_GET['contact_id']) ? (int)$_GET['contact_id'] : null;

            require_once dirname(__DIR__) . '/src/QuoteAssistant.php';
            $assistant = new \EmailIntelligence\QuoteAssistant();

            $result = $assistant->ask($query, [
                'contact_id' => $contactId
            ]);

            jsonResponse([
                'success' => true,
                'result' => $result
            ]);
            break;

        // ==========================================
        // Quote Stats - Quick statistics
        // ==========================================
        case 'quote_stats':
            $contactId = isset($_GET['contact_id']) ? (int)$_GET['contact_id'] : null;

            require_once dirname(__DIR__) . '/src/QuoteAssistant.php';
            $assistant = new \EmailIntelligence\QuoteAssistant();

            $stats = $assistant->getQuickStats($contactId);

            jsonResponse([
                'success' => true,
                'stats' => $stats
            ]);
            break;

        // ==========================================
        // Quote Suggestion - AI-powered quote recommendation
        // ==========================================
        case 'suggest_quote':
            $description = $_GET['description'] ?? $_POST['description'] ?? '';
            if (!$description) {
                jsonError('Project description required');
            }

            $contactId = isset($_GET['contact_id']) ? (int)$_GET['contact_id'] : null;

            require_once dirname(__DIR__) . '/src/QuoteAssistant.php';
            $assistant = new \EmailIntelligence\QuoteAssistant();

            $suggestion = $assistant->suggestQuote($description, $contactId);

            jsonResponse([
                'success' => true,
                'suggestion' => $suggestion
            ]);
            break;

        // ==========================================
        // Quote Extraction - Trigger extraction for contact
        // ==========================================
        case 'extract_quotes':
            $contactId = isset($_GET['contact_id']) ? (int)$_GET['contact_id'] : null;
            $reprocess = isset($_GET['reprocess']) && $_GET['reprocess'] === 'true';

            require_once dirname(__DIR__) . '/src/QuoteExtractor.php';
            $extractor = new \EmailIntelligence\QuoteExtractor();

            if ($contactId) {
                $result = $extractor->extractForContact($contactId, $reprocess);
            } else {
                $limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 10;
                $result = $extractor->extractAll($limit);
            }

            jsonResponse([
                'success' => true,
                'result' => $result
            ]);
            break;

        // ==========================================
        // Extraction Stats - Show extraction progress
        // ==========================================
        case 'extraction_stats':
            require_once dirname(__DIR__) . '/src/QuoteExtractor.php';
            $extractor = new \EmailIntelligence\QuoteExtractor();

            $stats = $extractor->getStats();

            jsonResponse([
                'success' => true,
                'stats' => $stats
            ]);
            break;

        // ==========================================
        // List Quotes - Get quotes with filtering
        // ==========================================
        case 'list_quotes':
            $contactId = isset($_GET['contact_id']) ? (int)$_GET['contact_id'] : null;
            $quoteType = $_GET['type'] ?? null;
            $limit = isset($_GET['limit']) ? min((int)$_GET['limit'], 500) : 50;
            $offset = isset($_GET['offset']) ? (int)$_GET['offset'] : 0;
            $sortBy = $_GET['sort'] ?? 'quote_date';
            $sortOrder = ($_GET['order'] ?? 'desc') === 'asc' ? 1 : -1;

            $mongo = \EmailIntelligence\MongoDatabase::getInstance();

            $filter = [];
            if ($contactId) $filter['contact_id'] = $contactId;
            if ($quoteType) $filter['quote_type'] = $quoteType;

            // Get total count
            $total = $mongo->count('quotes', $filter);

            // Get quotes
            $quotes = $mongo->findAll('quotes', $filter, [
                'sort' => [$sortBy => $sortOrder],
                'limit' => $limit,
                'skip' => $offset
            ]);

            // Enrich with contact names
            foreach ($quotes as &$quote) {
                if (!empty($quote['contact_id'])) {
                    $contact = $mongo->findOne('contacts', ['id' => $quote['contact_id']]);
                    $quote['contact_name'] = $contact['name'] ?? 'Unknown';
                    $quote['contact_company'] = $contact['company'] ?? '';
                }
            }

            jsonResponse([
                'success' => true,
                'quotes' => $quotes,
                'total' => $total,
                'limit' => $limit,
                'offset' => $offset
            ]);
            break;

        // ==========================================
        // Projects - CRUD for projects
        // ==========================================
        case 'projects':
            $contactId = isset($_GET['contact_id']) ? (int)$_GET['contact_id'] : null;
            $status = $_GET['status'] ?? null;
            $limit = isset($_GET['limit']) ? min((int)$_GET['limit'], 100) : 50;

            $mongo = \EmailIntelligence\MongoDatabase::getInstance();

            $filter = [];
            if ($contactId) $filter['contact_id'] = $contactId;
            if ($status) $filter['status'] = $status;

            $projects = $mongo->findAll('projects', $filter, [
                'sort' => ['created_at' => -1],
                'limit' => $limit
            ]);

            // Enrich with contact names
            foreach ($projects as &$project) {
                if (!empty($project['contact_id'])) {
                    $contact = $mongo->findOne('contacts', ['id' => $project['contact_id']]);
                    $project['contact_name'] = $contact['name'] ?? 'Unknown';
                    $project['contact_company'] = $contact['company'] ?? '';
                }
            }

            jsonResponse([
                'success' => true,
                'projects' => $projects
            ]);
            break;

        // ==========================================
        // Add Project
        // ==========================================
        case 'add_project':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST method required', 405);
            }

            $input = json_decode(file_get_contents('php://input'), true);
            if (!$input) {
                $input = $_POST;
            }

            $required = ['contact_id', 'name'];
            foreach ($required as $field) {
                if (empty($input[$field])) {
                    jsonError("Field '{$field}' is required");
                }
            }

            $mongo = \EmailIntelligence\MongoDatabase::getInstance();

            $project = [
                'contact_id' => (int)$input['contact_id'],
                'quote_ids' => $input['quote_ids'] ?? [],
                'name' => $input['name'],
                'description' => $input['description'] ?? '',
                'quoted_amount' => isset($input['quoted_amount']) ? (float)$input['quoted_amount'] : null,
                'final_amount' => isset($input['final_amount']) ? (float)$input['final_amount'] : null,
                'currency' => $input['currency'] ?? 'USD',
                'hours_estimated' => isset($input['hours_estimated']) ? (int)$input['hours_estimated'] : null,
                'hours_actual' => isset($input['hours_actual']) ? (int)$input['hours_actual'] : null,
                'hourly_rate' => isset($input['hourly_rate']) ? (float)$input['hourly_rate'] : null,
                'status' => $input['status'] ?? 'proposed',
                'started_at' => $input['started_at'] ?? null,
                'completed_at' => $input['completed_at'] ?? null,
                'deadline' => $input['deadline'] ?? null,
                'tags' => $input['tags'] ?? [],
                'notes' => $input['notes'] ?? '',
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s')
            ];

            $projectId = $mongo->insert('projects', $project);

            jsonResponse([
                'success' => true,
                'project_id' => $projectId,
                'project' => $project
            ]);
            break;

        // ==========================================
        // Update Project
        // ==========================================
        case 'update_project':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST method required', 405);
            }

            $input = json_decode(file_get_contents('php://input'), true);
            if (!$input) {
                $input = $_POST;
            }

            if (empty($input['project_id'])) {
                jsonError("Field 'project_id' is required");
            }

            $mongo = \EmailIntelligence\MongoDatabase::getInstance();

            // Build update data (only provided fields)
            $updateData = ['updated_at' => date('Y-m-d H:i:s')];
            $allowedFields = [
                'name', 'description', 'quoted_amount', 'final_amount', 'currency',
                'hours_estimated', 'hours_actual', 'hourly_rate', 'status',
                'started_at', 'completed_at', 'deadline', 'tags', 'notes',
                'lessons_learned', 'profitability'
            ];

            foreach ($allowedFields as $field) {
                if (isset($input[$field])) {
                    $updateData[$field] = $input[$field];
                }
            }

            $mongo->update('projects', ['_id' => new \MongoDB\BSON\ObjectId($input['project_id'])], $updateData);

            jsonResponse([
                'success' => true,
                'updated' => $updateData
            ]);
            break;

        // ==========================================
        // Analyze Contact Full - With Custom Context
        // ==========================================
        case 'analyze_contact_full':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                //jsonError('POST required', 405);
            }

            $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
            if (strpos($contentType, 'application/json') !== false) {
                $jsonData = json_decode(file_get_contents('php://input'), true);
                $contactId = (int)($jsonData['contact_id'] ?? 0);
                $customContext = trim($jsonData['context'] ?? '');
                $includeRaw = (bool)($jsonData['include_raw'] ?? true);
            } else {
                $contactId = (int)($$_REQUEST['contact_id'] ?? 0);
                $customContext = trim($_POST['context'] ?? '');
                $includeRaw = (bool)($_POST['include_raw'] ?? true);
            }

            if (!$contactId) jsonError('Contact ID required');

            $analyzer = new ContactAnalyzer();

            if (!$analyzer->isConfigured()) {
                jsonError('Claude API not configured. Please set CLAUDE_API_KEY in config.php');
            }

            try {
                // Pass custom context to analyzer
                $analysis = $analyzer->analyzeContact($contactId, $includeRaw, $customContext);
                $stats = $analyzer->getStats();

                jsonResponse([
                    'success' => true,
                    'contact_id' => $contactId,
                    'analysis' => $analysis,
                    'stats' => $stats,
                    'message' => "Analyzed {$stats['threads_analyzed']} threads ({$stats['emails_processed']} emails) using {$stats['tokens_used']} tokens"
                ]);
            } catch (Exception $e) {
                jsonError('Contact analysis failed: ' . $e->getMessage());
            }
            break;

        // ==========================================
        // Analyze Contact Threads - Analyze all pending threads
        // ==========================================
        case 'analyze_contact_threads':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                //jsonError('POST required', 405);
            }

            $contactId = (int)($_REQUEST['contact_id'] ?? 0);
            //$contactId = (int)($_POST['contact_id'] ?? 0);
            $batchSize = (int)($_POST['batch_size'] ?? 5); // Default 5 threads per batch
            if (!$contactId) jsonError('Contact ID required');

            // Check for stop flag before starting
            $stopFile = dirname(__DIR__) . '/logs/analysis_stop_' . $contactId . '.flag';
            if (file_exists($stopFile)) {
                // Clear the stop flag
                @unlink($stopFile);
                jsonResponse([
                    'success' => true,
                    'stopped' => true,
                    'message' => 'Analysis stopped by user request'
                ]);
            }

            $analyzer = new ThreadAnalyzer();

            if (!$analyzer->isConfigured()) {
                jsonError('Claude API not configured. Please set CLAUDE_API_KEY in config.php');
            }

            // Use MongoDB to get pending threads (same as dashboard)
            $mongo = \EmailIntelligence\MongoDatabase::getInstance();

            // Get all pending threads for this contact from MongoDB
            $allThreads = $mongo->findAll('thread_analysis', ['contact_id' => $contactId], [
                'sort' => ['last_email_date' => -1]
            ]);

            // Filter to only pending threads (is_analyzed = 0 or needs_reanalysis = 1)
            $pendingThreads = array_values(array_filter($allThreads, function($t) {
                return ($t['is_analyzed'] ?? 0) == 0 || ($t['needs_reanalysis'] ?? 0) == 1;
            }));

            $stats = [
                'analyzed' => 0,
                'failed' => 0,
                'skipped' => 0,
                'total_pending' => count($pendingThreads)
            ];

            // Limit batch size to avoid timeout (max 10)
            $batchSize = min($batchSize, 10);
            $threadsToAnalyze = array_slice($pendingThreads, 0, $batchSize);
            //print_r($threadsToAnalyze);
            //exit();
            foreach ($threadsToAnalyze as $thread) {
                // Check stop flag before each thread
                if (file_exists($stopFile)) {
                    @unlink($stopFile);
                    $remaining = count($pendingThreads) - $stats['analyzed'] - $stats['failed'] - $stats['skipped'];
                    jsonResponse([
                        'success' => true,
                        'stopped' => true,
                        'contact_id' => $contactId,
                        'stats' => $stats,
                        'remaining' => $remaining,
                        'message' => 'Analysis stopped by user. ' . $stats['analyzed'] . ' threads completed before stop.'
                    ]);
                }

                try {
                    $threadId = $thread['thread_id'] ?? '';
                    if (empty($threadId)) {
                        $stats['skipped']++;
                        continue;
                    }

                    $result = $analyzer->analyzeThread($threadId);
                    if ($result) {
                        $stats['analyzed']++;
                    } else {
                        $stats['skipped']++;
                    }
                } catch (Exception $e) {
                    $stats['failed']++;
                    error_log("Thread analysis failed for " . ($thread['thread_id'] ?? 'unknown') . ": " . $e->getMessage());
                }
            }

            $remaining = count($pendingThreads) - count($threadsToAnalyze);

            jsonResponse([
                'success' => true,
                'contact_id' => $contactId,
                'stats' => $stats,
                'remaining' => $remaining,
                'batch_processed' => count($threadsToAnalyze),
                'message' => "Analyzed {$stats['analyzed']} threads, {$stats['failed']} failed, {$stats['skipped']} skipped" .
                             ($remaining > 0 ? ". {$remaining} threads remaining." : " All done!")
            ]);
            break;

        // ==========================================
        // Stop Analysis - Set stop flag for batch analysis
        // ==========================================
        case 'stop_analysis':
            $contactId = (int)($_REQUEST['contact_id'] ?? 0);
            if (!$contactId) jsonError('Contact ID required');

            $stopFile = dirname(__DIR__) . '/logs/analysis_stop_' . $contactId . '.flag';
            file_put_contents($stopFile, date('Y-m-d H:i:s') . ' - Stop requested');

            jsonResponse([
                'success' => true,
                'message' => 'Stop signal sent. Analysis will stop after current thread completes.'
            ]);
            break;

        // ==========================================
        // Check Analysis Status - Check if analysis is running/stopped
        // ==========================================
        case 'analysis_status':
            $contactId = (int)($_REQUEST['contact_id'] ?? 0);
            if (!$contactId) jsonError('Contact ID required');

            $stopFile = dirname(__DIR__) . '/logs/analysis_stop_' . $contactId . '.flag';

            jsonResponse([
                'success' => true,
                'contact_id' => $contactId,
                'stop_requested' => file_exists($stopFile)
            ]);
            break;

        // ==========================================
        // Generate Email - AI Email Composer (with ChromaDB context)
        // ==========================================
        case 'generate_email':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                //jsonError('POST required', 405);
            }

            $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
            if (strpos($contentType, 'application/json') !== false) {
                $jsonData = json_decode(file_get_contents('php://input'), true);
                $contactId = (int)($jsonData['contact_id'] ?? 0);
                $emailType = $jsonData['email_type'] ?? 'winback';
                $additionalContext = trim($jsonData['additional_context'] ?? '');
                $aiModel = $jsonData['ai_model'] ?? 'openai';
            } else {
                $contactId = (int)($_POST['contact_id'] ?? 0);
                $emailType = $_POST['email_type'] ?? 'winback';
                $additionalContext = trim($_POST['additional_context'] ?? '');
                $aiModel = $_POST['ai_model'] ?? 'openai';
            }

            if (!$contactId) jsonError('Contact ID required');

            // Get contact info from MongoDB
            $mongo = \EmailIntelligence\MongoDatabase::getInstance();
            $contact = $mongo->findOne('contacts', ['id' => $contactId]);
            if (!$contact) jsonError('Contact not found', 404);

            // Get profile from MongoDB
            $profile = $mongo->findOne('contact_profiles', ['contact_id' => $contactId]);

            // Get recent emails from MongoDB
            $recentEmails = $mongo->findAll('emails', ['contact_id' => $contactId], [
                'sort' => ['date' => -1],
                'limit' => 10
            ]);

            // Build email context from MongoDB emails
            $emailContext = [];
            foreach ($recentEmails as $email) {
                $dir = ($email['direction'] ?? '') === 'inbound' ? 'FROM THEM' : 'FROM US';
                $body = $email['body_text'] ?? $email['snippet'] ?? '';
                $emailContext[] = "{$dir} ({$email['date']}): {$email['subject']}\n" . substr($body, 0, 300);
            }

            // ========================================
            // ChromaDB: Get semantically relevant context
            // ========================================
            $vectorContext = '';
            $patternsContext = '';
            $similarEmailsContext = '';
            $quotesContext = '';

            // First, get quotes from MongoDB for this contact
            $quotes = $mongo->findAll('quotes', ['contact_id' => $contactId], [
                'sort' => ['quote_date' => -1],
                'limit' => 20
            ]);
            if (!empty($quotes)) {
                $quotesContext = "\n\nPREVIOUS QUOTES PROVIDED TO THIS CLIENT Please note there might be duplicate records or revisions of the same quote, identify only the last one and thake only that into account:\n";
                foreach ($quotes as $quote) {
                    $amount = isset($quote['amount']) ? '$' . number_format($quote['amount'], 2) : 'N/A';
                    $date = $quote['quote_date'] ?? $quote['date'] ?? 'N/A';
                    $desc = $quote['description'] ?? $quote['subject'] ?? 'Quote';
                    $status = $quote['status'] ?? '';
                    $quotesContext .= "- {$date}: {$desc} - {$amount}" . ($status ? " ({$status})" : "") . "\n";
                }
            }

            try {
                $vectorMemory = new \EmailIntelligence\VectorMemory();

                if ($vectorMemory->isHealthy()) {
                    // Build a search query that combines email type context with user's additional instructions
                    $baseQueries = [
                        'winback' => "client disengagement, last interactions, what they valued",
                        'followup' => "pending items, open questions, commitments",
                        'proposal' => "quotes, pricing, services, project scope, form changes",
                        'checkin' => "relationship highlights, positive interactions",
                        'custom' => ""
                    ];

                    // IMPORTANT: Always include the user's additional context in the search
                    $baseQuery = $baseQueries[$emailType] ?? '';
                    $searchQuery = $additionalContext
                        ? trim($additionalContext . ' ' . $baseQuery)
                        : ($baseQuery ?: "recent important discussions");

                    // Limit search query length
                    $searchQuery = substr($searchQuery, 0, 500);

                    // Get contact name to help with filtering later
                    $contactName = $contact['name'] ?? '';
                    $contactEmail = $contact['email'] ?? '';

                    // 1. Search emails collection for relevant content
                    // Strategy: Search with contact-specific terms to boost relevance
                    $contactSearchQuery = trim($searchQuery . ' ' . $contactName);

                    // Fetch more results to increase chance of finding contact's emails
                    $allRelevantEmails = $vectorMemory->search($contactSearchQuery, 100, [
                        'collection' => \EmailIntelligence\VectorMemory::COLLECTION_EMAILS
                    ]);

                    // Filter to this contact's emails only (by contact_id or email address)
                    $relevantEmails = array_filter($allRelevantEmails, function($r) use ($contactId, $contactEmail) {
                        $metaContactId = $r['metadata']['contact_id'] ?? null;
                        $metaContactEmail = $r['metadata']['contact_email'] ?? '';
                        return ($metaContactId == $contactId) ||
                               (stripos($metaContactEmail, $contactEmail) !== false);
                    });
                    $relevantEmails = array_slice($relevantEmails, 0, 5);

                    if (!empty($relevantEmails)) {
                        $vectorContext = "\n\nRELEVANT PAST EMAILS WITH THIS CLIENT:\n";
                        foreach ($relevantEmails as $i => $result) {
                            $sim = isset($result['similarity']) ? " (relevance: " . round($result['similarity'] * 100) . "%)" : "";
                            $vectorContext .= ($i + 1) . ". " . substr($result['content'] ?? '', 0, 500) . $sim . "\n\n";
                        }
                    }

                    // 2. Search thread summaries if user provided context about specific topics
                    if ($additionalContext) {
                        $threadSearchQuery = trim($additionalContext . ' ' . $contactName);
                        $allThreads = $vectorMemory->search($threadSearchQuery, 50, [
                            'collection' => \EmailIntelligence\VectorMemory::COLLECTION_THREADS
                        ]);

                        // Filter to this contact
                        $relevantThreads = array_filter($allThreads, function($r) use ($contactId, $contactEmail) {
                            $metaContactId = $r['metadata']['contact_id'] ?? null;
                            $metaContactEmail = $r['metadata']['contact_email'] ?? '';
                            return ($metaContactId == $contactId) ||
                                   (stripos($metaContactEmail, $contactEmail) !== false);
                        });
                        $relevantThreads = array_slice($relevantThreads, 0, 3);

                        if (!empty($relevantThreads)) {
                            $vectorContext .= "\n\nRELATED CONVERSATION SUMMARIES:\n";
                            foreach ($relevantThreads as $i => $result) {
                                $vectorContext .= "- " . substr($result['content'] ?? '', 0, 400) . "\n";
                            }
                        }
                    }

                    // 3. Get detected patterns for this contact
                    $patternSearchQuery = "patterns behaviors " . $contactName;
                    $allPatterns = $vectorMemory->search($patternSearchQuery, 50, [
                        'collection' => \EmailIntelligence\VectorMemory::COLLECTION_PATTERNS
                    ]);

                    // Filter to this contact
                    $patterns = array_filter($allPatterns, function($r) use ($contactId, $contactEmail) {
                        $metaContactId = $r['metadata']['contact_id'] ?? null;
                        $metaContactEmail = $r['metadata']['contact_email'] ?? '';
                        return ($metaContactId == $contactId) ||
                               (stripos($metaContactEmail, $contactEmail) !== false);
                    });
                    $patterns = array_slice($patterns, 0, 5);

                    if (!empty($patterns)) {
                        $patternsContext = "\n\nDETECTED BEHAVIORAL PATTERNS (use to inform your approach):\n";
                        foreach ($patterns as $pattern) {
                            $patternsContext .= "- " . substr($pattern['content'] ?? '', 0, 200) . "\n";
                        }
                    }

                    // 4. Get similar emails from OTHER contacts for reference (for proposal/pricing context)
                    if (in_array($emailType, ['winback', 'proposal']) && $additionalContext) {
                        $similarEmails = $vectorMemory->search(
                            $additionalContext,
                            5,
                            ['collection' => \EmailIntelligence\VectorMemory::COLLECTION_EMAILS]
                        );

                        // Filter OUT this contact to get examples from others
                        $otherContactEmails = array_filter($similarEmails, function($r) use ($contactId) {
                            return ($r['metadata']['contact_id'] ?? null) != $contactId;
                        });
                        $otherContactEmails = array_slice($otherContactEmails, 0, 2);

                        if (!empty($otherContactEmails)) {
                            $similarEmailsContext = "\n\nSIMILAR PAST COMMUNICATIONS (from other clients, for reference):\n";
                            foreach ($otherContactEmails as $email) {
                                $similarEmailsContext .= "- " . substr($email['content'] ?? '', 0, 300) . "\n";
                            }
                        }
                    }
                }
            } catch (Exception $e) {
                // ChromaDB not available - continue without vector context
                error_log("ChromaDB context fetch failed: " . $e->getMessage());
            }

            // Email type prompts
            $typePrompts = [
                'winback' => "Write a compelling win-back email to re-engage this lost or dormant client. Reference our past positive experiences, address any potential concerns they might have based on the behavioral patterns detected, and offer compelling reasons to reconnect. Be genuine and not salesy.",
                'followup' => "Write a professional follow-up email. Be concise and focused on moving the conversation forward. Reference any pending items from the insights.",
                'proposal' => "Write an email introducing a new service or proposal that would benefit this client based on their history, needs, and the insights gathered from our past interactions.",
                'checkin' => "Write a casual check-in email to maintain the relationship. Be warm and genuine, not overly formal. Reference positive aspects of our relationship history.",
                'custom' => "Write a professional email based on the additional context provided."
            ];

            $typePrompt = $typePrompts[$emailType] ?? $typePrompts['custom'];

// Dan 12182025 try to disable and check if we have better content
$similarEmailsContext = "";

            // Build AI prompt with ChromaDB context
            $contextStr = implode("\n\n", $emailContext);
            $profileStr = '';
            if ($profile) {
                $profileStr = "
Client Profile:
- Honesty Score: " . ($profile['honesty_score'] ?? 'N/A') . "%
- Reliability Score: " . ($profile['reliability_score'] ?? 'N/A') . "%
- Communication Style: " . ($profile['communication_style'] ?? 'Unknown') . "
- Psychological Profile: " . substr($profile['psychological_profile'] ?? '', 0, 300) . "
- Best Approach: " . implode(', ', $profile['best_approach'] ?? []);
            }

            $additionalStr = $additionalContext ? "\n\nAdditional Instructions: {$additionalContext}" : "";

            $prompt = <<<PROMPT
You are an expert email copywriter and business analyst helping a business owner communicate with clients.

CLIENT INFO:
Name: {$contact['name']}
Email: {$contact['email']}
Company: {$contact['company']}
{$profileStr}
{$quotesContext}

RECENT EMAIL HISTORY:
{$contextStr}
{$vectorContext}
{$patternsContext}
{$similarEmailsContext}

TASK:
{$typePrompt}
{$additionalStr}

IMPORTANT: If the user asks about previous quotes, pricing, or historical information, use the data provided above to answer accurately. If they ask for a quote estimate, base it on the previous pricing history and adjust appropriately.

Generate a personalized email with:
1. An attention-grabbing subject line
2. A warm, personalized opening that shows you remember the relationship
3. Clear value proposition or reason for reaching out (informed by the insights above)
4. If pricing/quotes are requested, include specific amounts based on the historical data
5. Specific call-to-action
6. Professional closing

Use the behavioral patterns and insights above to craft a more effective message. For example:
- If patterns show pricing sensitivity, emphasize value
- If patterns show preference for quick responses, keep it concise
- If there were past issues, acknowledge and address them
- If previous quotes are available, reference them when discussing pricing

Respond with JSON in this exact format:
{
    "subject": "Email subject line here",
    "body": "Full email body here with proper formatting and line breaks"
}
PROMPT;

            // Determine provider from model name
            // Claude models contain 'claude' in the name, OpenAI models don't
            $provider = (strpos($aiModel, 'claude') !== false) ? 'claude' : 'openai';
            $analyzer = new ThreadAnalyzer($provider, $aiModel);

            if (!$analyzer->isConfigured()) {
                jsonError('AI API not configured. Please set API keys in config.php');
            }

            try {
                $response = $analyzer->askClaude($prompt, $contactId); // Use contactId as threadId for logging if no threadId

                // Extract JSON from response
                $jsonMatch = preg_match('/\{[\s\S]*"subject"[\s\S]*"body"[\s\S]*\}/', $response, $matches);
                if ($jsonMatch) {
                    $parsed = json_decode($matches[0], true);
                } else {
                    $parsed = json_decode($response, true);
                }

                if (!$parsed || !isset($parsed['subject']) || !isset($parsed['body'])) {
                    jsonError('Failed to parse AI response');
                }

                // Log the AI task for audit/debugging
                $requestData = [
                    'contact_id' => $contactId,
                    'contact_name' => $contact['name'] ?? 'Unknown',
                    'contact_email' => $contact['email'] ?? 'Unknown',
                    'email_type' => $emailType,
                    'additional_context' => $additionalContext,
                    'ai_model' => $aiModel,
                    'ai_provider' => $provider,
                    'context_sources' => [
                        'recent_emails_count' => count($recentEmails ?? []),
                        'quotes_count' => count($quotes ?? []),
                        'used_vector_context' => !empty($vectorContext),
                        'used_patterns' => !empty($patternsContext)
                    ]
                ];

                $responseData = [
                    'subject' => $parsed['subject'],
                    'body' => $parsed['body'],
                    'email_type' => $emailType,
                    'contact_id' => $contactId,
                    'used_vector_context' => !empty($vectorContext),
                    'used_patterns' => !empty($patternsContext),
                    'used_quotes' => !empty($quotesContext),
                    'quotes_count' => count($quotes ?? [])
                ];

                logAITask('generate_email', $requestData, $prompt, json_encode($responseData, JSON_PRETTY_PRINT));

                jsonResponse($responseData);
            } catch (Exception $e) {
                jsonError('Email generation failed: ' . $e->getMessage());
            }
            break;

        // ==========================================
        // Count Filter Emails - Preview filter impact
        // ==========================================
        case 'count_filter_emails':
            $domain = trim($_GET['domain'] ?? '');
            if (empty($domain)) {
                jsonError('Domain or email pattern required');
            }

            // Clean up domain - handle both @domain.com and domain.com formats
            $pattern = $domain;
            if (strpos($domain, '@') !== 0 && strpos($domain, '@') === false) {
                $pattern = '@' . $domain;
            }

            // Count emails matching this pattern
            $emailCount = $db->fetchColumn(
                "SELECT COUNT(*) FROM emails WHERE from_email LIKE ? OR to_email LIKE ?",
                ['%' . $pattern . '%', '%' . $pattern . '%']
            );

            // Count unique threads
            $threadCount = $db->fetchColumn(
                "SELECT COUNT(DISTINCT thread_id) FROM emails WHERE from_email LIKE ? OR to_email LIKE ?",
                ['%' . $pattern . '%', '%' . $pattern . '%']
            );

            // Count contacts
            $contactCount = $db->fetchColumn(
                "SELECT COUNT(*) FROM contacts WHERE email LIKE ?",
                ['%' . $pattern . '%']
            );

            // Sample contacts
            $sampleContacts = $db->fetchAll(
                "SELECT id, name, email, company FROM contacts WHERE email LIKE ? LIMIT 5",
                ['%' . $pattern . '%']
            );

            jsonResponse([
                'success' => true,
                'domain' => $domain,
                'pattern' => $pattern,
                'email_count' => (int)$emailCount,
                'thread_count' => (int)$threadCount,
                'contact_count' => (int)$contactCount,
                'sample_contacts' => $sampleContacts
            ]);
            break;

        // ==========================================
        // Data Enrichment - Contact & Company Enrichment
        // ==========================================
        case 'enrich_contact':
            require_once dirname(__DIR__) . '/src/DataEnrichment.php';

            $contactId = $_GET['contact_id'] ?? $_POST['contact_id'] ?? '';
            $email = $_GET['email'] ?? $_POST['email'] ?? null;
            $sources = isset($_POST['sources']) ? json_decode($_POST['sources'], true) : null;

            if (!$contactId && !$email) {
                jsonError('contact_id or email required');
            }

            $enrichment = new DataEnrichment();

            // If only email provided, find or create contact
            if (!$contactId && $email) {
                $mongo = \EmailIntelligence\MongoDatabase::getInstance();
                $contact = $mongo->findOne('contacts', ['email' => $email]);
                if ($contact) {
                    $contactId = (string) ($contact['_id'] ?? $contact['id'] ?? '');
                } else {
                    // Allow enrichment even without existing contact
                    $contactId = '';
                }
            }

            $result = $enrichment->enrichContact($contactId, $email, $sources);
            jsonResponse($result);
            break;

        case 'enrich_company':
            require_once dirname(__DIR__) . '/src/DataEnrichment.php';

            $domain = $_GET['domain'] ?? $_POST['domain'] ?? '';
            $sources = isset($_POST['sources']) ? json_decode($_POST['sources'], true) : null;

            if (!$domain) {
                jsonError('domain required');
            }

            $enrichment = new DataEnrichment();
            $result = $enrichment->enrichCompany($domain, $sources);
            jsonResponse($result);
            break;

        case 'enrich_all_contacts':
            require_once dirname(__DIR__) . '/src/DataEnrichment.php';

            $priority = (int) ($_POST['priority'] ?? 3);

            $enrichment = new DataEnrichment();
            $result = $enrichment->queueAllContacts($priority);
            jsonResponse($result);
            break;

        case 'enrich_all_companies':
            require_once dirname(__DIR__) . '/src/DataEnrichment.php';

            $priority = (int) ($_POST['priority'] ?? 3);

            $enrichment = new DataEnrichment();
            $result = $enrichment->queueAllCompanies($priority);
            jsonResponse($result);
            break;

        case 'suggest_enrichment_sources':
            require_once dirname(__DIR__) . '/src/DataEnrichment.php';

            $entityType = $_GET['entity_type'] ?? $_POST['entity_type'] ?? '';
            $entityId = $_GET['entity_id'] ?? $_POST['entity_id'] ?? '';

            if (!$entityType || !$entityId) {
                jsonError('entity_type and entity_id required');
            }

            if (!in_array($entityType, ['contact', 'company'])) {
                jsonError('entity_type must be "contact" or "company"');
            }

            $enrichment = new DataEnrichment();
            $suggestions = $enrichment->suggestSources($entityType, $entityId);
            jsonResponse([
                'success' => true,
                'entity_type' => $entityType,
                'entity_id' => $entityId,
                'suggestions' => $suggestions
            ]);
            break;

        case 'approve_enrichment':
            require_once dirname(__DIR__) . '/src/DataEnrichment.php';

            $entityType = $_POST['entity_type'] ?? '';
            $entityId = $_POST['entity_id'] ?? '';
            $sources = isset($_POST['sources']) ? json_decode($_POST['sources'], true) : null;
            $priority = (int) ($_POST['priority'] ?? 5);

            if (!$entityType || !$entityId) {
                jsonError('entity_type and entity_id required');
            }

            $enrichment = new DataEnrichment();

            // Queue or immediately enrich based on config
            if (defined('AI_AUTO_ENRICH') && AI_AUTO_ENRICH) {
                if ($entityType === 'contact') {
                    $result = $enrichment->enrichContact($entityId, null, $sources);
                } else {
                    $result = $enrichment->enrichCompany($entityId, $sources);
                }
            } else {
                $result = $enrichment->queueEnrichment($entityType, $entityId, $sources, $priority);
            }

            jsonResponse($result);
            break;

        case 'enrichment_status':
            require_once dirname(__DIR__) . '/src/DataEnrichment.php';

            $entityType = $_GET['entity_type'] ?? '';
            $entityId = $_GET['entity_id'] ?? '';

            if (!$entityType || !$entityId) {
                jsonError('entity_type and entity_id required');
            }

            $enrichment = new DataEnrichment();
            $status = $enrichment->getEnrichmentStatus($entityType, $entityId);
            jsonResponse([
                'success' => true,
                'entity_type' => $entityType,
                'entity_id' => $entityId,
                'enrichment' => $status
            ]);
            break;

        case 'enrichment_queue':
            require_once dirname(__DIR__) . '/src/DataEnrichment.php';

            $enrichment = new DataEnrichment();
            $queueStatus = $enrichment->getQueueStatus();
            jsonResponse([
                'success' => true,
                'queue' => $queueStatus
            ]);
            break;

        case 'process_enrichment_queue':
            require_once dirname(__DIR__) . '/src/DataEnrichment.php';

            $batchSize = (int) ($_POST['batch_size'] ?? 10);

            $enrichment = new DataEnrichment();
            $results = $enrichment->processQueue($batchSize);
            jsonResponse([
                'success' => true,
                'results' => $results
            ]);
            break;

        case 'enrichment_logs':
            require_once dirname(__DIR__) . '/src/DataEnrichment.php';

            $entityType = $_GET['entity_type'] ?? null;
            $entityId = $_GET['entity_id'] ?? null;
            $limit = (int) ($_GET['limit'] ?? 50);

            $enrichment = new DataEnrichment();
            $logs = $enrichment->getLogs($entityType, $entityId, $limit);

            // Convert MongoDB objects to arrays for JSON
            $logsArray = array_map(function($log) {
                $arr = (array) $log;
                if (isset($arr['_id'])) $arr['_id'] = (string) $arr['_id'];
                if (isset($arr['created_at']) && $arr['created_at'] instanceof MongoDB\BSON\UTCDateTime) {
                    $arr['created_at'] = $arr['created_at']->toDateTime()->format('c');
                }
                return $arr;
            }, $logs);

            jsonResponse([
                'success' => true,
                'logs' => $logsArray
            ]);
            break;

        case 'contact_enrichment':
            require_once dirname(__DIR__) . '/src/DataEnrichment.php';

            $contactId = $_GET['contact_id'] ?? '';
            if (!$contactId) {
                jsonError('contact_id required');
            }

            $enrichment = new DataEnrichment();
            $data = $enrichment->getContactEnrichment($contactId);
            jsonResponse([
                'success' => true,
                'contact_id' => $contactId,
                'enrichment' => $data
            ]);
            break;

        case 'company_enrichment':
            require_once dirname(__DIR__) . '/src/DataEnrichment.php';

            $domain = $_GET['domain'] ?? '';
            if (!$domain) {
                jsonError('domain required');
            }

            $enrichment = new DataEnrichment();
            $data = $enrichment->getCompanyEnrichment($domain);
            jsonResponse([
                'success' => true,
                'domain' => $domain,
                'enrichment' => $data
            ]);
            break;

        case 'apollo_status':
            require_once dirname(__DIR__) . '/src/DataEnrichment.php';

            $enrichment = new DataEnrichment();
            $status = $enrichment->getApolloStatus();
            jsonResponse([
                'success' => true,
                'apollo' => $status
            ]);
            break;

        case 'puppeteer_status':
            require_once dirname(__DIR__) . '/src/DataEnrichment.php';

            $enrichment = new DataEnrichment();
            $status = $enrichment->getPuppeteerStatus();
            jsonResponse([
                'success' => true,
                'puppeteer' => $status
            ]);
            break;

        case 'enrichment_sources':
            // Return configured enrichment sources
            $sources = defined('ENRICHMENT_SOURCES') ? ENRICHMENT_SOURCES : [];
            jsonResponse([
                'success' => true,
                'sources' => $sources
            ]);
            break;

        // ==========================================
        // Important Context - Save
        // ==========================================
        case 'save_important_context':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $content = $_POST['content'] ?? '';
            if (!$content) jsonError('Content required');

            $ctx = new ImportantContext();
            $id = $ctx->save([
                'contact_id' => $_POST['contact_id'] ?? null,
                'thread_id' => $_POST['thread_id'] ?? null,
                'content' => $content,
                'source_type' => $_POST['source_type'] ?? 'manual',
                'description' => $_POST['description'] ?? null
            ]);

            jsonResponse(['success' => true, 'id' => $id]);
            break;

        // ==========================================
        // Important Context - Get
        // ==========================================
        case 'get_important_context':
            $contactId = isset($_GET['contact_id']) ? (int)$_GET['contact_id'] : null;
            $threadId = $_GET['thread_id'] ?? null;

            $ctx = new ImportantContext();
            $items = $ctx->get($contactId, $threadId);

            jsonResponse(['success' => true, 'items' => $items]);
            break;

        // ==========================================
        // Important Context - Delete
        // ==========================================
        case 'delete_important_context':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $id = $_POST['id'] ?? '';
            if (!$id) jsonError('ID required');

            $ctx = new ImportantContext();
            $success = $ctx->delete($id);

            jsonResponse(['success' => $success]);
            break;

        // ==========================================
        // Save AI Query Response (MongoDB)
        // ==========================================
        case 'save_ai_response':
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                jsonError('POST required', 405);
            }

            $mongo = \EmailIntelligence\MongoDatabase::getInstance();
            if (!$mongo->isAvailable()) {
                jsonError('MongoDB not available');
            }

            // Parse JSON input
            $jsonData = json_decode(file_get_contents('php://input'), true);
            if (!$jsonData) {
                jsonError('Invalid JSON data');
            }

            $threadIds = $jsonData['thread_ids'] ?? [];
            if (empty($threadIds)) {
                jsonError('thread_ids required');
            }

            // Auto-resolve URL thread IDs to hex IDs
            $threadIds = array_map('resolveThreadId', $threadIds);

            $responseDoc = [
                'thread_ids' => $threadIds,
                'contact_id' => isset($jsonData['contact_id']) ? (int)$jsonData['contact_id'] : null,
                'question' => $jsonData['question'] ?? 'General Analysis',
                'context' => $jsonData['context'] ?? null,
                'content_mode' => $jsonData['content_mode'] ?? 'full',
                'content_sent' => $jsonData['content_sent'] ?? null,
                'content_length' => $jsonData['content_length'] ?? 0,
                'estimated_tokens' => $jsonData['estimated_tokens'] ?? 0,
                'provider' => $jsonData['provider'] ?? 'unknown',
                'model' => $jsonData['model'] ?? 'unknown',
                'all_providers' => $jsonData['all_providers'] ?? [],
                'query_id' => $jsonData['query_id'] ?? null,
                'response' => $jsonData['response'] ?? null,
                'total_cost' => $jsonData['total_cost'] ?? 0,
                'created_at' => date('Y-m-d H:i:s')
            ];

            $id = $mongo->insert('ai_query_responses', $responseDoc);

            jsonResponse([
                'success' => true,
                'id' => $id,
                'message' => 'AI response saved'
            ]);
            break;

        // ==========================================
        // Get AI Query Response (MongoDB)
        // ==========================================
        case 'get_ai_response':
            $id = $_GET['id'] ?? '';
            if (!$id) {
                jsonError('Response ID required');
            }

            $mongo = \EmailIntelligence\MongoDatabase::getInstance();
            if (!$mongo->isAvailable()) {
                jsonError('MongoDB not available');
            }

            try {
                $response = $mongo->findOne('ai_query_responses', [
                    '_id' => new \MongoDB\BSON\ObjectId($id)
                ]);

                if (!$response) {
                    jsonError('Response not found', 404);
                }

                jsonResponse([
                    'success' => true,
                    'response' => $response
                ]);
            } catch (Exception $e) {
                jsonError('Invalid response ID: ' . $e->getMessage());
            }
            break;

        // ==========================================
        // List AI Query Responses (MongoDB)
        // ==========================================
        case 'list_ai_responses':
            $threadId = $_GET['thread_id'] ?? '';
            $contactId = isset($_GET['contact_id']) ? (int)$_GET['contact_id'] : null;
            $limit = min(50, max(1, (int)($_GET['limit'] ?? 20)));
            $offset = max(0, (int)($_GET['offset'] ?? 0));

            $mongo = \EmailIntelligence\MongoDatabase::getInstance();
            if (!$mongo->isAvailable()) {
                jsonError('MongoDB not available');
            }

            $filter = [];
            if ($threadId) {
                $threadId = resolveThreadId($threadId);
                $filter['thread_ids'] = $threadId;
            }
            if ($contactId) {
                $filter['contact_id'] = $contactId;
            }

            $responses = $mongo->findAll('ai_query_responses', $filter, [
                'sort' => ['created_at' => -1],
                'limit' => $limit,
                'skip' => $offset
            ]);

            // Get total count for pagination
            $total = $mongo->count('ai_query_responses', $filter);

            jsonResponse([
                'success' => true,
                'responses' => $responses,
                'count' => count($responses),
                'total' => $total,
                'offset' => $offset,
                'limit' => $limit
            ]);
            break;

        // ==========================================
        // Product Search - Start a new product search
        // ==========================================
        case 'startProductSearch':
            require_once dirname(__DIR__) . '/src/ProductSearch.php';

            $query = $_POST['query'] ?? $_GET['query'] ?? '';
            if (!$query) {
                jsonError('Search query is required');
            }

            $sites = $_POST['sites'] ?? $_GET['sites'] ?? [];
            if (is_string($sites)) {
                $sites = array_filter(array_map('trim', explode(',', $sites)));
            }

            $options = [
                'num_results' => (int)($_POST['num_results'] ?? $_GET['num_results'] ?? 5),
                'async' => (bool)($_POST['async'] ?? $_GET['async'] ?? false)
            ];

            $productSearch = new \EmailIntelligence\ProductSearch();

            if ($options['async']) {
                // Just start the search and return immediately
                $result = $productSearch->startSearch($query, $sites, $options);
            } else {
                // Run synchronously (start + execute)
                $result = $productSearch->search($query, $sites, $options);
            }

            jsonResponse($result);
            break;

        // ==========================================
        // Product Search - Get search status
        // ==========================================
        case 'getProductSearchStatus':
            require_once dirname(__DIR__) . '/src/ProductSearch.php';

            $searchId = $_GET['search_id'] ?? $_POST['search_id'] ?? '';
            if (!$searchId) {
                jsonError('Search ID is required');
            }

            $productSearch = new \EmailIntelligence\ProductSearch();
            $result = $productSearch->getSearchStatus($searchId);

            jsonResponse($result);
            break;

        // ==========================================
        // Product Search - Get search results
        // ==========================================
        case 'getProductSearchResults':
            require_once dirname(__DIR__) . '/src/ProductSearch.php';

            $searchId = $_GET['search_id'] ?? $_POST['search_id'] ?? '';
            if (!$searchId) {
                jsonError('Search ID is required');
            }

            $productSearch = new \EmailIntelligence\ProductSearch();
            $result = $productSearch->getSearchResults($searchId);

            jsonResponse($result);
            break;

        // ==========================================
        // Product Search - Execute pending search
        // ==========================================
        case 'executeProductSearch':
            require_once dirname(__DIR__) . '/src/ProductSearch.php';

            $searchId = $_GET['search_id'] ?? $_POST['search_id'] ?? '';
            if (!$searchId) {
                jsonError('Search ID is required');
            }

            $analyze = (bool)($_POST['analyze'] ?? $_GET['analyze'] ?? true);

            $productSearch = new \EmailIntelligence\ProductSearch();
            $result = $productSearch->executeSearch($searchId, $analyze);

            jsonResponse($result);
            break;

        // ==========================================
        // Product Search - Get recent searches
        // ==========================================
        case 'getProductSearchHistory':
            require_once dirname(__DIR__) . '/src/ProductSearch.php';

            $limit = min(50, max(5, (int)($_GET['limit'] ?? 10)));

            $productSearch = new \EmailIntelligence\ProductSearch();
            $result = $productSearch->getRecentSearches($limit);

            jsonResponse($result);
            break;

        // ==========================================
        // Product Search - Get available sites
        // ==========================================
        case 'getProductSearchSites':
            require_once dirname(__DIR__) . '/src/ProductSearch.php';

            $productSearch = new \EmailIntelligence\ProductSearch();
            $sites = $productSearch->getAvailableSites();

            jsonResponse([
                'success' => true,
                'sites' => $sites
            ]);
            break;

        // ==========================================
        // Product Search - Save products from extension scraping
        // ==========================================
        case 'saveProducts':
            require_once dirname(__DIR__) . '/src/MongoDatabase.php';

            $searchId = $_POST['search_id'] ?? json_decode(file_get_contents('php://input'), true)['search_id'] ?? '';
            $products = $_POST['products'] ?? json_decode(file_get_contents('php://input'), true)['products'] ?? [];

            if (empty($searchId)) {
                jsonError('Search ID is required');
            }

            if (empty($products)) {
                jsonError('No products to save');
            }

            $mongo = \EmailIntelligence\MongoDatabase::getInstance();
            $savedCount = 0;

            foreach ($products as $product) {
                $product['search_id'] = $searchId;
                $product['fetched_at'] = new \MongoDB\BSON\UTCDateTime();

                try {
                    $result = $mongo->insertOrUpdate(
                        'products',
                        $product,
                        ['external_id', 'site']
                    );
                    if ($result) $savedCount++;
                } catch (\Exception $e) {
                    // Log but continue
                    error_log("Error saving product: " . $e->getMessage());
                }
            }

            // Update search record with product count
            $mongo->update(
                'product_searches',
                ['search_id' => $searchId],
                [
                    'products_count' => $savedCount,
                    'updated_at' => new \MongoDB\BSON\UTCDateTime()
                ]
            );

            jsonResponse([
                'success' => true,
                'saved' => $savedCount,
                'total' => count($products)
            ]);
            break;

        // ==========================================
        // Business Ideas - Research Projects
        // ==========================================
        case 'business_ideas_projects':
            $manager = new \EmailIntelligence\BusinessIdeasManager();
            $status = $_GET['status'] ?? null;
            $limit = (int)($_GET['limit'] ?? 20);

            $filters = [];
            if ($status) {
                $filters['status'] = $status;
            }

            $projects = $manager->getProjects($filters, $limit);
            jsonResponse(['projects' => $projects]);
            break;

        case 'business_ideas_project':
            $id = $_GET['id'] ?? '';
            if (empty($id)) {
                jsonError('Project ID required');
            }

            $manager = new \EmailIntelligence\BusinessIdeasManager();
            $result = $manager->getProjectStatus($id);

            if (!$result['success']) {
                jsonError($result['error'] ?? 'Project not found');
            }

            jsonResponse($result);
            break;

        case 'create_business_ideas_project':
            $query = $_POST['query'] ?? '';
            if (empty($query)) {
                jsonError('Research query required');
            }

            $parameters = [];
            if (!empty($_POST['parameters'])) {
                $parameters = json_decode($_POST['parameters'], true) ?? [];
            }

            $manager = new \EmailIntelligence\BusinessIdeasManager();
            $result = $manager->createProject($query, $parameters);

            jsonResponse($result);
            break;

        case 'start_business_ideas_project':
            $id = $_POST['id'] ?? '';
            if (empty($id)) {
                jsonError('Project ID required');
            }

            $manager = new \EmailIntelligence\BusinessIdeasManager();
            $result = $manager->decomposeProjectIntoTasks($id);

            jsonResponse($result);
            break;

        case 'pause_business_ideas_project':
            $id = $_POST['id'] ?? '';
            if (empty($id)) {
                jsonError('Project ID required');
            }

            $manager = new \EmailIntelligence\BusinessIdeasManager();
            $success = $manager->setProjectStatus($id, 'paused');

            jsonResponse(['success' => $success]);
            break;

        case 'resume_business_ideas_project':
            $id = $_POST['id'] ?? '';
            if (empty($id)) {
                jsonError('Project ID required');
            }

            $manager = new \EmailIntelligence\BusinessIdeasManager();
            $success = $manager->setProjectStatus($id, 'running');

            jsonResponse(['success' => $success]);
            break;

        // ==========================================
        // Business Ideas - Task Queue
        // ==========================================
        case 'business_ideas_queue_status':
            $runner = new \EmailIntelligence\ResearchTaskRunner();
            $projectId = $_GET['project_id'] ?? null;

            $status = $runner->getQueueStatus($projectId);
            jsonResponse($status);
            break;

        case 'process_business_ideas_queue':
            $runner = new \EmailIntelligence\ResearchTaskRunner();
            $maxTasks = (int)($_POST['max_tasks'] ?? 5);
            $delayMs = (int)($_POST['delay_ms'] ?? 2000);

            $result = $runner->processQueue($maxTasks, $delayMs);
            jsonResponse($result);
            break;

        case 'business_ideas_tasks':
            $projectId = $_GET['project_id'] ?? '';
            if (empty($projectId)) {
                jsonError('Project ID required');
            }

            $runner = new \EmailIntelligence\ResearchTaskRunner();
            $status = $_GET['status'] ?? null;

            $tasks = $runner->getTasks($projectId, $status);
            jsonResponse(['tasks' => $tasks]);
            break;

        // ==========================================
        // Business Ideas - Findings & Scenarios
        // ==========================================
        case 'business_ideas_findings':
            $projectId = $_GET['project_id'] ?? '';
            if (empty($projectId)) {
                jsonError('Project ID required');
            }

            $mongo = \EmailIntelligence\MongoDatabase::getInstance();
            $findings = $mongo->findAll('business_research_findings',
                ['project_id' => $projectId],
                ['sort' => ['relevance.score' => -1], 'limit' => 100]
            );

            jsonResponse(['findings' => $findings]);
            break;

        case 'business_ideas_scenarios':
            $projectId = $_GET['project_id'] ?? '';
            if (empty($projectId)) {
                jsonError('Project ID required');
            }

            $mongo = \EmailIntelligence\MongoDatabase::getInstance();
            $scenarios = $mongo->findAll('business_scenarios',
                ['project_id' => $projectId]
            );

            jsonResponse(['scenarios' => $scenarios]);
            break;

        case 'generate_business_ideas_report':
            $id = $_POST['id'] ?? '';
            if (empty($id)) {
                jsonError('Project ID required');
            }

            $manager = new \EmailIntelligence\BusinessIdeasManager();
            $result = $manager->generateProjectReport($id);

            jsonResponse($result);
            break;

        case 'generate_business_scenarios':
            $id = $_POST['id'] ?? '';
            if (empty($id)) {
                jsonError('Project ID required');
            }

            $depth = $_POST['depth'] ?? 'high_level';

            $manager = new \EmailIntelligence\BusinessIdeasManager();
            $result = $manager->generateScenarios($id, $depth);

            jsonResponse($result);
            break;

        // ==========================================
        // Business Ideas - Context & Owner Status
        // ==========================================
        case 'business_context':
            $contextBuilder = new \EmailIntelligence\BusinessContextBuilder();
            $context = $contextBuilder->buildContext();

            jsonResponse(['context' => $context]);
            break;

        case 'get_owner_status':
            $contextBuilder = new \EmailIntelligence\BusinessContextBuilder();
            $status = $contextBuilder->getOwnerStatus();

            jsonResponse(['owner_status' => $status]);
            break;

        case 'save_owner_status':
            $contextBuilder = new \EmailIntelligence\BusinessContextBuilder();

            $data = [
                'revenue_target' => $_POST['revenue_target'] ?? '',
                'investment_capacity' => $_POST['investment_capacity'] ?? '',
                'growth_objectives' => $_POST['growth_objectives'] ?? '',
                'timeline' => $_POST['timeline'] ?? '',
                'notes' => $_POST['notes'] ?? ''
            ];

            $success = $contextBuilder->saveOwnerStatus($data);

            jsonResponse(['success' => $success]);
            break;

        // ==========================================
        // Web Search API (reusable by other services)
        // ==========================================
        case 'web_search':
            $searchService = new \EmailIntelligence\WebSearchService();

            if (!$searchService->isConfigured()) {
                jsonError('Google Search API not configured', 500);
            }

            $query = $_GET['q'] ?? $_POST['q'] ?? '';
            if (empty($query)) {
                jsonError('Search query (q) is required', 400);
            }

            $maxResults = (int)($_GET['max'] ?? $_POST['max'] ?? 10);
            $options = [];

            if (!empty($_GET['date_restrict'] ?? $_POST['date_restrict'])) {
                $options['dateRestrict'] = $_GET['date_restrict'] ?? $_POST['date_restrict'];
            }
            if (!empty($_GET['site'] ?? $_POST['site'])) {
                $options['siteSearch'] = $_GET['site'] ?? $_POST['site'];
            }

            $result = $searchService->search($query, $maxResults, $options);
            jsonResponse($result);
            break;

        case 'web_search_urls':
            $searchService = new \EmailIntelligence\WebSearchService();

            if (!$searchService->isConfigured()) {
                jsonError('Google Search API not configured', 500);
            }

            $query = $_GET['q'] ?? $_POST['q'] ?? '';
            if (empty($query)) {
                jsonError('Search query (q) is required', 400);
            }

            $maxResults = (int)($_GET['max'] ?? $_POST['max'] ?? 10);
            $result = $searchService->searchUrls($query, $maxResults);
            jsonResponse($result);
            break;

        case 'web_search_recent':
            $searchService = new \EmailIntelligence\WebSearchService();

            if (!$searchService->isConfigured()) {
                jsonError('Google Search API not configured', 500);
            }

            $query = $_GET['q'] ?? $_POST['q'] ?? '';
            if (empty($query)) {
                jsonError('Search query (q) is required', 400);
            }

            $period = $_GET['period'] ?? $_POST['period'] ?? 'm1'; // d1, w1, m1, y1
            $maxResults = (int)($_GET['max'] ?? $_POST['max'] ?? 10);

            $result = $searchService->searchRecent($query, $period, $maxResults);
            jsonResponse($result);
            break;

        case 'web_search_status':
            $searchService = new \EmailIntelligence\WebSearchService();
            jsonResponse($searchService->getStatus());
            break;

        // ==========================================
        // Chrome Extension Scraping API
        // ==========================================
        case 'extension_scrape_submit':
            // Receive scraped data from Chrome extension
            $mongo = \EmailIntelligence\MongoDatabase::getInstance();

            $data = json_decode(file_get_contents('php://input'), true);
            if (!$data) {
                $data = $_POST;
            }

            $taskId = $data['task_id'] ?? null;
            $url = $data['url'] ?? '';
            $content = $data['content'] ?? '';
            $title = $data['title'] ?? '';
            $source = $data['source'] ?? 'chrome_extension';

            if (empty($url) || empty($content)) {
                jsonError('URL and content are required', 400);
            }

            $scrapeData = [
                'task_id' => $taskId,
                'url' => $url,
                'title' => $title,
                'content' => $content,
                'text_length' => strlen($content),
                'source' => $source,
                'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
                'scraped_at' => date('Y-m-d H:i:s'),
                'created_at' => date('Y-m-d H:i:s')
            ];

            $id = $mongo->insert('extension_scraped_data', $scrapeData);

            // If task_id provided, update the task
            if ($taskId) {
                $mongo->update('business_research_tasks',
                    ['_id' => new \MongoDB\BSON\ObjectId($taskId)],
                    ['extension_scrape_received' => true, 'extension_scrape_id' => $id]
                );
            }

            jsonResponse([
                'success' => true,
                'id' => $id,
                'message' => 'Scrape data received'
            ]);
            break;

        case 'extension_get_tasks':
            // Get pending scrape tasks for Chrome extension
            $mongo = \EmailIntelligence\MongoDatabase::getInstance();

            $tasks = $mongo->findAll('extension_scrape_queue', [
                'status' => 'pending'
            ], ['sort' => ['priority' => -1, 'created_at' => 1], 'limit' => 10]);

            jsonResponse([
                'success' => true,
                'tasks' => array_map(function($t) {
                    return [
                        'id' => (string)$t['_id'],
                        'url' => $t['url'],
                        'type' => $t['type'] ?? 'scrape',
                        'selectors' => $t['selectors'] ?? [],
                        'wait_time' => $t['wait_time'] ?? 3000,
                        'requires_login' => $t['requires_login'] ?? false,
                        'login_domain' => $t['login_domain'] ?? null
                    ];
                }, $tasks),
                'count' => count($tasks)
            ]);
            break;

        case 'extension_queue_scrape':
            // Queue a URL for Chrome extension to scrape
            $mongo = \EmailIntelligence\MongoDatabase::getInstance();

            $data = json_decode(file_get_contents('php://input'), true);
            if (!$data) {
                $data = $_POST;
            }

            $url = $data['url'] ?? '';
            if (empty($url)) {
                jsonError('URL is required', 400);
            }

            $queueItem = [
                'url' => $url,
                'type' => $data['type'] ?? 'scrape',
                'selectors' => $data['selectors'] ?? [],
                'wait_time' => $data['wait_time'] ?? 3000,
                'requires_login' => $data['requires_login'] ?? false,
                'login_domain' => $data['login_domain'] ?? null,
                'callback_task_id' => $data['task_id'] ?? null,
                'priority' => $data['priority'] ?? 5,
                'status' => 'pending',
                'created_at' => date('Y-m-d H:i:s')
            ];

            $id = $mongo->insert('extension_scrape_queue', $queueItem);

            jsonResponse([
                'success' => true,
                'queue_id' => $id,
                'message' => 'URL queued for extension scraping'
            ]);
            break;

        case 'extension_complete_task':
            // Mark extension scrape task as complete
            $mongo = \EmailIntelligence\MongoDatabase::getInstance();

            $taskId = $_POST['task_id'] ?? $_GET['task_id'] ?? '';
            if (empty($taskId)) {
                jsonError('Task ID is required', 400);
            }

            $mongo->update('extension_scrape_queue',
                ['_id' => new \MongoDB\BSON\ObjectId($taskId)],
                ['status' => 'completed', 'completed_at' => date('Y-m-d H:i:s')]
            );

            jsonResponse(['success' => true]);
            break;

        // ==========================================
        // Default
        // ==========================================
        default:
            jsonError('Unknown action: ' . $action, 400);
    }

} catch (Exception $e) {
    jsonError('Server error: ' . $e->getMessage(), 500);
}

+        // AI Chat Sessions - List recent sessions

+            $ctxMongo = \EmailIntelligence\MongoDatabase::getInstance();

+            $sessions = $ctxMongo->findAll('ai_chat_sessions', [], [

+            // Strip full messages from list view (just return metadata)

+            $sessionList = array_map(function($s) {

+                    'session_id' => $s['session_id'] ?? '',

+                    'title' => $s['title'] ?? 'Untitled',

+                    'agent' => $s['agent'] ?? 'general',

+                    'provider' => $s['provider'] ?? '',

+                    'contact_name' => $s['contact_name'] ?? null,

+                    'contact_id' => $s['contact_id'] ?? null,

+                    'context_thread_ids' => $s['context_thread_ids'] ?? [],

+                    'message_count' => count($s['messages'] ?? []),

+                    'created_at' => $s['created_at'] ?? '',

+                    'updated_at' => $s['updated_at'] ?? '',

+            jsonResponse(['success' => true, 'sessions' => $sessionList]);

+        // AI Chat Session - Load a specific session

+            $sessionId = $_GET['session_id'] ?? '';

+            if (!$sessionId) jsonError('session_id required');

+            $session = $ctxMongo->findOne('ai_chat_sessions', ['session_id' => $sessionId]);

+            if (!$session) jsonError('Session not found', 404);

+            jsonResponse(['success' => true, 'session' => [

+                'session_id' => $session['session_id'],

+                'title' => $session['title'] ?? 'Untitled',

+                'agent' => $session['agent'] ?? 'general',

+                'provider' => $session['provider'] ?? '',

+                'model' => $session['model'] ?? '',

+                'contact_id' => $session['contact_id'] ?? null,

+                'contact_name' => $session['contact_name'] ?? null,

+                'context_thread_ids' => $session['context_thread_ids'] ?? [],

+                'messages' => $session['messages'] ?? [],

+                'created_at' => $session['created_at'] ?? '',

+                'updated_at' => $session['updated_at'] ?? '',

+            if ($_SERVER['REQUEST_METHOD'] !== 'POST') jsonError('POST required', 405);

+            $jsonData = json_decode(file_get_contents('php://input'), true) ?? [];

+            $sessionId = $jsonData['session_id'] ?? '';

+            $deleted = $ctxMongo->delete('ai_chat_sessions', ['session_id' => $sessionId]);

+            jsonResponse(['success' => true, 'deleted' => $deleted]);

+        // AI Contact Memories - Get memories for a contact

+            $contactId = (int)($_GET['contact_id'] ?? 0);

+            if (!$contactId) jsonError('contact_id required');

+            $memDoc = $ctxMongo->findOne('ai_contact_memory', ['contact_id' => $contactId]);

+                'contact_id' => $contactId,

+                'memories' => $memDoc ? ($memDoc['memories'] ?? []) : [],

+        // AI Contact Memory Add - Manually add a memory fact

+            $contactId = (int)($jsonData['contact_id'] ?? 0);

+            $fact = trim($jsonData['fact'] ?? '');

+            if (!$contactId || !$fact) jsonError('contact_id and fact required');

+            $existing = $ctxMongo->findOne('ai_contact_memory', ['contact_id' => $contactId]);

+            $memories = $existing ? ($existing['memories'] ?? []) : [];

+                'id' => uniqid('mem_', true),

+                'created_at' => date('Y-m-d H:i:s'),

+            $ctxMongo->insertOrUpdate('ai_contact_memory', [

+                'memories' => $memories,

+                'updated_at' => date('Y-m-d H:i:s'),

+            jsonResponse(['success' => true, 'memory' => $newMem]);

+        // AI Contact Memory Delete - Remove a memory fact

+        case 'ai_contact_memory_delete':

+            $memoryId = $jsonData['memory_id'] ?? '';

+            if (!$contactId || !$memoryId) jsonError('contact_id and memory_id required');

+            if ($existing && !empty($existing['memories'])) {

+                $memories = array_values(array_filter($existing['memories'], function($m) use ($memoryId) {

+                    return ($m['id'] ?? '') !== $memoryId;

+                    'contact_id' => $contactId,

+                    'memories' => $memories,

+                    'updated_at' => date('Y-m-d H:i:s'),

+            jsonResponse(['success' => true]);