/**
 * ai-chat-stream Edge Function
 * C端消费者聊天 - 直接大模型流式输出（支持多轮对话记忆 + 主动澄清）
 *
 * 通过 AI Hub（OpenAI 兼容 API）直接调用大模型，不再经过 Dify/Coze 中间层。
 * 无 AI Hub 时回退到 Lovable Gateway。
 *
 * v2 更新：
 * - 从 messages 表加载对话历史，注入 LLM 上下文（多轮记忆）
 * - 系统提示词增强：意图模糊时主动追问澄清（CLARIFY intent）
 * - 支持 localConversationId 定位本地会话
 */

import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4';

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-trace-id, x-user-id',
  'Access-Control-Allow-Methods': 'POST, OPTIONS',
};

interface ChatRequest {
  message: string;
  conversationId?: string;        // AI 服务商的远程会话 ID
  localConversationId?: string;   // ★ 本地 conversations 表的主键 UUID
  inputs: {
    user_id?: string;
    trace_id: string;
    user_context?: string;
    catalog?: Array<{
      id: string;
      name: string;
      price: number;
      stock_quantity?: number;
      category?: string;
      description?: string;
    }>;
  };
}

// ─── AI Hub / LLM 配置 ───
const AI_HUB_KEY = Deno.env.get('AI_HUB_API_KEY') || '';
const AI_HUB_BASE = (Deno.env.get('AI_HUB_BASE_URL') || '').replace(/\/+$/, '');
const AI_HUB_MODEL = Deno.env.get('AI_HUB_DEFAULT_CHAT_MODEL') || 'agent-default';
const LOVABLE_KEY = Deno.env.get('LOVABLE_API_KEY') || '';
const USE_AI_HUB = !!(AI_HUB_KEY && AI_HUB_BASE);

// ─── 对话历史类型 ───
type HistoryMessage = { role: 'user' | 'assistant'; content: string };

// ─── 系统提示词构建 ───
function buildSystemPrompt(catalog: any[], userContext: string): string {
  const catalogSection = catalog.length > 0
    ? `\n\n## 可用产品目录（共${catalog.length}个，展示前30个）\n${
        catalog.slice(0, 30).map(p =>
          `• ID="${p.id}" | ${p.name} | ¥${p.price} | 库存:${p.stock_quantity ?? '未知'} | ${p.category || ''} | ${(p.description || '').substring(0, 60)}`
        ).join('\n')
      }\n\n严格规则：product_id 必须从上面目录中逐字符精确复制，禁止编造、简化或截断UUID！`
    : '\n\n（当前无可用产品目录）';

  const contextSection = userContext
    ? `\n\n## 用户上下文\n${userContext}`
    : '';

  return `你是"小智"，千江商城的智能购物顾问。

## 核心规则
1. 产品ID必须从产品目录中精确复制，严禁编造或生成
2. 所有价格、库存以目录为准，不可臆造
3. 回复使用友好、专业、简洁的中文
4. 不透露你是AI
5. 理解对话历史，记住用户之前说过的话——结合上下文做推荐和回复

${catalogSection}${contextSection}

## 澄清规则（重要）
当用户意图模糊、信息不足时，你必须**主动追问澄清**，而不是盲目推荐。需要澄清的典型场景：

1. **购物意图模糊**：用户说"想买个手机"但没提预算、品牌偏好、用途 → 追问预算范围、偏好品牌/功能、主要用途
2. **对比请求缺标准**：用户说"哪个更好"但没说看重什么 → 追问用户最在意的因素（价格/性能/外观/售后）
3. **售后请求缺信息**：用户提退货/维修但没说订单号 → 友好索要订单号
4. **预算不明确**：用户只说"便宜的"/"好一点的" → 追问具体预算区间
5. **模糊指代**：用户说"刚才那个"、"太贵了有便宜的吗" → 结合对话历史定位之前推荐的产品

澄清要求：
- 每次最多问2-3个关键问题，不要连珠炮式提问
- 语气友好，先共情再追问（示例："我理解您想找一款手机，为了给您更精准的推荐，想了解一下您的预算和品牌偏好？"）
- 澄清后 JSON 中标记 intent: "CLARIFY"
- clarifies 字段列出具体追问点（数组，每个一条追问）

## 回复末尾必须包含JSON
\`\`\`json
{"picks":[{"product_id":"从目录复制的真实UUID","note":"推荐理由"}],"intent":"SHOPPING|COMPARE|AFTERSALES|GENERAL|CLARIFY","highlights":["要点1","要点2"],"clarifies":["追问1","追问2"],"title":"简短标题"}
\`\`\`

## 规则
- picks 最多5个，product_id 必须从目录逐字符复制；澄清模式下 picks 为空数组
- intent: SHOPPING(推荐购物)、COMPARE(对比)、AFTERSALES(售后)、GENERAL(一般)、CLARIFY(主动澄清追问)
- clarifies: 当 intent=CLARIFY 时必填，列出2-3个具体追问问题
- highlights: 2-4个回复要点（各20-80字）；澄清模式下为对话摘要
- title: 简短对话标题（≤30字）
- 对比类询问使用Markdown表格
- 售后问题友好引导联系人工客服`;
}

// ─── 对话历史加载 ───

/** 从 messages 表加载指定会话的历史消息 */
async function loadConversationHistory(
  supabase: any,
  conversationId: string,
  userId: string,
  maxMessages = 20
): Promise<HistoryMessage[]> {
  try {
    console.log(`🔍 [ai-chat-stream] 查询历史: conv_id=${conversationId} user_id=${userId} limit=${maxMessages}`);
    const { data, error } = await supabase
      .from('messages')
      .select('sender_type, content')
      .eq('conversation_id', conversationId)
      .eq('user_id', userId)
      .order('created_at', { ascending: true })
      .limit(maxMessages);

    if (error) {
      console.error('❌ [ai-chat-stream] 加载历史消息失败:', JSON.stringify(error));
      return [];
    }

    console.log(`📜 [ai-chat-stream] 历史查询结果: conversation_id=${conversationId} user_id=${userId} rows=${data?.length || 0}`);
    return (data || []).map((m: any) => ({
      role: m.sender_type === 'user' ? 'user' : 'assistant',
      content: m.content,
    }));
  } catch (err) {
    console.error('[ai-chat-stream] 加载历史消息异常:', err);
    return [];
  }
}

/** 按总字符数截断历史消息，保留最近的消息 */
function truncateHistory(history: HistoryMessage[], maxChars: number): HistoryMessage[] {
  let total = 0;
  const result: HistoryMessage[] = [];
  // 从最新往最早遍历，保留最近的消息直到达到上限
  for (let i = history.length - 1; i >= 0; i--) {
    if (total + history[i].content.length > maxChars) {
      if (result.length === 0) result.unshift(history[i]); // 至少保留最新一条
      break;
    }
    total += history[i].content.length;
    result.unshift(history[i]); // 保持时间顺序
  }
  return result;
}

// ─── 结构化输出解析 ───

// 尝试从文本中提取完整的结构化 JSON（包含 picks/intent/highlights/comparison/next 等字段）
function parseStructuredPayload(text: string): any | null {
  const candidates: string[] = [];
  const fencedMatch = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
  if (fencedMatch) candidates.push(fencedMatch[1]);
  const trimmed = text.trim();
  if (trimmed.startsWith('{')) candidates.push(trimmed);
  // 扫描第一个 { 到最后一个 } 之间的内容
  const firstBrace = text.indexOf('{');
  const lastBrace = text.lastIndexOf('}');
  if (firstBrace >= 0 && lastBrace > firstBrace) {
    candidates.push(text.slice(firstBrace, lastBrace + 1));
  }
  for (const candidate of candidates) {
    try {
      const parsed = JSON.parse(candidate);
      if (parsed && typeof parsed === 'object' && (parsed.picks || parsed.intent || parsed.highlights || parsed.clarifies)) {
        return parsed;
      }
    } catch {
      // 继续
    }
  }
  return null;
}

// 解析文本中的 picks - 支持三种格式：① 纯 JSON 对象 ② ```json``` 代码块 ③ 文本中的 JSON 片段
function parsePicksFromText(text: string, catalog: any[]): Array<{ product_id: string; note: string }> {
  const picks: Array<{ product_id: string; note: string }> = [];
  const candidates: string[] = [];

  // 候选 1：```json ... ``` 代码块
  const fencedMatch = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
  if (fencedMatch) candidates.push(fencedMatch[1]);

  // 候选 2：整个文本 trim 后若以 { 开头，尝试整体解析
  const trimmed = text.trim();
  if (trimmed.startsWith('{')) candidates.push(trimmed);

  // 候选 3：搜索 "picks" 关键字附近的 JSON 数组片段
  const picksArrMatch = text.match(/"picks"\s*:\s*(\[[\s\S]*?\])/);
  if (picksArrMatch) candidates.push(`{"picks":${picksArrMatch[1]}}`);

  for (const candidate of candidates) {
    try {
      const parsed = JSON.parse(candidate);
      if (parsed?.picks && Array.isArray(parsed.picks)) {
        for (const p of parsed.picks) {
          if (!p?.product_id) continue;
          const exists = catalog.some(c => c.id === p.product_id);
          if (exists && !picks.some(pick => pick.product_id === p.product_id)) {
            picks.push({ product_id: p.product_id, note: p.note || '' });
          }
        }
        if (picks.length > 0) return picks;
      }
    } catch {
      // 继续尝试下一个候选
    }
  }

  // 兜底候选 4：扫描文本中所有 UUID，匹配 catalog
  if (picks.length === 0) {
    const uuidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
    const found = text.match(uuidRegex) || [];
    for (const id of found) {
      const product = catalog.find(c => c.id === id);
      if (product && !picks.some(pick => pick.product_id === id)) {
        picks.push({ product_id: id, note: '' });
      }
    }
  }

  return picks;
}

// 检测意图（兜底方案，LLM 返回结构化 JSON 时优先使用 LLM 的判断）
function detectIntent(message: string, answer: string): string {
  const shoppingKeywords = ['买', '购', '推荐', '价格', '多少钱', '手机', '电脑'];
  const afterSalesKeywords = ['退货', '换货', '售后', '维修'];
  const compareKeywords = ['对比', '比较', '哪个更好'];
  const clarifyKeywords = ['是吗', '什么', '哪个', '哪款', '多少', '怎样', '如何', '怎么', '能不能', '可以吗'];
  if (compareKeywords.some(kw => message.includes(kw))) return 'COMPARE';
  if (shoppingKeywords.some(kw => message.includes(kw)) || answer.includes('picks')) return 'SHOPPING';
  if (afterSalesKeywords.some(kw => message.includes(kw))) return 'AFTERSALES';
  // 如果消息很短且以问号结尾，可能是模糊询问，倾向 CLARIFY
  if (message.length < 20 && clarifyKeywords.some(kw => message.includes(kw))) return 'CLARIFY';
  return 'GENERAL';
}

// ─── 主处理 ───

Deno.serve(async (req) => {
  if (req.method === 'OPTIONS') {
    return new Response(null, { headers: corsHeaders });
  }

  const traceId = req.headers.get('x-trace-id') || crypto.randomUUID();

  try {
    const body: ChatRequest = await req.json();
    const { message, conversationId, localConversationId, inputs } = body;

    console.log('📥 [ai-chat-stream] request:', {
      trace_id: traceId,
      message: message?.substring(0, 80),
      catalogCount: inputs?.catalog?.length || 0,
      conversationId: conversationId || '(empty)',
      localConversationId: localConversationId || '(empty)',
      localConversationId_type: typeof localConversationId,
      inputs_user_id: inputs?.user_id || '(empty)',
      inputs_user_id_type: typeof inputs?.user_id,
    });

    const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
    const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
    const supabase = createClient(supabaseUrl, supabaseKey);

    // ─── 构建产品目录（合并前端传入 + 数据库查询）───
    const inputCatalog = Array.isArray(inputs.catalog) ? inputs.catalog : [];
    const { data: products } = await supabase
      .from('products')
      .select('id, name, price, stock_quantity, category, description, brand, is_hot, is_recommended')
      .eq('is_active', true)
      .order('is_hot', { ascending: false })
      .order('is_recommended', { ascending: false })
      .limit(500);
    const catalogById = new Map<string, any>();
    [...inputCatalog, ...(products || [])].forEach((product: any) => {
      if (product?.id) catalogById.set(product.id, product);
    });
    const catalog = Array.from(catalogById.values());

    // ─── 确定 LLM API 端点 ───
    let apiUrl: string;
    let apiKey: string;
    let model: string;

    if (USE_AI_HUB) {
      apiUrl = `${AI_HUB_BASE}/chat/completions`;
      apiKey = AI_HUB_KEY;
      model = AI_HUB_MODEL;
      console.log('🤖 [ai-chat-stream] Using AI Hub:', { base: AI_HUB_BASE, model });
    } else if (LOVABLE_KEY) {
      apiUrl = 'https://ai.gateway.lovable.dev/v1/chat/completions';
      apiKey = LOVABLE_KEY;
      model = 'google/gemini-2.5-flash';
      console.log('🤖 [ai-chat-stream] Using Lovable Gateway');
    } else {
      throw new Error('AI_HUB_API_KEY/AI_HUB_BASE_URL 和 LOVABLE_API_KEY 均未配置，无法调用大模型');
    }

    // ─── 加载对话历史 ───
    let historyMessages: HistoryMessage[] = [];
    const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
    const rawUserId = inputs.user_id;
    const userIdPassed = !!(rawUserId && uuidRegex.test(rawUserId));
    const userId = userIdPassed ? rawUserId : crypto.randomUUID();
    const localConvIdPassed = !!(localConversationId && uuidRegex.test(localConversationId));
    const localConvId = localConvIdPassed ? localConversationId! : crypto.randomUUID();

    console.log('🔑 [ai-chat-stream] ID resolution:', {
      rawUserId,
      userIdPassed,
      userId_used: userId,
      rawLocalConvId: localConversationId,
      localConvIdPassed,
      localConvId_used: localConvId,
    });

    if (localConvId && userId) {
      historyMessages = await loadConversationHistory(supabase, localConvId, userId, 20);
      historyMessages = truncateHistory(historyMessages, 6000);
      console.log('📜 [ai-chat-stream] 历史消息:', {
        loaded: historyMessages.length,
        totalChars: historyMessages.reduce((s, m) => s + m.content.length, 0),
        oldestRole: historyMessages[0]?.role || 'n/a',
        newestRole: historyMessages[historyMessages.length - 1]?.role || 'n/a',
      });
    }

    // ─── 构建系统提示词 ───
    const systemPrompt = buildSystemPrompt(catalog, inputs.user_context || '');

    // ─── 调用大模型流式 API ───
    console.log('🚀 [ai-chat-stream] Calling LLM API...');

    const llmResponse = await fetch(apiUrl, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        model,
        messages: [
          { role: 'system', content: systemPrompt },
          ...historyMessages,
          { role: 'user', content: message },
        ],
        stream: true,
        stream_options: { include_usage: true },
        temperature: 0.7,
        max_tokens: 4096,
      }),
    });

    if (!llmResponse.ok) {
      const errorText = await llmResponse.text();
      console.error('❌ [ai-chat-stream] LLM API error:', llmResponse.status, errorText);
      throw new Error(`LLM API error: ${llmResponse.status} - ${errorText.substring(0, 300)}`);
    }

    // ─── 流式转换：OpenAI SSE → 自定义 SSE ───
    const encoder = new TextEncoder();
    let fullAnswer = '';

    const stream = new ReadableStream({
      async start(controller) {
        try {
          const reader = llmResponse.body?.getReader();
          if (!reader) throw new Error('LLM response body is empty');

          const decoder = new TextDecoder();
          let buffer = '';
          let openaiDone = false;

          while (!openaiDone) {
            const { done, value } = await reader.read();
            if (done) break;

            buffer += decoder.decode(value, { stream: true });
            const lines = buffer.split('\n');
            buffer = lines.pop() || '';

            for (const line of lines) {
              const trimmed = line.trim();
              if (!trimmed) continue;

              // OpenAI SSE 格式: "data: {...}" 或 "data: [DONE]"
              if (!trimmed.startsWith('data:')) continue;

              const dataContent = trimmed.slice(5).trim(); // 去掉 "data:" 前缀
              if (dataContent === '[DONE]') {
                openaiDone = true;
                break;
              }
              if (!dataContent) continue; // 跳过空的 keepalive 行

              try {
                const parsed = JSON.parse(dataContent);
                const delta = parsed?.choices?.[0]?.delta;

                if (delta?.content) {
                  fullAnswer += delta.content;
                  const sseData = JSON.stringify({ type: 'text', content: delta.content });
                  controller.enqueue(encoder.encode(`data: ${sseData}\n\n`));
                }
              } catch {
                // 非 JSON 行，跳过（如注释行、空 data 等）
              }
            }
          }

          // ─── 流结束，后处理 ───
          // 如果 LLM 没有返回内容，发送友好提示
          if (!fullAnswer) {
            fullAnswer = '抱歉，AI助手暂时无法回复，请稍后再试。';
            const sseData = JSON.stringify({ type: 'text', content: fullAnswer });
            controller.enqueue(encoder.encode(`data: ${sseData}\n\n`));
          }

          console.log('📝 [ai-chat-stream] Full answer length:', fullAnswer.length);

          // 优先尝试解析 LLM 直接返回的结构化 JSON
          const structured = parseStructuredPayload(fullAnswer);

          let picks: Array<{ product_id: string; note: string }> = [];
          let intent = 'GENERAL';
          let highlights: string[] = [];
          let comparison: string | undefined;
          let next: string[] | undefined;
          let title: string | undefined;
          let clarifies: string[] | undefined;

          if (structured) {
            console.log('✅ [ai-chat-stream] Using structured payload from LLM');
            // 校验 picks 中的 product_id 必须存在于 catalog（防幻觉）
            const rawPicks = Array.isArray(structured.picks) ? structured.picks : [];
            const catalogIdSet = new Set(catalog.map((c: any) => c?.id).filter(Boolean));
            const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

            type PickAudit = { product_id: string | null; note: string | null; accepted: boolean; reasons: string[] };
            const pickAudit: PickAudit[] = rawPicks.map((p: any) => {
              const id = typeof p?.product_id === 'string' ? p.product_id.trim() : '';
              const reasons: string[] = [];
              if (!id) reasons.push('missing_product_id');
              else if (!uuidRegex.test(id)) reasons.push('invalid_uuid_format');
              else if (!catalogIdSet.has(id)) reasons.push('not_in_catalog');
              return { product_id: id || null, note: p?.note ?? null, accepted: reasons.length === 0, reasons };
            });

            const accepted = pickAudit.filter(a => a.accepted);
            const rejected = pickAudit.filter(a => !a.accepted);
            console.log(`📊 [ai-chat-stream] Pick audit — total:${pickAudit.length} accepted:${accepted.length} rejected:${rejected.length} catalog_size:${catalogIdSet.size}`);
            if (rejected.length > 0) {
              console.log('🚫 [ai-chat-stream] Rejected picks:', JSON.stringify(rejected, null, 2));
              rejected.forEach(r => {
                if (r.product_id && r.reasons.includes('not_in_catalog')) {
                  const prefix = r.product_id.slice(0, 8);
                  const prefixHits = catalog
                    .filter((c: any) => typeof c?.id === 'string' && c.id.startsWith(prefix))
                    .map((c: any) => ({ id: c.id, name: c.name }));
                  console.log(`   ↳ ${r.product_id} not in catalog. Same-prefix(${prefix}) entries in catalog:`, JSON.stringify(prefixHits));
                }
              });
            }

            picks = accepted.map(a => ({ product_id: a.product_id as string, note: a.note || '' }));
            intent = structured.intent || detectIntent(message, fullAnswer);
            highlights = Array.isArray(structured.highlights) ? structured.highlights : [];
            comparison = typeof structured.comparison === 'string' ? structured.comparison : undefined;
            next = Array.isArray(structured.next) ? structured.next : undefined;
            title = typeof structured.title === 'string' ? structured.title : undefined;
            clarifies = Array.isArray(structured.clarifies) ? structured.clarifies : undefined;
          } else {
            console.log('⚠️ [ai-chat-stream] No structured payload, falling back to text parsing');
            picks = parsePicksFromText(fullAnswer, catalog);
            intent = detectIntent(message, fullAnswer);
            highlights = fullAnswer.split(/[。！？\n]/).filter((s: string) => s.length > 10 && s.length < 100).slice(0, 3);
          }

          console.log('🔍 [ai-chat-stream] Parsed picks:', JSON.stringify(picks));
          const mentionedIds = catalog
            .filter(c => fullAnswer.includes(c.id))
            .map(c => ({ id: c.id, name: c.name }));
          console.log('🔎 [ai-chat-stream] Product IDs mentioned in text:', JSON.stringify(mentionedIds));

          // ─── 保存消息到数据库（服务端写入，保障多轮记忆）───
          if (localConvId && userId) {
            const baseTime = new Date();
            const userTs = baseTime.toISOString();
            const aiTs = new Date(baseTime.getTime() + 1).toISOString(); // +1ms 保证顺序

            console.log('💾 [ai-chat-stream] 准备写入 DB:', {
              conversation_id: localConvId,
              user_id: userId,
              user_msg_len: message.length,
              ai_msg_len: fullAnswer.length,
            });

            // 确保 conversations 表有对应行（访客场景下前端只写 localStorage，FK 约束会拒绝 messages 写入）
            const { error: upsertConvError } = await supabase.from('conversations').upsert({
              id: localConvId,
              user_id: userId,
            }, { onConflict: 'id' });
            if (upsertConvError) {
              console.error('❌ [ai-chat-stream] upsert conversations 失败:', JSON.stringify(upsertConvError));
            } else {
              console.log('✅ [ai-chat-stream] conversations 已 upsert');
            }

            // 保存用户消息
            const { error: saveUserError } = await supabase.from('messages').insert({
              conversation_id: localConvId,
              user_id: userId,
              content: message,
              sender_type: 'user',
              message_type: 'text',
              created_at: userTs,
            });
            if (saveUserError) {
              console.error('❌ [ai-chat-stream] 保存用户消息失败:', JSON.stringify(saveUserError));
            } else {
              console.log('✅ [ai-chat-stream] 用户消息已保存');
            }

            // 保存 AI 回复
            const { error: saveAiError } = await supabase.from('messages').insert({
              conversation_id: localConvId,
              user_id: userId,
              content: fullAnswer,
              sender_type: 'ai',
              message_type: 'text',
              metadata: { picks, intent, highlights, title },
              created_at: aiTs,
            });
            if (saveAiError) {
              console.error('❌ [ai-chat-stream] 保存 AI 回复失败:', JSON.stringify(saveAiError));
            } else {
              console.log('✅ [ai-chat-stream] AI 回复已保存');
            }
          } else {
            console.error('❌ [ai-chat-stream] 跳过 DB 写入 — localConvId 或 userId 为空:', { localConvId, userId });
          }

          // ─── 发送最终元数据 ───
          const finalData = JSON.stringify({
            type: 'done',
            intent,
            picks,
            highlights,
            ...(comparison ? { comparison } : {}),
            ...(next ? { next } : {}),
            ...(title ? { title } : {}),
            ...(clarifies ? { clarifies } : {}),
            meta: {
              platform: 'ai-hub',
              user: userId,
              catalog_count: catalog.length,
              probe_id: traceId,
              source: structured ? 'llm-structured' : 'text-fallback',
              hasHistory: historyMessages.length > 0,
              historyMessages: historyMessages.length,
            },
            conversation_id: localConvId,
          });

          controller.enqueue(encoder.encode(`data: ${finalData}\n\n`));
          controller.enqueue(encoder.encode('data: [DONE]\n\n'));
          controller.close();

          console.log('✅ [ai-chat-stream] Stream completed:', {
            answerLength: fullAnswer.length,
            picksCount: picks.length,
            intent,
            hasComparison: !!comparison,
            conversation_id: localConvId,
          });
        } catch (error) {
          console.error('❌ [ai-chat-stream] Stream error:', error);
          const errorData = JSON.stringify({
            type: 'error',
            message: (error instanceof Error ? error.message : '流式响应失败'),
          });
          controller.enqueue(encoder.encode(`data: ${errorData}\n\n`));
          controller.close();
        }
      },
    });

    return new Response(stream, {
      headers: {
        ...corsHeaders,
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'X-Trace-Id': traceId,
      },
    });
  } catch (error) {
    console.error('❌ [ai-chat-stream] Fatal error:', error);
    return new Response(
      JSON.stringify({ error: (error instanceof Error ? error.message : 'Unknown error') }),
      {
        status: 500,
        headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      }
    );
  }
});
