/**
 * ChatArea - 聊天区域组件
 */

import { state, KEYS } from '../state/state.js';
import { logError, logSession } from '../services/log-service.js';
import { marked } from '../utils/marked-config.js';
import { escapeHtml, formatTime } from '../utils/utils.js';
import { estimateTokens } from '../services/context-manager.js';
import { getToolIcon, formatToolName } from '../services/tool-registry.js';
import type { ChatSession, ChatMessage, ToolCallRecord } from '../types.js';

function getFileIcon(filename: string): string {
  const name = filename.toLowerCase();
  if (name === 'dockerfile') return '🐳';
  if (name === 'makefile' || name === 'gnumakefile') return '🔨';
  const ext = filename.split('.').pop()?.toLowerCase() || '';
  const map: Record<string, string> = {
    py: '🐍', js: '💛', ts: '🔷', tsx: '⚛️', jsx: '⚛️',
    java: '☕', go: '🐹', rs: '🦀', rb: '💎', php: '🐘',
    cpp: '⚙️', c: '⚙️', sh: '🐚', html: '🌐', css: '🎨',
    json: '📋', yaml: '📋', yml: '📋', md: '📝', txt: '📄',
    sql: '🗃️', xml: '📰', csv: '📊', diff: '🔀',
  };
  return map[ext] || '📄';
}

let chatAreaEl: HTMLElement;
let messagesContainerEl: HTMLElement;
let emptyStateEl: HTMLElement;
let scrollBtnEl: HTMLElement;
let autoScroll = true;
let currentPlaceholder: HTMLElement | null = null;

/** 渲染可折叠系统提示词卡片 */
let _sysPromptIdCounter = 0;
function renderSystemPromptCard(sysPrompt: string): string {
  const id = `sysprompt_${++_sysPromptIdCounter}`;
  const tokenEstimate = estimateTokens(sysPrompt);
  const preview = sysPrompt.length > 120 ? sysPrompt.slice(0, 120).replace(/\n/g, ' ') + '…' : sysPrompt.replace(/\n/g, ' ');
  return `<div class="system-prompt-card">
    <div class="system-prompt-header" onclick="
      var el=document.getElementById('${id}');
      var chev=this.querySelector('.sp-chevron');
      el.style.display=el.style.display==='none'?'':'none';
      chev.classList.toggle('sp-expanded');
    ">
      <span class="sp-icon">📋</span>
      <span class="sp-title">系统提示词</span>
      <span class="sp-tokens">~${tokenEstimate} tokens</span>
      <span class="sp-preview">${escapeHtml(preview)}</span>
      <svg class="sp-chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
    </div>
    <div class="system-prompt-body" id="${id}" style="display:none;">
      <pre>${escapeHtml(sysPrompt)}</pre>
    </div>
  </div>`;
}

export function initChatArea(): void {
  chatAreaEl = document.querySelector('#chatArea')!;
  messagesContainerEl = document.querySelector('#messagesContainer')!;
  emptyStateEl = document.querySelector('#emptyState')!;
  scrollBtnEl = document.querySelector('#scrollToBottom')!;

  chatAreaEl.addEventListener('scroll', () => {
    const atBottom = isAtBottom();
    if (atBottom) {
      autoScroll = true;
      scrollBtnEl.style.display = 'none';
    } else {
      autoScroll = false;
      scrollBtnEl.style.display = '';
    }
  });

  scrollBtnEl.addEventListener('click', () => {
    autoScroll = true;
    scrollBtnEl.style.display = 'none';
    chatAreaEl.scrollTo({ top: chatAreaEl.scrollHeight, behavior: 'smooth' });
  });
}

function isAtBottom(): boolean {
  return chatAreaEl.scrollHeight - chatAreaEl.scrollTop - chatAreaEl.clientHeight < 60;
}

export function safeMarkdown(text: unknown): string {
  if (!text && text !== 0) return '';
  const str = typeof text === 'string' ? text : String(text);
  if (!str) return '';
  try {
    return marked(str);
  } catch {
    return escapeHtml(str);
  }
}

export function scrollToBottom(): void {
  if (!autoScroll) return;
  requestAnimationFrame(() => {
    chatAreaEl.scrollTop = chatAreaEl.scrollHeight;
  });
}

/** 发送新消息时重置自动滚动，确保新消息可见 */
export function resetAutoScroll(): void {
  autoScroll = true;
  if (scrollBtnEl) scrollBtnEl.style.display = 'none';
}

/** P1-1: 已渲染消息索引集合，用于稳定增量 diff（替代 DOM querySelectorAll 计数） */
const _renderedMsgIndices = new Set<number>();

/** 标记当前渲染批次中系统提示词卡片是否已渲染（每会话仅首条 assistant 消息展示） */
let _sysPromptRendered = false;

export function renderMessages(): void {
  const currentSession = state.get<ChatSession | null>(KEYS.CURRENT_SESSION);
  const msgs = currentSession ? currentSession.messages : [];

  // 全量清空重建，避免增量 diff 的边界 bug
  messagesContainerEl.innerHTML = '';
  currentPlaceholder = null;
  _renderedMsgIndices.clear();
  _sysPromptRendered = false; // 重置：每轮全量渲染时重新判定首条 assistant

  if (msgs.length === 0) {
    emptyStateEl.style.display = '';
    messagesContainerEl.style.display = 'none';
    return;
  }

  emptyStateEl.style.display = 'none';
  messagesContainerEl.style.display = '';

  for (let i = 0; i < msgs.length; i++) {
    const msg = msgs[i];
    // 只跳过既无内容也无 think 也无工具的空消息
    if (msg.role === 'assistant' && !msg.content && !msg.think && (!msg.toolCalls || msg.toolCalls.length === 0)) continue;
    appendMessageDOM(msgs[i], i);
    _renderedMsgIndices.add(i);
  }

  scrollToBottom();
}

export function appendMessageDOM(msg: ChatMessage, index: number): void {
  const div = document.createElement('div');
  div.className = `message ${msg.role}`;
  div.dataset.index = String(index);

  const avatar = msg.role === 'user' ? '👤' : '🤖';
  const roleLabel = msg.role === 'user' ? '你' : 'AI';
  const modelTag = (msg.role === 'assistant' && msg.model)
    ? `<span class="model-tag">${escapeHtml(msg.model)}</span>`
    : '';

  let contentHtml = '';
  let safeContent = (msg.content != null) ? String(msg.content) : '';

  if (msg.role === 'assistant') {
    // ── 系统提示词折叠卡片（仅会话首条 assistant 消息展示，避免重复）──
    if (!_sysPromptRendered) {
      const sysPrompt = state.get<string>('_lastSystemPrompt', '');
      if (sysPrompt) {
        contentHtml += renderSystemPromptCard(sysPrompt);
      }
      _sysPromptRendered = true;
    }

    if (msg.think) {
      contentHtml += `
        <div class="think-block">
          <div class="think-header" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? '' : 'none'; this.querySelector('.chevron').classList.toggle('expanded');">
            <span>🤔 思考过程</span>
            <svg class="chevron expanded" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
          </div>
          <div class="think-content"><pre>${escapeHtml(msg.think)}</pre></div>
        </div>`;
    }
    if (safeContent) {
      contentHtml += `<div class="msg-content">${safeMarkdown(safeContent)}</div>`;
    } else {
      contentHtml += `<div class="msg-content"></div>`;
    }
  } else {
    contentHtml += `<div class="msg-content">${escapeHtml(safeContent)}</div>`;
  }

  if (msg.images && msg.images.length > 0) {
    contentHtml += '<div class="msg-images">';
    for (const img of msg.images) {
      contentHtml += `<img class="msg-img" src="data:image/png;base64,${img}" alt="用户图片" data-lightbox="true">`;
    }
    contentHtml += '</div>';
  }

  if ((msg as any)._videos && (msg as any)._videos.length > 0) {
    for (const v of (msg as any)._videos) {
      contentHtml += `<div class="msg-video-indicator">🎬 ${v.fileName} · ${v.frameCount} 帧 · 1fps</div>`;
    }
  } else if ((msg as any)._videoFrames && (msg as any)._videoFrames.length > 0) {
    const n = (msg as any)._videoFrames.length;
    contentHtml += `<div class="msg-video-indicator">🎬 ${n} 帧 · 1fps</div>`;
  }

  if (msg.files && msg.files.length > 0) {
    contentHtml += '<div class="msg-files">';
    for (const f of msg.files) {
      const icon = getFileIcon(f.name);
      contentHtml += `<div class="file-chip"><span class="file-icon">${icon}</span><span class="file-name">${escapeHtml(f.name)}</span></div>`;
    }
    contentHtml += '</div>';
  }

  if (msg.role === 'user' && (!msg.content || msg.content.trim() === '') && (msg.images?.length || 0) > 0) {
    contentHtml = contentHtml.replace('<div class="msg-content"></div>', '');
  }

  // ── 工具调用历史记录（可折叠）──
  if (msg.role === 'assistant' && msg.toolCalls && msg.toolCalls.length > 0) {
    const tcCount = msg.toolCalls.length;
    const successCount = msg.toolCalls.filter(t => t.status === 'success').length;
    const errorCount = msg.toolCalls.filter(t => t.status === 'error').length;
    const summary = msg.toolCalls.map(t => {
      const names: Record<string, string> = {
        read_file: 'read_file', write_file: 'write_file', list_directory: 'list_directory',
        search_files: 'search_files', run_command: 'run_command', web_fetch: 'web_fetch',
        web_search: 'web_search', git: 'git', browser_open: 'browser_open',
        memory_search: 'memory_search', memory_add: 'memory_add', spawn_task: 'spawn_task',
      };
      return names[t.name] || t.name;
    }).slice(0, 5).join(', ') + (tcCount > 5 ? ` +${tcCount - 5}` : '');

    contentHtml += `<div class="tool-calls-history">
      <div class="tool-calls-toggle" onclick="this.parentElement.classList.toggle('expanded')">
        <span class="tool-calls-chevron">▸</span>
        <span class="tool-calls-summary">🔧 ${tcCount} 个工具调用（✅${successCount} ❌${errorCount}）</span>
      </div>
      <div class="tool-calls-list">
        ${msg.toolCalls.map(tc => renderToolCallCard(tc)).join('')}
      </div>
    </div>`;
  }

  if (msg.timestamp) {
    contentHtml += `<div class="msg-stats"><span>${formatTime(msg.timestamp)}</span></div>`;
  }

  div.innerHTML = `
    <div class="msg-avatar">${avatar}</div>
    <div class="msg-body">
      <div class="msg-role">${roleLabel}${modelTag}</div>
      ${contentHtml}
    </div>
  `;

  messagesContainerEl.appendChild(div);
}

function renderToolCallCard(tc: ToolCallRecord): string {
  const statusLabels: Record<string, string> = {
    pending: '📝 准备中…', running: '🔄 执行中', success: '✅ 完成', error: '❌ 失败', cancelled: '🚫 已取消'
  };

  const icon = getToolIcon(tc.name);
  const name = formatToolName(tc.name);
  const status = statusLabels[tc.status] || tc.status;
  const args = tc.arguments || {};

  let paramsHtml = '';
  if (args.path) paramsHtml += `<div class="tool-param">路径: <code>${escapeHtml(String(args.path))}</code></div>`;
  if (args.command) paramsHtml += `<div class="tool-param">命令: <code>${escapeHtml(String(args.command))}</code></div>`;
  if (args.query) paramsHtml += `<div class="tool-param">查询: <code>${escapeHtml(String(args.query))}</code></div>`;
  if (args.url) paramsHtml += `<div class="tool-param">URL: <code>${escapeHtml(String(args.url))}</code></div>`;
  if (args.source) paramsHtml += `<div class="tool-param">源: <code>${escapeHtml(String(args.source))}</code></div>`;
  if (args.destination) paramsHtml += `<div class="tool-param">目标: <code>${escapeHtml(String(args.destination))}</code></div>`;
  if (args.selector) paramsHtml += `<div class="tool-param">选择器: <code>${escapeHtml(String(args.selector))}</code></div>`;
  if (args.action) paramsHtml += `<div class="tool-param">操作: <code>${escapeHtml(String(args.action))}</code></div>`;

  // 失败/取消时显示错误信息（成功结果在工作空间工具页签查看）
  let errorHtml = '';
  if (tc.status === 'error' && tc.result?.error) {
    errorHtml = `<div class="tool-call-error">❌ ${escapeHtml(String(tc.result.error))}</div>`;
  } else if (tc.status === 'cancelled') {
    errorHtml = `<div class="tool-call-error">🚫 用户取消了操作</div>`;
  }

  return `<div class="tool-call-card tool-call-${tc.status}">
    <div class="tool-call-header">
      <span class="tool-call-icon">${icon}</span>
      <span class="tool-call-name">${name}</span>
      <span class="tool-call-status">${status}</span>
    </div>
    ${paramsHtml ? `<div class="tool-call-params">${paramsHtml}</div>` : ''}
    ${errorHtml}
  </div>`;
}

export function updateLastAssistantMessage(
  content: string, think: string | null, _stats: unknown, model?: string,
  timestamp?: number, toolCalls?: ToolCallRecord[]
): void {
  const lastMsg = currentPlaceholder;
  if (!lastMsg) return;

  const isFinal = toolCalls !== undefined || timestamp !== undefined;

  if (lastMsg.classList.contains('loading')) {
    lastMsg.classList.remove('loading');
    const loadingDots = lastMsg.querySelector('.loading-dots');
    const loadingText = lastMsg.querySelector('.loading-text');
    if (loadingDots) loadingDots.remove();
    if (loadingText) loadingText.remove();

    // 流式消息首次转正：注入系统提示词折叠卡片（仅当会话中无前序 assistant 消息时）
    const sysPrompt = state.get<string>('_lastSystemPrompt', '');
    if (sysPrompt && !lastMsg.querySelector('.system-prompt-card')) {
      const session = state.get<ChatSession | null>(KEYS.CURRENT_SESSION);
      const hasPrevAssistant = session?.messages.some(m => m.role === 'assistant') ?? false;
      if (!hasPrevAssistant) {
        const msgBody = lastMsg.querySelector('.msg-body');
        const tempDiv = document.createElement('div');
        tempDiv.innerHTML = renderSystemPromptCard(sysPrompt);
        const card = tempDiv.firstElementChild!;
        const thinkBlock = msgBody?.querySelector('.think-block');
        if (thinkBlock) {
          msgBody!.insertBefore(card, thinkBlock);
        } else {
          msgBody!.insertBefore(card, msgBody!.firstChild);
        }
      }
    }
  }

  if (model) {
    let modelTag = lastMsg.querySelector('.model-tag');
    if (!modelTag) {
      modelTag = document.createElement('span');
      modelTag.className = 'model-tag';
      const roleEl = lastMsg.querySelector('.msg-role');
      if (roleEl) roleEl.appendChild(modelTag);
    }
    modelTag.textContent = model;
  }

  const contentDiv = lastMsg.querySelector('.msg-content');
  const safeContent = (content != null) ? String(content) : '';
  if (contentDiv && safeContent) {
    // P0-1: 流式期间纯文本追加（避免每 chunk 全量 Markdown + DOMPurify），最终渲染时才做 Markdown
    if (isFinal) {
      contentDiv.innerHTML = safeMarkdown(safeContent);
    } else {
      contentDiv.textContent = safeContent;
    }
  }

  let thinkBlock = lastMsg.querySelector('.think-block');
  if (think) {
    if (!thinkBlock) {
      const msgBody = lastMsg.querySelector('.msg-body')!;
      thinkBlock = document.createElement('div');
      thinkBlock.className = 'think-block';
      thinkBlock.innerHTML = `
        <div class="think-header" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? '' : 'none'; this.querySelector('.chevron').classList.toggle('expanded');">
          <span>🤔 思考过程</span>
          <svg class="chevron expanded" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
        </div>
        <div class="think-content"><pre></pre></div>
      `;
      msgBody.insertBefore(thinkBlock, contentDiv);
    }
    thinkBlock.querySelector('pre')!.textContent = think;
  }

  // ── 工具调用历史记录（可折叠）──
  if (toolCalls && toolCalls.length > 0) {
    let existingHistory = lastMsg.querySelector('.tool-calls-history');
    if (existingHistory) existingHistory.remove();

    const tcCount = toolCalls.length;
    const successCount = toolCalls.filter(t => t.status === 'success').length;
    const errorCount = toolCalls.filter(t => t.status === 'error').length;

    const historyDiv = document.createElement('div');
    historyDiv.className = 'tool-calls-history';
    historyDiv.innerHTML = `
      <div class="tool-calls-toggle" onclick="this.parentElement.classList.toggle('expanded')">
        <span class="tool-calls-chevron">▸</span>
        <span class="tool-calls-summary">🔧 ${tcCount} 个工具调用（✅${successCount} ❌${errorCount}）</span>
      </div>
      <div class="tool-calls-list">
        ${toolCalls.map(tc => renderToolCallCard(tc)).join('')}
      </div>
    `;
    const msgBody = lastMsg.querySelector('.msg-body');
    if (msgBody) {
      const refNode = contentDiv?.nextSibling || null;
      msgBody.insertBefore(historyDiv, refNode);
    }
  }

  // ── 时间戳 ──
  if (timestamp) {
    let statsDiv = lastMsg.querySelector('.msg-stats');
    if (!statsDiv) {
      statsDiv = document.createElement('div');
      statsDiv.className = 'msg-stats';
      const msgBody = lastMsg.querySelector('.msg-body');
      if (msgBody) msgBody.appendChild(statsDiv);
    }
    statsDiv.innerHTML = `<span>${formatTime(timestamp)}</span>`;
  }

  scrollToBottom();
}

/** 实时追加工具调用卡片到当前 placeholder（直播聊天时使用） */
export function appendToolCallCardToPlaceholder(tc: ToolCallRecord): void {
  if (!currentPlaceholder) return;
  let container = currentPlaceholder.querySelector('.tool-calls-container');
  if (!container) {
    container = document.createElement('div');
    container.className = 'tool-calls-container';
    const msgBody = currentPlaceholder.querySelector('.msg-body');
    if (msgBody) msgBody.appendChild(container);
  }
  const cardDiv = document.createElement('div');
  cardDiv.innerHTML = renderToolCallCard(tc);
  const card = cardDiv.firstElementChild as HTMLElement;
  card.dataset.toolName = tc.name;
  container.appendChild(card);
  scrollToBottom();
}

/** 更新 placeholder 中已有的工具卡片状态（running → success/error） */
export function updateToolCallCardInPlaceholder(tc: ToolCallRecord): void {
  if (!currentPlaceholder) return;
  const container = currentPlaceholder.querySelector('.tool-calls-container');
  if (!container) return;
  // 找到第一个同名且状态为 running 的卡片（CSS.escape 防特殊字符）
  const escapedName = CSS.escape(tc.name);
  const existing = container.querySelector(`.tool-call-card[data-tool-name="${escapedName}"].tool-call-running`) as HTMLElement;
  if (existing) {
    const cardDiv = document.createElement('div');
    cardDiv.innerHTML = renderToolCallCard(tc);
    const newCard = cardDiv.firstElementChild as HTMLElement;
    newCard.dataset.toolName = tc.name;
    existing.replaceWith(newCard);
  } else {
    // 没找到 running 的卡片，直接追加
    appendToolCallCardToPlaceholder(tc);
  }
}

export function appendAssistantPlaceholder(): void {
  const div = document.createElement('div');
  div.className = 'message assistant loading';
  div.innerHTML = `
    <div class="msg-avatar">🤖</div>
    <div class="msg-body">
      <div class="msg-role">AI</div>
      <div class="msg-content">
        <div class="loading-dots"><span></span><span></span><span></span></div>
        <span class="loading-text">正在思考...</span>
      </div>
    </div>
  `;
  messagesContainerEl.appendChild(div);
  currentPlaceholder = div;
  scrollToBottom();
}

export function appendSystemMessage(text: string, tempClass?: string): void {
  const div = document.createElement('div');
  div.style.cssText = 'text-align:center;padding:8px;font-size:12px;color:var(--text-muted);';
  if (tempClass) div.classList.add(tempClass);
  div.textContent = text;
  emptyStateEl.style.display = 'none';
  messagesContainerEl.style.display = '';
  messagesContainerEl.appendChild(div);
  scrollToBottom();
}

/** v4.1: 清除所有消息 DOM 元素（用于 undo/compress 后强制重建） */
export function clearMessagesDOM(): void {
  messagesContainerEl.innerHTML = '';
  _renderedMsgIndices.clear(); // P1-1: 重置 diff 状态
}

export function clearMessages(): void {
  messagesContainerEl.innerHTML = '';
  currentPlaceholder = null;
  _renderedMsgIndices.clear(); // P1-1: 重置 diff 状态
}

/** 清理 currentPlaceholder 引用（当 placeholder 从外部被移除时调用） */
export function resetCurrentPlaceholder(): void {
  if (currentPlaceholder && !currentPlaceholder.isConnected) {
    currentPlaceholder = null;
  }
}

/** 从 DOM 移除当前 placeholder 并清空引用 */
export function removeCurrentPlaceholder(): void {
  if (currentPlaceholder) {
    currentPlaceholder.remove();
    currentPlaceholder = null;
  }
}

export function enableAutoScroll(): void {
  autoScroll = true;
  if (scrollBtnEl) scrollBtnEl.style.display = 'none';
}

// ── 导出功能 ──

async function nativeSaveFile(defaultName: string, content: string): Promise<void> {
  const bridge = window.metonaDesktop;
  if (bridge) {
    const ext = defaultName.split('.').pop() || 'txt';
    const filePath = await bridge.dialog.saveFile({
      defaultPath: defaultName,
      filters: [{ name: ext.toUpperCase(), extensions: [ext] }]
    });
    if (!filePath) return;
    const result = await bridge.fs.writeFile(filePath, content, 'utf-8');
    if (!result.success) {
      logError('导出失败', result.error);
    }
  } else {
    // 回退
    const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = defaultName;
    a.click();
  }
}

export async function exportAsMarkdown(session: ChatSession): Promise<void> {
  let md = `# ${session.title}\n\n**模型:** ${session.model}  \n**时间:** ${formatTime(session.createdAt)}\n\n---\n\n`;
  session.messages.forEach(m => {
    const role = m.role === 'user' ? '👤 用户' : '🤖 AI';
    md += `### ${role}\n\n${m.content || ''}\n\n`;
    if (m.files && m.files.length > 0) {
      md += m.files.map(f => `📎 \`${f.name}\``).join(' · ') + '\n\n';
    }
    if (m.think) md += `> **思考:** ${m.think}\n\n`;
  });
  await nativeSaveFile(`${session.title}.md`, md);
  logSession('导出', `${session.title}.md`);
}

export async function exportAsHtml(session: ChatSession): Promise<void> {
  let html = `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>${escapeHtml(session.title)}</title>
<style>body{max-width:800px;margin:0 auto;padding:20px;font-family:system-ui;line-height:1.6;background:#FAF7F2;color:#2D2016;}
.user{background:#F5F0E8;padding:12px;border-radius:12px;margin:8px 0;border:1px solid rgba(0,0,0,0.06);}
.assistant{background:#fff;padding:12px;border-radius:12px;margin:8px 0;border:1px solid rgba(0,0,0,0.06);box-shadow:0 1px 4px rgba(45,32,22,0.04);}
pre{background:#2D2016;color:#F5F0E8;padding:14px;border-radius:12px;overflow-x:auto;}
code{background:#F5F0E8;color:#E8734A;padding:2px 6px;border-radius:4px;}</style></head><body>
<h1>${escapeHtml(session.title)}</h1><p>${formatTime(session.createdAt)} · ${session.model}</p><hr>`;
  session.messages.forEach(m => {
    html += `<div class="${m.role}"><strong>${m.role === 'user' ? '👤 用户' : '🤖 AI'}</strong><br>${(m.content || '').replace(/\n/g, '<br>')}</div>`;
  });
  html += '</body></html>';
  await nativeSaveFile(`${session.title}.html`, html);
  logSession('导出', `${session.title}.html`);
}

export async function exportAsTxt(session: ChatSession): Promise<void> {
  let txt = `${session.title}\n模型: ${session.model}\n时间: ${formatTime(session.createdAt)}\n${'='.repeat(50)}\n\n`;
  session.messages.forEach(m => {
    txt += `[${m.role === 'user' ? '用户' : 'AI'}]\n${m.content || ''}\n\n`;
  });
  await nativeSaveFile(`${session.title}.txt`, txt);
  logSession('导出', `${session.title}.txt`);
}
