  2305→            break;
  2306→
  2307→        // ==========================================
  2308→        // AI Chat - Multi-model chat with full context
  2309→        // ==========================================
  2310→        case 'ai_chat':
  2311→            if ($_SERVER['REQUEST_METHOD'] !== 'POST') jsonError('POST required', 405);
  2312→
  2313→            $jsonData = json_decode(file_get_contents('php://input'), true) ?? [];
  2314→            $chatMessage  = trim($jsonData['message'] ?? '');
  2315→            $chatModel    = $jsonData['model'] ?? 'gpt-4o';
  2316→            $chatMode     = $jsonData['mode'] ?? 'general';   // thread|contact|quote|reply|general
  2317→            $chatThreadId = $jsonData['thread_id'] ?? null;
  2318→            $chatContactId= (int)($jsonData['contact_id'] ?? 0);
  2319→            $chatHistory  = $jsonData['history'] ?? [];       // [{role,content}]
  2320→            $chatEmailId  = (int)($jsonData['email_id'] ?? 0);
  2321→
  2322→            if (!$chatMessage) jsonError('message required');
  2323→            if ($chatThreadId) $chatThreadId = resolveThreadId($chatThreadId);
  2324→
  2325→            // --- Build context block ---
  2326→            $ctxParts = [];
  2327→            $ctxAdapter = QueryAdapter::getInstance();
  2328→            $ctxMongo = \EmailIntelligence\MongoDatabase::getInstance();
  2329→            $useMongoDB = $ctxAdapter->getActiveDatabase() === 'mongodb';
  2330→
  2331→            // Thread context - use MongoDB-aware queries
  2332→            if ($chatThreadId) {
  2333→                if ($useMongoDB) {
  2334→                    $ctxEmails = $ctxMongo->findAll('emails', ['thread_id' => $chatThreadId], [
  2335→                        'sort' => ['date' => 1]
  2336→                    ]);
  2337→                } else {
  2338→                    $ctxEmails = $db->fetchAll(
  2339→                        "SELECT direction, date, from_email, to_email, subject, body_text FROM emails WHERE thread_id = ? ORDER BY date ASC",
  2340→                        [$chatThreadId]
  2341→                    );
  2342→                }
  2343→                if ($ctxEmails) {
  2344→                    $ctxParts[] = "=== EMAIL THREAD (" . count($ctxEmails) . " emails) ===";
  2345→                    foreach ($ctxEmails as $i => $em) {
  2346→                        $dir = ($em['direction'] ?? '') === 'inbound' ? 'FROM THEM' : 'FROM US';
  2347→                        $body = substr($em['body_text'] ?? '', 0, 800);
  2348→                        $ctxParts[] = "Email #".($i+1)." {$dir} ({$em['date']})\nSubject: {$em['subject']}\n{$body}";
  2349→                    }
  2350→                    // Include existing analysis if available
  2351→                    if ($useMongoDB) {
  2352→                        $ctxAnalysis = $ctxMongo->findOne('thread_analysis', ['thread_id' => $chatThreadId, 'is_analyzed' => 1]);
  2353→                        if (!$ctxAnalysis) {
  2354→                            // Try without is_analyzed filter (MongoDB may store as bool or int)
  2355→                            $ctxAnalysis = $ctxMongo->findOne('thread_analysis', ['thread_id' => $chatThreadId]);
  2356→                        }
  2357→                    } else {
  2358→                        $ctxAnalysis = $db->fetchOne("SELECT summary, raw_analysis FROM thread_analysis WHERE thread_id = ? AND is_analyzed=1", [$chatThreadId]);
  2359→                    }
  2360→                    if ($ctxAnalysis && !empty($ctxAnalysis['summary'])) {
  2361→                        $ctxParts[] = "=== EXISTING AI ANALYSIS ===\nSummary: " . $ctxAnalysis['summary'];
  2362→                    }
  2363→                }
  2364→            }
  2365→
  2366→            // Contact context - use MongoDB-aware queries
  2367→            if ($chatContactId) {
  2368→                if ($useMongoDB) {
  2369→                    $ctxContact = $ctxMongo->findOne('contacts', ['id' => $chatContactId]);
  2370→                    $ctxProfile = $ctxMongo->findOne('contact_profiles', ['contact_id' => $chatContactId]);
  2371→                } else {
  2372→                    $ctxContact = $db->fetchOne("SELECT name, email, type, company FROM contacts WHERE id = ?", [$chatContactId]);
  2373→                    $ctxProfile = $db->fetchOne("SELECT * FROM contact_profiles WHERE contact_id = ?", [$chatContactId]);
  2374→                }
  2375→                if ($ctxContact) {
  2376→                    $ctxParts[] = "=== CONTACT: {$ctxContact['name']} ({$ctxContact['email']}) ===";
  2377→                    if ($ctxProfile) {
  2378→                        $ctxParts[] = "Trust score: " . ($ctxProfile['overall_trust_score'] ?? 'N/A') .
  2379→                            " | Communication style: " . ($ctxProfile['communication_style'] ?? 'N/A');
  2380→                    }
  2381→                }
  2382→            }
  2383→
  2384→            // Single email context (for reply mode) - use MongoDB-aware queries
  2385→            if ($chatEmailId) {
  2386→                if ($useMongoDB) {
  2387→                    $ctxEmail = $ctxMongo->findOne('emails', ['id' => $chatEmailId]);
  2388→                } else {
  2389→                    $ctxEmail = $db->fetchOne("SELECT * FROM emails WHERE id = ?", [$chatEmailId]);
  2390→                }
  2391→                if ($ctxEmail) {
  2392→                    $ctxParts[] = "=== SPECIFIC EMAIL TO REPLY TO ===\nFrom: {$ctxEmail['from_email']}\nSubject: {$ctxEmail['subject']}\n{$ctxEmail['body_text']}";
  2393→                }
  2394→            }
  2395→
  2396→            // Mode-specific system instructions
  2397→            $systemInstructions = [
  2398→                'thread'  => "You are a business intelligence assistant analyzing an email thread. Provide insights about the conversation, relationships, risks, and opportunities. Be direct and specific.",
  2399→                '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.",
  2400→                '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.",
  2401→                '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.",
  2402→                'general' => "You are a business intelligence assistant with access to email and contact data. Answer questions, provide analysis, and help with business decisions.",
  2403→            ];
  2404→            $sysMsg = $systemInstructions[$chatMode] ?? $systemInstructions['general'];
  2405→            $contextBlock = implode("\n\n", $ctxParts);
  2406→            if ($contextBlock) {
  2407→                $sysMsg .= "\n\nCONTEXT:\n" . $contextBlock;
  2408→            }
  2409→
  2410→            // Build messages array with history
  2411→            $messages = [];
  2412→            foreach ($chatHistory as $h) {
  2413→                if (isset($h['role'], $h['content'])) {
  2414→                    $messages[] = ['role' => $h['role'], 'content' => $h['content']];
  2415→                }
  2416→            }
  2417→            $messages[] = ['role' => 'user', 'content' => $chatMessage];
  2418→
  2419→            // Determine provider from model name
  2420→            $chatProvider = 'openai';
  2421→            if (strpos($chatModel, 'claude') !== false) $chatProvider = 'claude';
  2422→            elseif (strpos($chatModel, 'gemini') !== false) $chatProvider = 'gemini';
  2423→            elseif (strpos($chatModel, 'grok') !== false) $chatProvider = 'grok';
  2424→
  2425→            // Route through proxy for multi-model support
  2426→            $proxyUrl = 'https://mssql.danmarcrm.com/dev1/extension2/aiapi/api_codex51/api/index.php';
  2427→            $proxyPayload = json_encode([
  2428→                'action'       => 'ask',
  2429→                'user_content' => $chatMessage,
  2430→                'system'       => $sysMsg,
  2431→                'models'       => [$chatModel],
  2432→                'providers'    => [$chatProvider],
  2433→                'history'      => array_slice($chatHistory, -10), // last 10 turns
  2434→            ]);
  2435→
  2436→            $ch = curl_init($proxyUrl);
  2437→            curl_setopt_array($ch, [
  2438→                CURLOPT_RETURNTRANSFER => true,
  2439→                CURLOPT_POST => true,
  2440→                CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
  2441→                CURLOPT_POSTFIELDS => $proxyPayload,
  2442→                CURLOPT_TIMEOUT => 120,
  2443→            ]);
  2444→            $proxyResponse = curl_exec($ch);
  2445→            $curlError = curl_error($ch);
  2446→            curl_close($ch);
  2447→
  2448→            if ($curlError) {
  2449→                // Fallback to direct ThreadAnalyzer if proxy fails
  2450→                try {
  2451→                    $analyzer = new ThreadAnalyzer($chatProvider === 'openai' ? 'openai' : 'claude', $chatModel);
  2452→                    $fullPrompt = $sysMsg . "\n\nUser: " . $chatMessage;
  2453→                    $answer = $analyzer->askClaude($fullPrompt);
  2454→                    jsonResponse(['success' => true, 'answer' => $answer, 'model' => $chatModel, 'via' => 'direct']);
  2455→                } catch (Exception $e) {
  2456→                    jsonError('AI request failed: ' . $e->getMessage());
  2457→                }
  2458→                break;
  2459→            }
  2460→
  2461→            $proxyResult = json_decode($proxyResponse, true);
  2462→            if (!$proxyResult || !$proxyResult['success']) {
  2463→                jsonError('AI proxy error: ' . ($proxyResult['error'] ?? 'Unknown error'));
  2464→            }
  2465→
  2466→            // Extract answer from proxy response
  2467→            // Proxy returns: {"success":true,"responses":{"openai_response":{"response":"...","model":"..."}}}
  2468→            $answer = '';
  2469→            $usedModel = $chatModel;
  2470→            $responses = $proxyResult['responses'] ?? [];
  2471→            if (!empty($responses)) {
  2472→                $firstResponse = reset($responses);
  2473→                $answer = $firstResponse['response'] ?? $firstResponse['content'] ?? '';
  2474→                $usedModel = $firstResponse['model'] ?? $chatModel;
  2475→            }
  2476→            if (!$answer) {
  2477→                // Fallback: try top-level content
  2478→                $answer = $proxyResult['content'] ?? $proxyResult['response'] ?? '';
  2479→            }
  2480→            if (!$answer) {
  2481→                jsonError('AI proxy returned empty response. Raw: ' . substr($proxyResponse, 0, 200));
  2482→            }
  2483→
  2484→            jsonResponse([
  2485→                'success' => true,
  2486→                'answer'  => $answer,
  2487→                'model'   => $usedModel,
  2488→                'provider'=> $chatProvider,
  2489→                'via'     => 'proxy'
  2490→            ]);
  2491→            break;
  2492→
  2493→
  2494→

            break;

        // ==========================================
        // AI Chat - Multi-model chat with full context
        // ==========================================
        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';
            $chatMode     = $jsonData['mode'] ?? 'general';   // thread|contact|quote|reply|general
            $chatThreadId = $jsonData['thread_id'] ?? null;
            $chatContactId= (int)($jsonData['contact_id'] ?? 0);
            $chatHistory  = $jsonData['history'] ?? [];       // [{role,content}]
            $chatEmailId  = (int)($jsonData['email_id'] ?? 0);

            if (!$chatMessage) jsonError('message required');
            if ($chatThreadId) $chatThreadId = resolveThreadId($chatThreadId);

            // --- Build context block ---
            $ctxParts = [];
            $ctxAdapter = QueryAdapter::getInstance();
            $ctxMongo = \EmailIntelligence\MongoDatabase::getInstance();
            $useMongoDB = $ctxAdapter->getActiveDatabase() === 'mongodb';

            // Thread context - use MongoDB-aware queries
            if ($chatThreadId) {
                if ($useMongoDB) {
                    $ctxEmails = $ctxMongo->findAll('emails', ['thread_id' => $chatThreadId], [
                        '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",
                        [$chatThreadId]
                    );
                }
                if ($ctxEmails) {
                    $ctxParts[] = "=== EMAIL THREAD (" . count($ctxEmails) . " emails) ===";
                    foreach ($ctxEmails as $i => $em) {
                        $dir = ($em['direction'] ?? '') === 'inbound' ? 'FROM THEM' : 'FROM US';
                        $body = substr($em['body_text'] ?? '', 0, 800);
                        $ctxParts[] = "Email #".($i+1)." {$dir} ({$em['date']})\nSubject: {$em['subject']}\n{$body}";
                    }
                    // Include existing analysis if available
                    if ($useMongoDB) {
                        $ctxAnalysis = $ctxMongo->findOne('thread_analysis', ['thread_id' => $chatThreadId, 'is_analyzed' => 1]);
                        if (!$ctxAnalysis) {
                            // Try without is_analyzed filter (MongoDB may store as bool or int)
                            $ctxAnalysis = $ctxMongo->findOne('thread_analysis', ['thread_id' => $chatThreadId]);
                        }
                    } else {
                        $ctxAnalysis = $db->fetchOne("SELECT summary, raw_analysis FROM thread_analysis WHERE thread_id = ? AND is_analyzed=1", [$chatThreadId]);
                    }
                    if ($ctxAnalysis && !empty($ctxAnalysis['summary'])) {
                        $ctxParts[] = "=== EXISTING AI ANALYSIS ===\nSummary: " . $ctxAnalysis['summary'];
                    }
                }
            }

            // Contact context - use MongoDB-aware queries
            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');
                    }
                }
            }

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

            // Mode-specific system instructions
            $systemInstructions = [
                '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.",
            ];
            $sysMsg = $systemInstructions[$chatMode] ?? $systemInstructions['general'];
            $contextBlock = implode("\n\n", $ctxParts);
            if ($contextBlock) {
                $sysMsg .= "\n\nCONTEXT:\n" . $contextBlock;
            }

            // 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 from model name
            $chatProvider = 'openai';
            if (strpos($chatModel, 'claude') !== false) $chatProvider = 'claude';
            elseif (strpos($chatModel, 'gemini') !== false) $chatProvider = 'gemini';
            elseif (strpos($chatModel, 'grok') !== false) $chatProvider = 'grok';

            // 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'       => [$chatModel],
                'providers'    => [$chatProvider],
                'history'      => array_slice($chatHistory, -10), // last 10 turns
            ]);

            $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']);
                } 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
            // Proxy returns: {"success":true,"responses":{"openai_response":{"response":"...","model":"..."}}}
            $answer = '';
            $usedModel = $chatModel;
            $responses = $proxyResult['responses'] ?? [];
            if (!empty($responses)) {
                $firstResponse = reset($responses);
                $answer = $firstResponse['response'] ?? $firstResponse['content'] ?? '';
                $usedModel = $firstResponse['model'] ?? $chatModel;
            }
            if (!$answer) {
                // Fallback: try top-level content
                $answer = $proxyResult['content'] ?? $proxyResult['response'] ?? '';
            }
            if (!$answer) {
                jsonError('AI proxy returned empty response. Raw: ' . substr($proxyResponse, 0, 200));
            }

            jsonResponse([
                'success' => true,
                'answer'  => $answer,
                'model'   => $usedModel,
                'provider'=> $chatProvider,
                'via'     => 'proxy'
            ]);
            break;


Now I have the full `ai_chat` case block (lines 2310-2491). Let me replace it with the enhanced version that includes:

1. Multi-thread support (`thread_ids` array)
2. Stripped quoted content
3. Token budget management
4. Agent presets
5. Session persistence (auto-save)
6. Contact memory injection
7. Auto-memory extraction from AI response
8. Fixed provider/model proxy format

This is a big replacement. Let me write it carefully.


Now I'll replace the entire `ai_chat` case block with the enhanced version. This is a big change — it adds multi-thread, token budget, agents, sessions, and memory all at once.


Edit

        // ==========================================
        // AI Chat - Multi-model chat with full context
        // ==========================================
        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';
            $chatMode     = $jsonData['mode'] ?? 'general';   // thread|contact|quote|reply|general
            $chatThreadId = $jsonData['thread_id'] ?? null;
            $chatContactId= (int)($jsonData['contact_id'] ?? 0);
            $chatHistory  = $jsonData['history'] ?? [];       // [{role,content}]
            $chatEmailId  = (int)($jsonData['email_id'] ?? 0);

            if (!$chatMessage) jsonError('message required');
            if ($chatThreadId) $chatThreadId = resolveThreadId($chatThreadId);

            // --- Build context block ---
            $ctxParts = [];
            $ctxAdapter = QueryAdapter::getInstance();
            $ctxMongo = \EmailIntelligence\MongoDatabase::getInstance();
            $useMongoDB = $ctxAdapter->getActiveDatabase() === 'mongodb';

            // Thread context - use MongoDB-aware queries
            if ($chatThreadId) {
                if ($useMongoDB) {
                    $ctxEmails = $ctxMongo->findAll('emails', ['thread_id' => $chatThreadId], [
                        '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",
                        [$chatThreadId]
                    );
                }
                if ($ctxEmails) {
                    $ctxParts[] = "=== EMAIL THREAD (" . count($ctxEmails) . " emails) ===";
                    foreach ($ctxEmails as $i => $em) {
                        $dir = ($em['direction'] ?? '') === 'inbound' ? 'FROM THEM' : 'FROM US';
                        $body = substr($em['body_text'] ?? '', 0, 800);
                        $ctxParts[] = "Email #".($i+1)." {$dir} ({$em['date']})\nSubject: {$em['subject']}\n{$body}";
                    }
                    // Include existing analysis if available
                    if ($useMongoDB) {
                        $ctxAnalysis = $ctxMongo->findOne('thread_analysis', ['thread_id' => $chatThreadId, 'is_analyzed' => 1]);
                        if (!$ctxAnalysis) {
                            // Try without is_analyzed filter (MongoDB may store as bool or int)
                            $ctxAnalysis = $ctxMongo->findOne('thread_analysis', ['thread_id' => $chatThreadId]);
                        }
                    } else {
                        $ctxAnalysis = $db->fetchOne("SELECT summary, raw_analysis FROM thread_analysis WHERE thread_id = ? AND is_analyzed=1", [$chatThreadId]);
                    }
                    if ($ctxAnalysis && !empty($ctxAnalysis['summary'])) {
                        $ctxParts[] = "=== EXISTING AI ANALYSIS ===\nSummary: " . $ctxAnalysis['summary'];
                    }
                }
            }

            // Contact context - use MongoDB-aware queries
            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');
                    }
                }
            }

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

            // Mode-specific system instructions
            $systemInstructions = [
                '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.",
            ];
            $sysMsg = $systemInstructions[$chatMode] ?? $systemInstructions['general'];
            $contextBlock = implode("\n\n", $ctxParts);
            if ($contextBlock) {
                $sysMsg .= "\n\nCONTEXT:\n" . $contextBlock;
            }

            // 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 from model name
            $chatProvider = 'openai';
            if (strpos($chatModel, 'claude') !== false) $chatProvider = 'claude';
            elseif (strpos($chatModel, 'gemini') !== false) $chatProvider = 'gemini';
            elseif (strpos($chatModel, 'grok') !== false) $chatProvider = 'grok';

            // 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'       => [$chatModel],
                'providers'    => [$chatProvider],
                'history'      => array_slice($chatHistory, -10), // last 10 turns
            ]);

            $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']);
                } 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
            // Proxy returns: {"success":true,"responses":{"openai_response":{"response":"...","model":"..."}}}
            $answer = '';
            $usedModel = $chatModel;
            $responses = $proxyResult['responses'] ?? [];
            if (!empty($responses)) {
                $firstResponse = reset($responses);
                $answer = $firstResponse['response'] ?? $firstResponse['content'] ?? '';
                $usedModel = $firstResponse['model'] ?? $chatModel;
            }
            if (!$answer) {
                // Fallback: try top-level content
                $answer = $proxyResult['content'] ?? $proxyResult['response'] ?? '';
            }
            if (!$answer) {
                jsonError('AI proxy returned empty response. Raw: ' . substr($proxyResponse, 0, 200));
            }

            jsonResponse([
                'success' => true,
                'answer'  => $answer,
                'model'   => $usedModel,
                'provider'=> $chatProvider,
                'via'     => 'proxy'
            ]);
            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->upsert('ai_contact_memory',
                            ['contact_id' => $chatContactId],
                            [
                                'contact_id' => $chatContactId,
                                'contact_email' => $ctxContact['email'] ?? '',
                                'memories' => $memories,
                                'updated_at' => date('Y-m-d H:i:s'),
                            ]
                        );
                    }
                }
            }

            // 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->upsert('ai_chat_sessions',
                ['session_id' => $chatSessionId],
                [
                    '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,
                ]
            );

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