// chatbot-ui.js

/**
 * 全局函数，强制聊天机器人界面弹出（或切换到）模型选择器。
 *
 * 主要逻辑：
 * 1. 设置 `window.isModelSelectorOpen = true`。
 * 2. 调用 `ChatbotUI.updateChatbotUI()` 刷新界面以显示模型选择器。
 */
window.showModelSelectorForChatbot = function() {
  window.isModelSelectorOpen = true;
  if (typeof window.ChatbotUI === 'object' && typeof window.ChatbotUI.updateChatbotUI === 'function') {
    window.ChatbotUI.updateChatbotUI();
  }
};

// 全局状态变量
window.isChatbotPositionedLeft = localStorage.getItem('chatbotPosition') === 'left' || false;
window.isPresetQuestionsCollapsed = false; // 预设问题默认展开
window.presetAutoCollapseTriggeredForDoc = {}; // 记录文档是否已触发自动收起

// 全屏和宽度管理状态
window.isChatbotFullscreen = localStorage.getItem('chatbotFullscreen') === 'true' || false;
window.forceChatbotWidthReset = false; // 是否强制重置聊天窗口宽度
window.lastIsChunkCompareActive = undefined; // 上一次 Chunk Compare 标签页的激活状态
window.chatbotInitialLoad = true; // 是否为首次加载

// 浮动模式状态
window.isChatbotFloating = localStorage.getItem('chatbotFloating') === 'true' || false;
window.chatbotFloatingPosition = JSON.parse(localStorage.getItem('chatbotFloatingPosition') || '{"x": 100, "y": 100}');
window.chatbotFloatingSize = JSON.parse(localStorage.getItem('chatbotFloatingSize') || '{"width": 420, "height": 580}');

// 高级聊天功能选项
window.chatbotActiveOptions = {
  useContext: true, // 是否使用上下文
  useReActMode: false, // 是否启用ReAct框架（推理+工具调用）
  enableSemanticFeatures: true, // 是否启用意群和向量搜索功能（默认开启）
  multiHopRetrieval: false, // 是否启用多轮取材（先选片段再回答）
  contentLengthStrategy: 'default', // 内容长度策略: 'default', 'segmented'
  summarySource: 'ocr',   // 总结来源: 'ocr', 'none', 'translation'
  interestPointsActive: false,    // 兴趣点功能 (占位)
  memoryManagementActive: false   // 记忆管理功能 (占位)
};

/**
 * 处理暂停对话按钮的点击事件。
 *
 * 主要逻辑：
 * 1. 调用中止控制器来停止正在进行的请求。
 * 2. 更新UI状态。
 */
function handleChatbotStop() {
  if (window.chatbotAbortController) {
    window.chatbotAbortController.abort();
    console.log('[Chatbot] 用户中止了对话');
  }
}

/**
 * 处理聊天机器人发送按钮的点击事件。
 *
 * 主要逻辑：
 * 1. 获取输入框内容和已选图片。
 * 2. 如果文本和图片均为空，则不发送。
 * 3. 构造消息内容 (支持文本和图片混合)。
 * 4. 若使用 PromptConstructor，则增强用户输入。
 * 5. 清空输入框和已选图片预览。
 * 6. 调用 `ChatbotCore.sendChatbotMessage` 发送消息。
 */
function handleChatbotSend() {
  const input = document.getElementById('chatbot-input');
  if (!input) return;
  let val = input.value.trim();

  const selectedImages = window.ChatbotImageUtils.selectedChatbotImages || [];

  if (!val && selectedImages.length === 0) return;

  let messageContent = [];
  let displayMessageContent = []; // 用于UI显示，可能包含缩略图

  if (val) {
    messageContent.push({ type: 'text', text: val });
    displayMessageContent.push({ type: 'text', text: val });
  }

  selectedImages.forEach(img => {
    if (img.fullBase64) {
      messageContent.push({
        type: 'image_url',
        image_url: { url: img.fullBase64 }
      });
      displayMessageContent.push({
        type: 'image_url',
        image_url: {
          url: img.thumbnailBase64 || img.fullBase64, // 优先用缩略图显示
          fullUrl: img.fullBase64, // 点击放大用原图
          originalSrc: img.originalSrc
        }
      });
    }
  });

  // 兼容旧的单模态模型，如果只有一个文本部分，则直接发送文本字符串
  let sendVal = messageContent.length === 1 && messageContent[0].type === 'text' ? messageContent[0].text : messageContent;
  let displayVal = displayMessageContent.length === 1 && displayMessageContent[0].type === 'text' ? displayMessageContent[0].text : displayMessageContent;

  if (window.PromptConstructor && typeof window.PromptConstructor.enhanceUserPrompt === 'function') {
    sendVal = window.PromptConstructor.enhanceUserPrompt(sendVal);
  }

  input.value = '';
  window.ChatbotImageUtils.selectedChatbotImages = [];
  window.ChatbotImageUtils.updateSelectedImagesPreview();

  window.ChatbotCore.sendChatbotMessage(sendVal, updateChatbotUI, null, displayVal);
}

/**
 * 处理预设问题的点击事件 (UI层面)。
 *
 * 主要逻辑：
 * 1. 将预设问题填充到输入框。
 * 2. 调用 `handleChatbotSend` 发送。
 *
 * @param {string} q - 预设问题文本。
 */
// handlePresetQuestion 已在 ChatbotPreset 中定义（包含 Mermaid 和其他 prompt 注入逻辑）
// 不再在此重复定义，避免覆盖

/**
 * 更新聊天机器人界面的核心函数。
 *
 * 主要逻辑：
 * 1. **显隐控制**：根据 `isChatbotOpen` 控制 modal 和 fab。
 * 2. **宽度/全屏管理**：根据 `isChatbotFullscreen` 和 `forceChatbotWidthReset` 等状态调整窗口大小和样式。
 * 3. **模型信息获取**：从 `ChatbotCore` 获取模型配置。
 * 4. **模型选择器模式** (`isModelSelectorOpen`)：
 *    - 若为自定义模型且 `isModelSelectorOpen` 为 true，则调用 `ChatbotModelSelectorUI.render` 显示模型选择器。
 *    - 否则，确保模型选择器被移除，聊天区和预设问题区可见。
 * 5. **预设问题区渲染**：调用 `ChatbotPresetQuestionsUI.render`。
 * 6. **聊天消息渲染**：调用 `ChatbotMessageRenderer` 模块渲染历史消息和加载指示器。
 * 7. **滚动行为**：智能滚动聊天区域，确保新消息可见或保持用户当前视口。
 * 8. **Mermaid图渲染**：调用 `ChatbotRenderingUtils.renderAllMermaidBlocks`。
 * 9. **输入框与发送按钮状态更新**：根据加载状态启用/禁用。
 * 10. **免责声明与清空历史按钮更新**。
 * 11. **浮动高级选项按钮更新**：调用 `_updateFloatingOptionsDisplay`。
 */
function updateChatbotUI() {
  const modal = document.getElementById('chatbot-modal');
  const fab = document.getElementById('chatbot-fab');
  if (!modal || !fab) return;

  // 检测沉浸式模式
  const inImmersive = !!(window.ImmersiveLayout && typeof window.ImmersiveLayout.isActive === 'function' && window.ImmersiveLayout.isActive());
  // 在沉浸式模式下强制禁用浮动模式
  if (inImmersive && window.isChatbotFloating) {
    window.isChatbotFloating = false;
  }

  const currentDocId = window.ChatbotCore && typeof window.ChatbotCore.getCurrentDocId === 'function' ? window.ChatbotCore.getCurrentDocId() : 'default_doc';
  const fullscreenButton = document.getElementById('chatbot-fullscreen-toggle-btn');

  // --- 宽度重置逻辑 ---
  if (window.chatbotInitialLoad) {
    window.forceChatbotWidthReset = true;
    window.chatbotInitialLoad = false;
  }
  const chatbotWindowForCheck = modal.querySelector('.chatbot-window');
  const isOnHistoryDetailForCheck = window.location.pathname.includes('history_detail.html');
  const chunkCompareTabElementForCheck = document.getElementById('tab-chunk-compare');
  const currentChunkCompareActive = isOnHistoryDetailForCheck && chunkCompareTabElementForCheck && chunkCompareTabElementForCheck.classList.contains('active');
  if (window.lastIsChunkCompareActive !== undefined && window.lastIsChunkCompareActive !== currentChunkCompareActive) {
    window.forceChatbotWidthReset = true;
  }

  // --- 聊天窗口显隐与样式调整 ---
  if (window.isChatbotOpen) {
    fab.style.display = 'none';
    const chatbotWindow = modal.querySelector('.chatbot-window');
    if (chatbotWindow) {
      if (window.isChatbotFullscreen) {
        // 全屏样式
        modal.style.display = 'block';
        modal.style.position = 'fixed';
        modal.style.width = '100vw';
        modal.style.height = '100vh';
        modal.style.top = '0px';
        modal.style.left = '0px';
        modal.style.bottom = '0px';
        modal.style.right = '0px';
        modal.style.padding = '0px';
        modal.style.margin = '0px';
        modal.style.border = 'none';
        modal.style.background = 'var(--chat-bg,#ffffff)';
        modal.style.pointerEvents = 'auto';
        modal.style.zIndex = '100000';

        chatbotWindow.style.position = 'absolute';
        chatbotWindow.style.width = '100%';
        chatbotWindow.style.height = '100%';
        chatbotWindow.style.minWidth = '100%';
        chatbotWindow.style.minHeight = '100%';
        chatbotWindow.style.maxWidth = '100%';
        chatbotWindow.style.maxHeight = '100%';
        chatbotWindow.style.top = '0px';
        chatbotWindow.style.left = '0px';
        chatbotWindow.style.right = '0px';
        chatbotWindow.style.bottom = '0px';
        chatbotWindow.style.borderRadius = '0px';
        chatbotWindow.style.padding = '0px';
        chatbotWindow.style.margin = '0px';
        chatbotWindow.style.border = 'none';
        chatbotWindow.style.boxSizing = 'border-box';
        chatbotWindow.style.overflow = 'hidden';
        chatbotWindow.style.resize = 'none';

        if (fullscreenButton) {
          fullscreenButton.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path></svg>`;
          fullscreenButton.title = "退出全屏";
        }
      } else {
        // 非全屏样式
        modal.style.display = 'flex';
        modal.style.position = 'fixed';
        modal.style.width = 'auto';
        modal.style.height = 'auto';
        modal.style.top = '0';
        modal.style.left = '0';
        modal.style.bottom = '0';
        modal.style.right = '0';
        modal.style.padding = '0px';
        modal.style.margin = '0px';
        modal.style.border = 'none';
        modal.style.background = 'transparent';
        modal.style.pointerEvents = 'none';

        if (window.isChatbotFloating) {
          // 浮动模式
          chatbotWindow.style.position = 'fixed';
          chatbotWindow.style.left = window.chatbotFloatingPosition.x + 'px';
          chatbotWindow.style.top = window.chatbotFloatingPosition.y + 'px';
          chatbotWindow.style.right = 'auto';
          chatbotWindow.style.bottom = 'auto';
          chatbotWindow.style.width = window.chatbotFloatingSize.width + 'px';
          chatbotWindow.style.height = window.chatbotFloatingSize.height + 'px';
          chatbotWindow.style.maxWidth = '90vw';
          chatbotWindow.style.maxHeight = '90vh';
          chatbotWindow.style.minWidth = '320px';
          chatbotWindow.style.minHeight = '400px';
          // 样式由 CSS .chatbot-window.floating-mode 控制
          chatbotWindow.style.boxShadow = '';
          chatbotWindow.style.zIndex = '100001';
          chatbotWindow.classList.add('floating-mode');
        } else {
          // 固定位置模式
          chatbotWindow.classList.remove('floating-mode');
          chatbotWindow.style.position = 'absolute';
          let newMaxWidth = '720px';
          let newWidth = '92vw';
          let newMinHeight = 'calc(520px * 1.1)';
          let newMaxHeight = 'calc(85vh * 1.1)';

          const isOnHistoryDetail = window.location.pathname.includes('history_detail.html');
          const chunkCompareTabElement = document.getElementById('tab-chunk-compare');
          const isChunkCompareActive = isOnHistoryDetail && chunkCompareTabElement && chunkCompareTabElement.classList.contains('active');

          if (isChunkCompareActive) {
            newMinHeight = 'calc(520px * 1.25)';
            newMaxHeight = '99vh';
            newMaxWidth = 'calc(720px * 0.90)';
            newWidth = 'calc(92vw * 0.90)';
          }

          if (window.forceChatbotWidthReset || !chatbotWindow.style.width.endsWith('px')) {
            chatbotWindow.style.width = newWidth;
          }
          chatbotWindow.style.maxWidth = newMaxWidth;
          chatbotWindow.style.minWidth = `calc(${newWidth} * 0.32)`;
          chatbotWindow.style.minHeight = newMinHeight;
          chatbotWindow.style.maxHeight = newMaxHeight;
          if (window.forceChatbotWidthReset || !chatbotWindow.style.height.endsWith('px')) {
              chatbotWindow.style.height = '';
          }

          if (window.isChatbotPositionedLeft) {
            chatbotWindow.style.left = '44px';
            chatbotWindow.style.right = 'auto';
          } else {
            chatbotWindow.style.right = '44px';
            chatbotWindow.style.left = 'auto';
          }
          chatbotWindow.style.top = 'auto';
          chatbotWindow.style.bottom = '44px';
          // 样式由 CSS .chatbot-window 控制
          chatbotWindow.style.borderRadius = '';
          chatbotWindow.style.boxShadow = '';
        }

        chatbotWindow.style.padding = '';
        chatbotWindow.style.margin = '';
        chatbotWindow.style.border = '';
        chatbotWindow.style.boxSizing = 'border-box';
        chatbotWindow.style.overflow = 'auto';
        chatbotWindow.style.resize = 'none'; // 禁用默认resize，使用自定义拖拽

        if (fullscreenButton) {
          fullscreenButton.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"></path></svg>`;
          fullscreenButton.title = "全屏模式";
        }
      }
    }
  } else {
    modal.style.display = 'none';
    // 在沉浸式模式下不显示 FAB，以免与沉浸布局冲突
    fab.style.display = inImmersive ? 'none' : 'block';
  }
  window.forceChatbotWidthReset = false;
  window.lastIsChunkCompareActive = currentChunkCompareActive;

  const posToggleBtn = document.getElementById('chatbot-position-toggle-btn');
  if (posToggleBtn) {
    if (window.isChatbotFloating) {
      // 浮动模式下隐藏位置切换按钮
      posToggleBtn.style.display = 'none';
    } else {
      posToggleBtn.style.display = 'flex';
      if (window.isChatbotPositionedLeft) {
        posToggleBtn.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline><path d="M20 4v16"></path></svg>`;
        posToggleBtn.title = "切换到右下角";
      } else {
        posToggleBtn.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline><path d="M4 4v16"></path></svg>`;
        posToggleBtn.title = "切换到左下角";
      }
    }
  }

  const floatToggleBtn = document.getElementById('chatbot-float-toggle-btn');
  if (floatToggleBtn) {
    // 在沉浸式模式隐藏浮动切换按钮
    if (inImmersive) {
      floatToggleBtn.style.display = 'none';
    } else {
      floatToggleBtn.style.display = 'flex';
    }
    if (window.isChatbotFloating) {
      floatToggleBtn.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v4"></path><path d="M16 2v4"></path><rect width="18" height="18" x="3" y="4" rx="2"></rect><path d="M3 10h18"></path></svg>`;
      floatToggleBtn.title = "固定模式";
    } else {
      floatToggleBtn.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>`;
      floatToggleBtn.title = "浮动模式";
    }
  }

  const chatBody = document.getElementById('chatbot-body');
  const chatbotPresetHeader = document.getElementById('chatbot-preset-header');
  const chatbotPresetBody = document.getElementById('chatbot-preset-body');
  let modelSelectorDiv = document.getElementById('chatbot-model-selector');

  const existingPresetContainer = document.getElementById('chatbot-preset-container');
  if (existingPresetContainer) existingPresetContainer.remove();

  const chatbotWindow = modal.querySelector('.chatbot-window');
  if (!chatbotWindow) {
    console.error("Chatbot UI: .chatbot-window not found for preset container.");
    return;
  }

  let isCustomModel = false;
  let availableModels = [];
  let currentSettings = {};
  try {
    // 缓存配置,避免流式更新时频繁加载
    // 只在模型选择器打开或缓存失效时重新获取
    if (!window._cachedChatbotConfig || window.isModelSelectorOpen) {
      window._cachedChatbotConfig = window.ChatbotCore.getChatbotConfig();
    }
    const config = window._cachedChatbotConfig;
    currentSettings = config.settings || {};
    isCustomModel = config.model === 'custom' || (typeof config.model === 'string' && config.model.startsWith('custom_source_'));
    availableModels = config.siteSpecificAvailableModels || [];
  } catch (e) {
    console.error("Error getting chatbot config for UI:", e);
  }

  const presetContainer = window.ChatbotPresetQuestionsUI.render(
    chatbotWindow,
    isCustomModel,
    currentDocId,
    updateChatbotUI,
    window.ChatbotPreset?.handlePresetQuestion || window.handlePresetQuestion
  );

  chatbotWindow.appendChild(presetContainer);

  let userMessageCount = 0;
  if (window.ChatbotCore && window.ChatbotCore.chatHistory) {
    userMessageCount = window.ChatbotCore.chatHistory.filter(m => m.role === 'user').length;
  }

  if (userMessageCount >= 3 &&
      !window.presetAutoCollapseTriggeredForDoc[currentDocId] &&
      !window.isPresetQuestionsCollapsed &&
      !window.isModelSelectorOpen) {
    window.isPresetQuestionsCollapsed = true;
    window.presetAutoCollapseTriggeredForDoc[currentDocId] = true;
    const presetToggleBtn = presetContainer.querySelector('#chatbot-preset-toggle-btn');
    if (presetToggleBtn) {
      presetToggleBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>';
      presetToggleBtn.title = "展开快捷指令";
    }
  }

  // 获取 mainContentArea 元素
  const mainContentArea = document.getElementById('chatbot-main-content-area');

  if (mainContentArea) {
    let current_padding_top_for_main_content_area = 12;
    if (presetContainer && presetContainer.style.display !== 'none' && presetContainer.offsetHeight) {
        current_padding_top_for_main_content_area = presetContainer.offsetHeight;
    }
    mainContentArea.style.paddingTop = current_padding_top_for_main_content_area + 'px';

    // 仅在“固定模式”下根据内容自适应高度；浮动/全屏不改动用户设置的尺寸
    if (!window.isChatbotFloating && !window.isChatbotFullscreen) {
      const titleBar = document.getElementById('chatbot-title-bar');
      const inputContainer = document.getElementById('chatbot-input-container');

      if (titleBar && inputContainer && chatbotWindow) {
          const h_title_bar = titleBar.offsetHeight;
          const h_input_container = inputContainer.offsetHeight;
          const h_chat_body_target = 250;

          const desired_window_height = h_title_bar + current_padding_top_for_main_content_area + h_chat_body_target + h_input_container;

          const min_win_h_px = parseFloat(getComputedStyle(chatbotWindow).minHeight) || 520;
          const max_win_h_px = parseFloat(getComputedStyle(chatbotWindow).maxHeight) || (0.85 * window.innerHeight);

          chatbotWindow.style.height = Math.max(min_win_h_px, Math.min(max_win_h_px, desired_window_height)) + 'px';
      }
    }
  }

  if (isCustomModel && window.isModelSelectorOpen) {
    if (window.ChatbotModelSelectorUI && typeof window.ChatbotModelSelectorUI.render === 'function') {
      window.ChatbotModelSelectorUI.render(mainContentArea, chatBody, availableModels, currentSettings, updateChatbotUI);
    } else {
      console.error("ChatbotModelSelectorUI.render is not available.");
    }
    return;
  } else {
    const existingModelSelectorDiv = document.getElementById('chatbot-model-selector');
    if (existingModelSelectorDiv) existingModelSelectorDiv.remove();
    if (presetContainer) presetContainer.style.display = '';
    if (chatBody) chatBody.style.display = '';
  }
  if (chatBody) {
    const oldScrollTop = chatBody.scrollTop;
    const oldScrollHeight = chatBody.scrollHeight;
    const oldClientHeight = chatBody.clientHeight;

    // Phase 3.5 提前定义消息计数（用于后续滚动逻辑）
    // 使用统一的状态管理对象，避免全局变量污染
    if (!window.ChatbotRenderState) {
      window.ChatbotRenderState = { lastRenderedMessageCount: 0 };
    }
    const currentMessageCount = window.ChatbotCore.chatHistory.length;
    const lastRenderedCount = window.ChatbotRenderState.lastRenderedMessageCount || 0;

    let docName = 'unknown_doc';
    let docId = 'unknown_doc'; // 完整的 docId，用于 draw.io 等功能
    let dataForMindmap = { images: [], ocr: '', translation: '' };
    if (window.ChatbotCore && typeof window.ChatbotCore.getCurrentDocContent === 'function') {
        const currentDoc = window.ChatbotCore.getCurrentDocContent();
        if (currentDoc) {
            docName = currentDoc.name || 'unknown_doc';
            dataForMindmap = {
                images: currentDoc.images || [],
                ocr: currentDoc.ocr || '',
                translation: currentDoc.translation || ''
            };
        }
    }
    // 获取完整的 docId（包含文档名、图片数量、OCR长度、翻译长度）
    if (window.ChatbotCore && typeof window.ChatbotCore.getCurrentDocId === 'function') {
        docId = window.ChatbotCore.getCurrentDocId();
    }

    if (window.ChatbotMessageRenderer) {
        // Phase 3.5 增量渲染: 只渲染变化的消息，避免重新渲染整个历史

        // 如果是流式更新（消息数量没变），只更新最后一条消息
        if (window.ChatbotCore.isChatbotLoading && currentMessageCount === lastRenderedCount && currentMessageCount > 0) {
          const lastMessage = window.ChatbotCore.chatHistory[currentMessageCount - 1];
          const lastMessageContainer = chatBody.querySelector(`.assistant-message[data-message-index="${currentMessageCount - 1}"]`);

          // 检查是否需要完整渲染（reasoning 第一次出现）
          let needFullRender = false;
          if (lastMessage.reasoningContent && lastMessageContainer) {
            const reasoningBlockId = `reasoning-block-${currentMessageCount - 1}`;
            const reasoningBlock = lastMessageContainer.querySelector(`#${reasoningBlockId}`);
            if (!reasoningBlock) {
              needFullRender = true; // reasoning 块不存在，需要完整渲染
            }
          }

          // 如果需要完整渲染，跳过增量更新
          if (!needFullRender && lastMessageContainer && lastMessage.role === 'assistant') {
            // 0. 更新 ReAct 可视化 (reactLog) - 增量追加
            if (lastMessage.reactLog && lastMessage.reactLog.length > 0) {
              const vizId = `react-viz-${currentMessageCount - 1}`;
              let vizContainer = lastMessageContainer.querySelector(`#${vizId}`);
              
              // 如果容器不存在，说明是第一次出现 ReAct 日志，需要完整渲染
              if (!vizContainer) {
                needFullRender = true;
              } else {
                // 增量更新步骤
                const stepsContainer = vizContainer.querySelector('.react-steps-container');
                if (stepsContainer) {
                  const currentStepsCount = stepsContainer.children.length;
                  const newStepsCount = lastMessage.reactLog.length;
                  
                  if (newStepsCount > currentStepsCount) {
                    // 追加新步骤
                    const stepsToAdd = lastMessage.reactLog.slice(currentStepsCount);
                    let newStepsHtml = '';
                    
                    stepsToAdd.forEach((step, i) => {
                        let icon = '';
                        let title = '';
                        let typeClass = '';
                        let content = '';
                        const stepIndex = currentStepsCount + i + 1;

                        if (step.type === 'thought') {
                            icon = 'carbon:idea';
                            title = `Thought ${step.iteration || stepIndex}`;
                            typeClass = 'step-thought';
                            content = step.content;
                        } else if (step.type === 'action') {
                            icon = 'carbon:tools';
                            title = `Action ${step.iteration || stepIndex}`;
                            typeClass = 'step-action';
                            content = `Tool: ${step.tool}\nInput: ${JSON.stringify(step.params, null, 2)}`;
                        } else if (step.type === 'observation') {
                            icon = 'carbon:view';
                            title = `Observation ${step.iteration || stepIndex}`;
                            typeClass = 'step-observation';
                            content = typeof step.result === 'string' ? step.result : JSON.stringify(step.result, null, 2);
                            if (content.length > 500) content = content.slice(0, 500) + '... (truncated)';
                        }

                        if (content) {
                            content = window.ChatbotUtils.escapeHtml(content);
                            // 添加 slideIn 动画
                            newStepsHtml += `
                                <div class="react-step-item ${typeClass}" style="animation: slideIn 0.3s ease-out forwards;">
                                    <div class="react-step-header">
                                        <iconify-icon icon="${icon}"></iconify-icon>
                                        <span>${title}</span>
                                    </div>
                                    <div class="react-step-content">${content}</div>
                                </div>
                            `;
                        }
                    });
                    
                    if (newStepsHtml) {
                        stepsContainer.insertAdjacentHTML('beforeend', newStepsHtml);
                        stepsContainer.scrollTop = stepsContainer.scrollHeight;
                        
                        // 更新状态徽章
                        const statusBadge = vizContainer.querySelector('.react-status-badge');
                        if (statusBadge) {
                            const lastStep = lastMessage.reactLog[lastMessage.reactLog.length - 1];
                            if (lastStep.type === 'action') {
                                statusBadge.textContent = 'Executing...';
                                statusBadge.className = 'react-status-badge react-status-executing';
                            } else if (lastStep.type === 'thought') {
                                statusBadge.textContent = 'Thinking...';
                                statusBadge.className = 'react-status-badge react-status-thinking';
                            }
                        }
                    }
                  }
                }
              }
            }
            
            // 如果在上述检查中发现需要完整渲染，则跳出
            if (needFullRender) {
               // Fall through to full render logic below
            } else {

            // 1. 更新思考过程 (reasoning)
            if (lastMessage.reasoningContent) {
              const reasoningBlockId = `reasoning-block-${currentMessageCount - 1}`;
              let reasoningBlock = lastMessageContainer.querySelector(`#${reasoningBlockId}`);

              // 更新 reasoning 内容
              // Phase 4.x 修复：使用更快的选择器（优先使用类名，回退到子元素选择器）
              const reasoningContentDiv = reasoningBlock
                ? (reasoningBlock.querySelector('.reasoning-content') || reasoningBlock.querySelector(':scope > div:last-child'))
                : null;
              if (reasoningContentDiv) {
                const newReasoningLength = lastMessage.reasoningContent.length;
                const lastReasoningLength = parseInt(reasoningBlock.dataset.lastReasoningLength || '0', 10);

                if (newReasoningLength !== lastReasoningLength) {
                  try {
                    if (typeof renderWithKatexStreaming === 'function') {
                      reasoningContentDiv.innerHTML = renderWithKatexStreaming(lastMessage.reasoningContent);
                    } else {
                      reasoningContentDiv.innerHTML = lastMessage.reasoningContent.replace(/\n/g, '<br>');
                    }
                    reasoningBlock.dataset.lastReasoningLength = newReasoningLength.toString();
                  } catch (e) {
                    if (window.PerfLogger) {
                      window.PerfLogger.error('Reasoning 增量渲染失败:', e);
                    }
                    reasoningContentDiv.textContent = lastMessage.reasoningContent;
                  }
                }
              }
            }

            // 2. 更新主内容 (content)
            const contentDiv = lastMessageContainer.querySelector('.markdown-content');
            if (contentDiv && lastMessage.content) {
              // 使用内容长度与内容本身判断是否有变化
              const newContent = String(lastMessage.content);
              const newContentLength = newContent.length;
              const lastContentLength = parseInt(contentDiv.dataset.lastLength || '0', 10);
              const lastContent = contentDiv.dataset.lastContent || '';

              if (newContentLength !== lastContentLength) {
                try {
                  const isPureExtension =
                    newContentLength > lastContentLength &&
                    lastContent &&
                    newContent.indexOf(lastContent) === 0;
                  const appendedText = isPureExtension ? newContent.slice(lastContent.length) : '';

                  const canUseIncrementalAppend =
                    isPureExtension &&
                    !lastMessage.isRawHtml && // 纯 HTML 内容不使用增量渲染
                    isChatbotSafePlainAppend(lastContent, appendedText);

                  let didIncrementalUpdate = false;

                  if (canUseIncrementalAppend && typeof renderWithKatexStreaming === 'function') {
                    // Phase 4.2: 简单增量渲染（纯文本追加场景）
                    const appendedHtml = renderWithKatexStreaming(appendedText);
                    const temp = document.createElement('div');
                    temp.innerHTML = appendedHtml;
                    while (temp.firstChild) {
                      contentDiv.appendChild(temp.firstChild);
                    }
                    didIncrementalUpdate = true;
                  } else if (
                    isPureExtension &&
                    !lastMessage.isRawHtml && // 纯 HTML 内容不使用增量渲染
                    appendedText &&
                    window.ChatbotMathStreaming &&
                    typeof window.ChatbotMathStreaming.renderIncremental === 'function'
                  ) {
                    // Phase 4.2（原型）: 长公式增量渲染，仅在扩展场景下启用
                    let prevState = null;
                    const stateRaw = contentDiv.dataset.mathStreamingState || '';
                    if (stateRaw) {
                      try {
                        prevState = JSON.parse(stateRaw);
                      } catch (e) {
                        prevState = null;
                      }
                    }

                    const result = window.ChatbotMathStreaming.renderIncremental(prevState, appendedText);
                    if (result && typeof result.html === 'string' && result.html) {
                      const temp = document.createElement('div');
                      temp.innerHTML = result.html;
                      while (temp.firstChild) {
                        contentDiv.appendChild(temp.firstChild);
                      }
                      if (result.state) {
                        try {
                          contentDiv.dataset.mathStreamingState = JSON.stringify(result.state);
                        } catch (e) {
                          contentDiv.dataset.mathStreamingState = '';
                        }
                      }
                      didIncrementalUpdate = true;
                    }
                  }

                  if (!didIncrementalUpdate) {
                    // 回退：完整重渲染
                    let contentToRender = newContent;

                    // 🔧 检测并修复历史数据中被转义的 HTML（向后兼容）
                    if (!lastMessage.isRawHtml &&
                        (contentToRender.includes('&lt;div') || contentToRender.includes('&lt;button')) &&
                        (contentToRender.includes('配图 XML') || contentToRender.includes('手动修复'))) {
                      console.log('[UI] 检测到被转义的 HTML，自动反转义');
                      // 创建临时元素进行反转义
                      const tempDiv = document.createElement('div');
                      tempDiv.innerHTML = contentToRender;
                      contentToRender = tempDiv.innerHTML; // 使用 innerHTML 而不是 textContent
                      lastMessage.isRawHtml = true; // 标记为纯 HTML
                    }

                    // 检查是否为纯 HTML 内容（不需要 Markdown 解析）
                    if (lastMessage.isRawHtml) {
                      contentDiv.innerHTML = contentToRender;
                    } else if (typeof renderWithKatexStreaming === 'function') {
                      contentDiv.innerHTML = renderWithKatexStreaming(contentToRender);
                    } else if (typeof marked !== 'undefined') {
                      contentDiv.innerHTML = marked.parse(contentToRender);
                    } else {
                      contentDiv.textContent = contentToRender;
                    }
                    // 重渲染后清理流式状态，避免状态与内容不一致
                    delete contentDiv.dataset.mathStreamingState;
                  }

                  contentDiv.dataset.lastLength = newContentLength.toString();
                  contentDiv.dataset.lastContent = newContent;
                } catch (e) {
                  if (window.PerfLogger) {
                    window.PerfLogger.error('增量渲染失败:', e);
                  }
                  contentDiv.textContent = newContent;
                }
              }
            }

            } // End of else block for needFullRender check

            // 3. 更新工具调用块 (toolCallHtml) - 支持流式多轮取材实时更新
            if (lastMessage.toolCallHtml) {
              const toolCallBlockContainer = lastMessageContainer.querySelector('.tool-thinking-block');
              const newToolCallHtml = String(lastMessage.toolCallHtml);

              // 检查是否需要更新（比较HTML内容）
              if (toolCallBlockContainer) {
                const currentHtml = toolCallBlockContainer.outerHTML;
                if (currentHtml !== newToolCallHtml) {
                  // 更新工具调用块HTML
                  const tempDiv = document.createElement('div');
                  tempDiv.innerHTML = newToolCallHtml;
                  const newToolCallBlock = tempDiv.firstElementChild;
                  if (newToolCallBlock) {
                    toolCallBlockContainer.replaceWith(newToolCallBlock);
                  }
                }
              } else if (newToolCallHtml) {
                // 工具调用块不存在，插入新的块（在主内容之前）
                const contentDiv = lastMessageContainer.querySelector('.markdown-content');
                if (contentDiv && contentDiv.parentNode) {
                  const tempDiv = document.createElement('div');
                  tempDiv.innerHTML = newToolCallHtml;
                  const newToolCallBlock = tempDiv.firstElementChild;
                  if (newToolCallBlock) {
                    contentDiv.parentNode.insertBefore(newToolCallBlock, contentDiv);
                  }
                }
              }
            }
          }

          // Phase 3.5 智能滚动：流式更新时保持用户阅读位置
          // 只有在用户主动停留在底部附近时才自动滚动
          if (!needFullRender) {
            const scrollThreshold = window.PerformanceConfig?.SCROLL?.BOTTOM_THRESHOLD || 50;
            const isUserAtBottom = oldScrollHeight - oldClientHeight <= oldScrollTop + scrollThreshold;
            if (isUserAtBottom) {
              chatBody.scrollTop = chatBody.scrollHeight;
            }
            // 如果用户正在查看上方内容，不做任何滚动操作
            return; // 跳过完整重新渲染
          }
          // needFullRender = true 时，继续执行完整渲染（不 return）
        }

        // 完整渲染：新消息或非流式更新
        let messagesHtml = window.ChatbotCore.chatHistory.map((m, index) => {
            if (m.role === 'segment-summary') {
                return '';
            }
            if (m.role === 'final-summary') {
                return window.ChatbotMessageRenderer.renderFinalSummaryMessage(m);
            }
            if (m.role === 'user') {
                return window.ChatbotMessageRenderer.renderUserMessage(m, index);
            }
            return window.ChatbotMessageRenderer.renderAssistantMessage(m, index, docName, dataForMindmap, docId);
        }).join('');

        // Prevent double typing indicators:
        // Only show the standalone typing indicator if the last message is NOT from the assistant.
        // If the last message IS from the assistant, it means the response has started (even if empty/reasoning),
        // so the assistant message itself will render the appropriate state (Logo, Reasoning, or Content).
        const lastMsg = window.ChatbotCore.chatHistory[window.ChatbotCore.chatHistory.length - 1];
        const isLastMsgAssistant = lastMsg && lastMsg.role === 'assistant';

        if (window.ChatbotCore.isChatbotLoading && !isLastMsgAssistant) {
            messagesHtml += window.ChatbotMessageRenderer.renderTypingIndicator();
        }

        chatBody.innerHTML = messagesHtml + window.ChatbotMessageRenderer.getMarkdownStyles();
        window.ChatbotRenderState.lastRenderedMessageCount = currentMessageCount;
        if (window.PerfLogger) {
          window.PerfLogger.debug(`增量渲染: 完整渲染 ${currentMessageCount} 条消息`);
        }
    } else {
        console.error("ChatbotMessageRenderer is not loaded!");
        chatBody.innerHTML = "<p style='color:red;'>错误：消息渲染模块加载失败。</p>";
    }

    setTimeout(() => {
      if (!window.ChatbotCore.isChatbotLoading) {
        chatBody.querySelectorAll('code.language-mermaid, pre code.language-mermaid').forEach(block => {
          block.setAttribute('data-mermaid-final', 'true');
        });
      }
    }, 0);

    // Phase 3.5 智能滚动：完整渲染时保持用户阅读位置
    const isUserAtBottom = oldScrollHeight - oldClientHeight <= oldScrollTop + 50; // 增加容差到 50px
    const isNewMessageArrival = currentMessageCount > lastRenderedCount; // 检测是否有新消息到达

    // 自动滚动到底部的条件：
    // 1. 有新消息到达（用户发送消息或助手开始回复）
    // 2. 或者用户已经停留在底部附近
    if (isNewMessageArrival || isUserAtBottom) {
      chatBody.scrollTop = chatBody.scrollHeight;
    }
    // 否则保持用户当前的阅读位置（即使正在加载也不强制滚动）

    if (window.ChatbotRenderingUtils && typeof window.ChatbotRenderingUtils.renderAllMermaidBlocks === 'function') {
      if (window.mermaidLoaded && typeof window.mermaid !== 'undefined') {
        window.ChatbotRenderingUtils.renderAllMermaidBlocks(chatBody);
      } else {
        setTimeout(() => {
          if (window.ChatbotRenderingUtils && typeof window.ChatbotRenderingUtils.renderAllMermaidBlocks === 'function') {
            window.ChatbotRenderingUtils.renderAllMermaidBlocks(chatBody);
          }
        }, 600);
        setTimeout(() => {
          if (window.ChatbotRenderingUtils && typeof window.ChatbotRenderingUtils.renderAllMermaidBlocks === 'function') {
            window.ChatbotRenderingUtils.renderAllMermaidBlocks(chatBody);
          }
        }, 1200);
      }
    } else {
      console.warn('ChatbotUI: ChatbotRenderingUtils.renderAllMermaidBlocks is not available.');
    }

    // Phase 4.1: 表格滚动性能优化 - 使用 IntersectionObserver 优先，回退到 requestAnimationFrame 节流
    setupChatbotTableScrollHints(chatBody);
  }

  const input = document.getElementById('chatbot-input');
  const sendBtn = document.getElementById('chatbot-send-btn');
  const stopBtn = document.getElementById('chatbot-stop-btn');

  if (input && sendBtn) {
    input.disabled = window.ChatbotCore.isChatbotLoading;
    sendBtn.disabled = window.ChatbotCore.isChatbotLoading;

    if (window.ChatbotCore.isChatbotLoading) {
      sendBtn.style.display = 'none';
      if (stopBtn) stopBtn.style.display = 'flex';
    } else {
      sendBtn.style.display = 'flex';
      if (stopBtn) stopBtn.style.display = 'none';
    }
  }

  const disclaimerDiv = document.querySelector('.chatbot-disclaimer');
  if (disclaimerDiv) {
    const currentChatHistory = window.ChatbotCore && window.ChatbotCore.chatHistory ? window.ChatbotCore.chatHistory : [];
    if (currentChatHistory.length > 0) {
      disclaimerDiv.innerHTML = '<span>AI助手可能会犯错。请核实重要信息。</span>丨<span id="chatbot-clear-history-btn" style="color:#2563eb;cursor:pointer;font-weight:500;">删除对话记录</span>';
      const clearBtn = document.getElementById('chatbot-clear-history-btn');
      if (clearBtn) {
        clearBtn.onclick = function() {
          if (confirm('确定要删除当前对话的所有记录吗？')) {
            if (window.ChatbotCore && typeof window.ChatbotCore.clearCurrentDocChatHistory === 'function') {
              const docIdToClear = window.ChatbotCore.getCurrentDocId ? window.ChatbotCore.getCurrentDocId() : 'default_doc';
              window.ChatbotCore.clearCurrentDocChatHistory(updateChatbotUI);
              window.isPresetQuestionsCollapsed = false;
              if (window.presetAutoCollapseTriggeredForDoc) {
                delete window.presetAutoCollapseTriggeredForDoc[docIdToClear];
              }
            } else {
              console.error("clearCurrentDocChatHistory function not found on ChatbotCore");
            }
          }
        };
      }
    } else {
      disclaimerDiv.innerHTML = '<p style="margin:0;">AI助手可能会犯错。请核实重要信息。</p>';
    }
  }

  // 将调用 _updateFloatingOptionsDisplay() 改为调用独立模块
  if (window.ChatbotFloatingOptionsUI && typeof window.ChatbotFloatingOptionsUI.updateDisplay === 'function') {
    window.ChatbotFloatingOptionsUI.updateDisplay();
  }
}

/**
 * Phase 4.1 - 表格滚动提示性能优化
 * 目标：在保持现有视觉行为（滚动到最右侧隐藏渐变阴影）的前提下，减少滚动事件压力。
 *
 * 策略：
 * 1. 优先使用 IntersectionObserver 观察表格中“最右侧单元格”是否完全进入视口。
 *    - root: table，自身作为滚动容器。
 *    - threshold: 1.0，仅当最后单元格完全可见时认为滚动到末尾。
 * 2. 当浏览器不支持 IntersectionObserver 时，回退到 requestAnimationFrame 节流的 scroll 监听。
 * 3. 每次调用都会清理旧的监听器和观察器，避免重复绑定和内存泄漏。
 */
function setupChatbotTableScrollHints(chatBody) {
  if (!chatBody) return;

  const tables = chatBody.querySelectorAll('.markdown-content table');
  tables.forEach(function(table) {
    // 清理旧的 IntersectionObserver（如有）
    if (table._scrollObserver && typeof table._scrollObserver.disconnect === 'function') {
      try {
        table._scrollObserver.disconnect();
      } catch (e) {
        if (window.PerfLogger) {
          window.PerfLogger.warn('ChatbotUI: disconnect table._scrollObserver failed', e);
        }
      }
    }
    table._scrollObserver = null;

    // 清理旧的 scroll 监听器（如有）
    if (table._scrollListener) {
      table.removeEventListener('scroll', table._scrollListener);
    }
    table._scrollListener = null;

    // 如果浏览器支持 IntersectionObserver，则优先使用
    if (window.IntersectionObserver) {
      let lastCell = table.querySelector('tr:last-child td:last-child');
      if (!lastCell) {
        lastCell = table.querySelector('td:last-child, th:last-child');
      }

      // 如果找不到可观察的单元格，则直接视为已滚动到底部（不显示渐变阴影）
      if (!lastCell) {
        table.classList.add('scrolled-to-end');
        return;
      }

      const observer = new IntersectionObserver(function(entries) {
        entries.forEach(function(entry) {
          // 当最后一个单元格完全进入视口时，认为滚动到了最右端
          const isEnd = entry.isIntersecting && entry.intersectionRatio >= 1;
          if (isEnd) {
            table.classList.add('scrolled-to-end');
          } else {
            table.classList.remove('scrolled-to-end');
          }
        });
      }, {
        root: table,
        threshold: 1.0
      });

      observer.observe(lastCell);
      table._scrollObserver = observer;
      return;
    }

    // 回退方案：使用 requestAnimationFrame 节流 scroll 事件
    let scrollRAF = null;
    const scrollListener = function() {
      if (scrollRAF) return;
      // 若浏览器不支持 requestAnimationFrame，则直接同步执行
      if (typeof window.requestAnimationFrame !== 'function') {
        const isScrolledToEnd = table.scrollLeft >= (table.scrollWidth - table.clientWidth - 5);
        if (isScrolledToEnd) {
          table.classList.add('scrolled-to-end');
        } else {
          table.classList.remove('scrolled-to-end');
        }
        return;
      }

      scrollRAF = window.requestAnimationFrame(function() {
        const isScrolledToEnd = table.scrollLeft >= (table.scrollWidth - table.clientWidth - 5);
        if (isScrolledToEnd) {
          table.classList.add('scrolled-to-end');
        } else {
          table.classList.remove('scrolled-to-end');
        }
        scrollRAF = null;
      });
    };

    table._scrollListener = scrollListener;
    table.addEventListener('scroll', scrollListener);

    // 初始检查（表格首次渲染时）
    scrollListener();
  });
}

/**
 * Phase 4.2 - 简单增量追加判定（纯文本场景）
 * 仅在追加内容较为“安全”时启用增量渲染：
 * - newContent 以 oldContent 为前缀（纯追加，而非回退/编辑）
 * - 追加部分不包含明显的结构/公式标记（如 ```、$$、\[…\] 等）
 * 复杂 Markdown / LaTeX 场景仍回退到完整重渲染，避免破坏结构。
 */
function isChatbotSafePlainAppend(oldContent, appendedText) {
  if (!appendedText) return false;

  // 边界检查 1：若旧内容末尾处位于未闭合的块级结构中（代码块/公式等），则不做增量。
  // 这里采用“保守策略”：一旦存在疑似未闭合结构，就直接回退完整渲染，以换取更高的渲染正确性。
  try {
    if (isChatbotInsideUnclosedBlock(oldContent)) {
      return false;
    }
  } catch (e) {
    // 检测异常时同样回退完整渲染
    if (window.PerfLogger) {
      window.PerfLogger.warn('ChatbotUI: isChatbotInsideUnclosedBlock failed, fallback to full render.', e);
    }
    return false;
  }

  // 若追加部分包含明显的代码块/公式/复杂结构标记，则不做增量
  const riskyPatterns = [
    /```/,          // 代码块
    /\$\$/,         // 块级公式
    /\\\[/,         // \[ ... \]
    /\\\(/,         // \( ... \)
    /<\/?[a-zA-Z]/, // HTML 标签
    /^#{1,6}\s/m,   // 标题
    /^\s{0,3}[-*+]\s/m, // 无序列表
    /^\s{0,3}\d+\.\s/m, // 有序列表
    /^\s{0,3}>\s/m  // 引用
  ];

  for (let i = 0; i < riskyPatterns.length; i++) {
    if (riskyPatterns[i].test(appendedText)) {
      return false;
    }
  }

  return true;
}

/**
 * Phase 4.2.2 - Markdown 结构边界检测
 * 检测 oldContent 末尾是否位于未闭合的 Markdown 块级结构中：
 * - ``` fenced code block
 * - $$ 块级公式
 * - \[ \] / \( \) LaTeX 公式包裹
 *
 * 为保证性能：
 * - 优先仅分析结尾一段文本（TAIL_WINDOW），减少在极长消息上的全量扫描开销；
 * - 对 fenced code / $$ 使用“奇偶计数”策略；对 \[ / \] 与 \( / \) 使用“数量差值”近似判断；
 * - 一旦存在“不确定”或“可能未闭合”的结构，则视为不安全，回退完整渲染。
 */
function isChatbotInsideUnclosedBlock(oldContent) {
  if (!oldContent || typeof oldContent !== 'string') return false;

  // 限制分析窗口，避免在超长内容上多次全量扫描
  const TAIL_WINDOW = 4000;
  const text = oldContent.length > TAIL_WINDOW
    ? oldContent.slice(-TAIL_WINDOW)
    : oldContent;

  // 辅助函数：统计匹配次数
  function countMatches(pattern) {
    const re = new RegExp(pattern, 'g');
    let count = 0;
    while (re.exec(text) !== null) {
      count++;
    }
    return count;
  }

  // 1) Fenced code block: ``` ... ```
  // 使用出现次数的奇偶性近似判断是否在未闭合的代码块中。
  const codeFenceCount = countMatches('```');
  if (codeFenceCount % 2 === 1) {
    return true;
  }

  // 2) Block math: $$ ... $$
  const blockMathCount = countMatches('\\$\\$');
  if (blockMathCount % 2 === 1) {
    return true;
  }

  // 3) LaTeX-style delimiters: \[ ... \], \( ... \)
  const openBracket = countMatches('\\\\\\[');
  const closeBracket = countMatches('\\\\\\]');
  if (openBracket > closeBracket) {
    return true;
  }

  const openParen = countMatches('\\\\\\(');
  const closeParen = countMatches('\\\\\\)');
  if (openParen > closeParen) {
    return true;
  }

  return false;
}

/**
 * 初始化聊天机器人浮动按钮 (FAB) 和主弹窗 (Modal) 的 UI。
 *
 * 主要步骤：
 * 1. **FAB 初始化**：创建 FAB，设置样式和点击事件（打开聊天弹窗）。
 * 2. **Modal 初始化**：创建 Modal，包含头部、预设区、聊天内容区、输入区等。
 *    - 头部：标题、全屏/位置切换/关闭按钮。
 *    - 主内容区：承载预设区和聊天内容区。
 *    - 输入区：图片添加、文本输入、发送按钮、免责声明。
 *    - 注入基础 CSS (滚动条、响应式、暗黑模式等)。
 * 3. **浮动高级选项初始化**：调用 `_createFloatingOptionsBar` 创建选项按钮并插入到输入区。
 * 4. **事件绑定**：为全屏、位置切换、关闭按钮绑定事件。
 * 5. **初始UI更新**：调用 `updateChatbotUI`。
 */
function initChatbotUI() {
  // --- FAB (浮动操作按钮) 初始化 ---
  let fab = document.getElementById('chatbot-fab');
  if (!fab) {
    fab = document.createElement('div');
    fab.id = 'chatbot-fab';
    // 设置 FAB 的固定定位、初始位置（根据 isChatbotPositionedLeft 决定左右）和层级
    fab.style.position = 'fixed';
    fab.style.bottom = '32px';
    if (window.isChatbotPositionedLeft) {
      fab.style.left = '32px';
      fab.style.right = 'auto';
    } else {
      fab.style.right = '32px';
      fab.style.left = 'auto';
    }
    fab.style.zIndex = '99999';
    // FAB 内部的按钮 HTML，使用响应式尺寸和CSS变量
    fab.innerHTML = `
      <button class="chatbot-fab-button"
        onmouseover="this.style.transform='scale(1.05)';"
        onmouseout="this.style.transform='scale(1)';">
        <i class="fa-solid fa-robot"></i>
      </button>
    `;
    document.body.appendChild(fab);
  }
  // FAB 点击事件：打开聊天窗口，默认展开预设问题，并强制重置窗口宽度
  fab.onclick = function() {
    window.isChatbotOpen = true;
    window.isPresetQuestionsCollapsed = false;
    window.forceChatbotWidthReset = true;
    updateChatbotUI();
  };

  // --- Modal (主聊天窗口) 初始化 ---
  let modal = document.getElementById('chatbot-modal');
  if (!modal) {
    modal = document.createElement('div');
    modal.id = 'chatbot-modal';
    // Modal 作为聊天窗口的容器，初始隐藏，通过 flex 布局控制 chatbot-window 的居中（非全屏时）
    modal.style.position = 'fixed';
    modal.style.inset = '0'; // 等同于 top:0, left:0, bottom:0, right:0
    modal.style.zIndex = '100000';
    modal.style.background = 'transparent'; // 背景透明，依赖内部 chatbot-window 的背景
    modal.style.display = 'none'; // 初始隐藏
    modal.style.pointerEvents = 'none'; // 自身不接收鼠标事件，允许穿透
    // Modal 内部的 HTML 结构
    modal.innerHTML = `
      <div class="chatbot-window">
        <!-- 拖拽调整大小的句柄 -->
        <div class="chatbot-resize-handles">
          <div class="chatbot-resize-handle chatbot-resize-n" data-direction="n"></div>
          <div class="chatbot-resize-handle chatbot-resize-s" data-direction="s"></div>
          <div class="chatbot-resize-handle chatbot-resize-w" data-direction="w"></div>
          <div class="chatbot-resize-handle chatbot-resize-e" data-direction="e"></div>
          <div class="chatbot-resize-handle chatbot-resize-nw" data-direction="nw"></div>
          <div class="chatbot-resize-handle chatbot-resize-ne" data-direction="ne"></div>
          <div class="chatbot-resize-handle chatbot-resize-sw" data-direction="sw"></div>
          <div class="chatbot-resize-handle chatbot-resize-se" data-direction="se"></div>
        </div>
        <!-- 浮动切换按钮 -->
        <div style="position:absolute;top:12px;right:138px;z-index:11;">
          <button id="chatbot-float-toggle-btn" title="浮动模式" style="width:32px;height:32px;border-radius:16px;border:none;background:rgba(0,0,0,0.06);color:#666;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all 0.2s;box-shadow:0 2px 6px rgba(0,0,0,0.06);" onmouseover="this.style.background='rgba(0,0,0,0.1)';this.style.transform='scale(1.05)'" onmouseout="this.style.background='rgba(0,0,0,0.06)';this.style.transform='scale(1)'">
            {/* 图标由 updateChatbotUI 动态设置 */}
          </button>
        </div>
        <!-- 全屏切换按钮 -->
        <div style="position:absolute;top:12px;right:98px;z-index:11;">
          <button id="chatbot-fullscreen-toggle-btn" title="全屏模式" style="width:32px;height:32px;border-radius:16px;border:none;background:rgba(0,0,0,0.06);color:#666;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all 0.2s;box-shadow:0 2px 6px rgba(0,0,0,0.06);" onmouseover="this.style.background='rgba(0,0,0,0.1)';this.style.transform='scale(1.05)'" onmouseout="this.style.background='rgba(0,0,0,0.06)';this.style.transform='scale(1)'">
            {/* 图标由 updateChatbotUI 动态设置 */}
          </button>
        </div>
        <!-- 位置切换按钮 -->
        <div style="position:absolute;top:12px;right:58px;z-index:11;">
          <button id="chatbot-position-toggle-btn" title="切换位置" style="width:32px;height:32px;border-radius:16px;border:none;background:rgba(0,0,0,0.06);color:#666;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all 0.2s;box-shadow:0 2px 6px rgba(0,0,0,0.06);" onmouseover="this.style.background='rgba(0,0,0,0.1)';this.style.transform='scale(1.05)'" onmouseout="this.style.background='rgba(0,0,0,0.06)';this.style.transform='scale(1)'">
            {/* 图标由 updateChatbotUI 动态设置 */}
          </button>
        </div>
        <!-- 关闭按钮 -->
        <div style="position:absolute;top:12px;right:18px;z-index:10;">
          <button id="chatbot-close-btn" style="width:32px;height:32px;border-radius:16px;border:none;background:rgba(0,0,0,0.06);color:#666;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all 0.2s;box-shadow:0 2px 6px rgba(0,0,0,0.06);" onmouseover="this.style.background='rgba(0,0,0,0.1)';this.style.transform='scale(1.05)'" onmouseout="this.style.background='rgba(0,0,0,0.06)';this.style.transform='scale(1)'">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
              <line x1="18" y1="6" x2="6" y2="18"></line>
              <line x1="6" y1="6" x2="18" y2="18"></line>
            </svg>
          </button>
        </div>
        <!-- 标题栏 (可拖拽移动窗口) -->
        <div id="chatbot-title-bar" class="chatbot-draggable-header" style="padding:12px 24px;display:flex;align-items:center;gap:8px;border-bottom:1px dashed rgba(0,0,0,0.1);flex-shrink:0;">
          <div style="width:32px;height:32px;border-radius:16px;background:linear-gradient(135deg,#3b82f6,#1d4ed8);display:flex;align-items:center;justify-content:center;">
            <i class="fa-solid fa-robot" style="font-size: 14px; color: white;"></i>
          </div>
          <span style="font-weight:600;font-size:1.05em;color:#111;">AI 智能助手</span>
        </div>
        <!-- 主内容区域，包含聊天记录和可能的预设问题区 -->
        <div id="chatbot-main-content-area" style="padding:12px 20px 0 20px;flex:1;display:flex;flex-direction:column;overflow:hidden;transition: padding-top 0.4s ease-out;">
          <!-- 聊天消息显示主体 -->
          <div id="chatbot-body"></div>
        </div>
        <!-- 输入区域容器 (Refactored) -->
        <div id="chatbot-input-container" class="chatbot-input-container">
          <!-- 浮动高级选项将由JS插入此处 -->
          <!-- 已选图片预览区 -->
          <div id="chatbot-selected-images-preview" class="chatbot-image-preview-area">
            {/* 图片预览由 ChatbotImageUtils.updateSelectedImagesPreview 更新 */}
          </div>
          <!-- 输入框和发送按钮的 flex 容器 -->
          <div class="chatbot-input-wrapper">
            <!-- 添加图片按钮 -->
            <button id="chatbot-add-image-btn" title="添加图片"
              class="chatbot-input-btn chatbot-add-image-btn"
              onclick="window.ChatbotImageUtils.openImageSelectionModal()">
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>
            </button>
            <!-- 提供PDF按钮 -->
            <button id="chatbot-provide-pdf-btn" title="提供当前PDF给大模型"
              class="chatbot-input-btn chatbot-provide-pdf-btn"
              onclick="if(window.ChatbotActions && window.ChatbotActions.handleProvidePdf) window.ChatbotActions.handleProvidePdf();">
              <iconify-icon icon="carbon:document-pdf" width="20"></iconify-icon>
            </button>
            <!-- 文本输入框 -->
            <input id="chatbot-input" type="text" placeholder="请输入问题..."
              class="chatbot-input-field"
              onkeydown="if(event.key==='Enter'){window.handleChatbotSend();}"
            />
            <!-- 发送按钮 -->
            <button id="chatbot-send-btn"
              class="chatbot-input-btn chatbot-send-btn"
              onclick="window.handleChatbotSend()"
            >
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                <line x1="22" y1="2" x2="11" y2="13"></line>
                <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
              </svg>
            </button>
            <!-- 暂停按钮 -->
            <button id="chatbot-stop-btn"
              class="chatbot-input-btn chatbot-stop-btn"
              onclick="window.handleChatbotStop()"
              title="停止对话"
            >
              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="position:relative;z-index:1;">
                <circle cx="12" cy="12" r="10"></circle>
                <rect x="9" y="9" width="6" height="6" fill="currentColor"></rect>
              </svg>
            </button>
          </div>
          <!-- 免责声明 -->
          <div class="chatbot-disclaimer">
            <p style="margin:0;">AI助手可能会犯错。请核实重要信息。</p>
          </div>
        </div>
      </div>
    `;
    document.body.appendChild(modal);
  }

  // --- 浮动高级选项初始化 ---
  const inputContainerElement = document.getElementById('chatbot-input-container');
  if (window.ChatbotFloatingOptionsUI && typeof window.ChatbotFloatingOptionsUI.createBar === 'function') {
    window.ChatbotFloatingOptionsUI.createBar(inputContainerElement, updateChatbotUI);
  }

  // --- 核心控制按钮事件绑定 ---
  // 浮动模式切换按钮点击事件
  document.getElementById('chatbot-float-toggle-btn').onclick = function() {
    // 沉浸式模式下禁用浮动模式切换
    if (window.ImmersiveLayout && typeof window.ImmersiveLayout.isActive === 'function' && window.ImmersiveLayout.isActive()) {
      return; // 直接忽略
    }
    window.isChatbotFloating = !window.isChatbotFloating;
    localStorage.setItem('chatbotFloating', String(window.isChatbotFloating));

    if (window.isChatbotFloating) {
      // 切换到浮动模式时，记录当前位置和大小
      const chatbotWindow = modal.querySelector('.chatbot-window');
      if (chatbotWindow) {
        const rect = chatbotWindow.getBoundingClientRect();
        window.chatbotFloatingPosition = { x: rect.left, y: rect.top };
        window.chatbotFloatingSize = { width: rect.width, height: rect.height };
        localStorage.setItem('chatbotFloatingPosition', JSON.stringify(window.chatbotFloatingPosition));
        localStorage.setItem('chatbotFloatingSize', JSON.stringify(window.chatbotFloatingSize));
      }
    }
    updateChatbotUI();
  };

  // 全屏切换按钮点击事件
  document.getElementById('chatbot-fullscreen-toggle-btn').onclick = function() {
    const wasFullscreen = window.isChatbotFullscreen;
    window.isChatbotFullscreen = !window.isChatbotFullscreen; // 切换全屏状态
    localStorage.setItem('chatbotFullscreen', String(window.isChatbotFullscreen)); // 保存到localStorage
    if (wasFullscreen && !window.isChatbotFullscreen) { // 如果是从全屏退出
      window.forceChatbotWidthReset = true; // 强制重置宽度
    }
    updateChatbotUI();
  };

  // 位置切换按钮点击事件
  document.getElementById('chatbot-position-toggle-btn').onclick = function() {
    window.isChatbotPositionedLeft = !window.isChatbotPositionedLeft; // 切换左右位置状态
    localStorage.setItem('chatbotPosition', window.isChatbotPositionedLeft ? 'left' : 'right'); // 保存到localStorage
    window.forceChatbotWidthReset = true; // 位置变化也应重置宽度
    updateChatbotUI();
  };

  // 关闭按钮点击事件
  document.getElementById('chatbot-close-btn').onclick = function() {
    window.isChatbotOpen = false; // 关闭聊天窗口
    updateChatbotUI();
  };

  // --- 拖拽和调整大小功能初始化 ---
  initChatbotDragAndResize();

  updateChatbotUI(); // 首次完整渲染或更新UI
}

/**
 * 初始化聊天机器人的拖拽移动和调整大小功能
 */
function initChatbotDragAndResize() {
  const modal = document.getElementById('chatbot-modal');
  if (!modal) return;

  let isDragging = false;
  let isResizing = false;
  let dragStartX, dragStartY;
  let initialX, initialY, initialWidth, initialHeight;
  let resizeDirection = '';

  // 拖拽移动功能 (仅在浮动模式下生效)
  function handleDragStart(e) {
    if (!window.isChatbotFloating || window.isChatbotFullscreen) return;

    const chatbotWindow = modal.querySelector('.chatbot-window');
    if (!chatbotWindow) return;

    isDragging = true;
    dragStartX = e.clientX;
    dragStartY = e.clientY;

    const rect = chatbotWindow.getBoundingClientRect();
    initialX = rect.left;
    initialY = rect.top;

    document.body.classList.add('chatbot-dragging');
    e.preventDefault();
  }

  function handleDragMove(e) {
    if (!isDragging || !window.isChatbotFloating) return;

    const deltaX = e.clientX - dragStartX;
    const deltaY = e.clientY - dragStartY;

    const newX = Math.max(0, Math.min(window.innerWidth - 320, initialX + deltaX));
    const newY = Math.max(0, Math.min(window.innerHeight - 200, initialY + deltaY));

    window.chatbotFloatingPosition = { x: newX, y: newY };
    localStorage.setItem('chatbotFloatingPosition', JSON.stringify(window.chatbotFloatingPosition));

    const chatbotWindow = modal.querySelector('.chatbot-window');
    if (chatbotWindow) {
      chatbotWindow.style.left = newX + 'px';
      chatbotWindow.style.top = newY + 'px';
    }
  }

  function handleDragEnd() {
    if (isDragging) {
      isDragging = false;
      document.body.classList.remove('chatbot-dragging');
    }
  }

  // 调整大小功能
  function handleResizeStart(e) {
    if (window.isChatbotFullscreen || !window.isChatbotFloating) return;

    const chatbotWindow = modal.querySelector('.chatbot-window');
    if (!chatbotWindow) return;

    isResizing = true;
    resizeDirection = e.target.dataset.direction;
    dragStartX = e.clientX;
    dragStartY = e.clientY;

    const rect = chatbotWindow.getBoundingClientRect();
    initialX = rect.left;
    initialY = rect.top;
    initialWidth = rect.width;
    initialHeight = rect.height;

    document.body.classList.add('chatbot-dragging');
    e.preventDefault();
    e.stopPropagation();
  }

  function handleResizeMove(e) {
    if (!isResizing || !window.isChatbotFloating) return;

    const deltaX = e.clientX - dragStartX;
    const deltaY = e.clientY - dragStartY;

    let newX = initialX;
    let newY = initialY;
    let newWidth = initialWidth;
    let newHeight = initialHeight;

    // 根据拖拽方向调整大小和位置
    if (resizeDirection.includes('n')) {
      newY = initialY + deltaY;
      newHeight = initialHeight - deltaY;
    }
    if (resizeDirection.includes('s')) {
      newHeight = initialHeight + deltaY;
    }
    if (resizeDirection.includes('w')) {
      newX = initialX + deltaX;
      newWidth = initialWidth - deltaX;
    }
    if (resizeDirection.includes('e')) {
      newWidth = initialWidth + deltaX;
    }

    // 应用最小和最大尺寸限制
    newWidth = Math.max(320, Math.min(window.innerWidth * 0.9, newWidth));
    newHeight = Math.max(400, Math.min(window.innerHeight * 0.9, newHeight));

    // 确保窗口不会超出屏幕边界
    newX = Math.max(0, Math.min(window.innerWidth - newWidth, newX));
    newY = Math.max(0, Math.min(window.innerHeight - newHeight, newY));

    // 更新全局状态
    window.chatbotFloatingPosition = { x: newX, y: newY };
    window.chatbotFloatingSize = { width: newWidth, height: newHeight };
    localStorage.setItem('chatbotFloatingPosition', JSON.stringify(window.chatbotFloatingPosition));
    localStorage.setItem('chatbotFloatingSize', JSON.stringify(window.chatbotFloatingSize));

    // 应用新的位置和大小
    const chatbotWindow = modal.querySelector('.chatbot-window');
    if (chatbotWindow) {
      chatbotWindow.style.left = newX + 'px';
      chatbotWindow.style.top = newY + 'px';
      chatbotWindow.style.width = newWidth + 'px';
      chatbotWindow.style.height = newHeight + 'px';
    }
  }

  function handleResizeEnd() {
    if (isResizing) {
      isResizing = false;
      resizeDirection = '';
      document.body.classList.remove('chatbot-dragging');
    }
  }

  // 绑定拖拽移动事件 (标题栏)
  modal.addEventListener('mousedown', function(e) {
    if (e.target.closest('.chatbot-draggable-header')) {
      handleDragStart(e);
    } else if (e.target.closest('.chatbot-resize-handle')) {
      handleResizeStart(e);
    }
  });

  // 全局鼠标移动和释放事件
  document.addEventListener('mousemove', function(e) {
    if (isDragging) {
      handleDragMove(e);
    } else if (isResizing) {
      handleResizeMove(e);
    }
  });

  document.addEventListener('mouseup', function() {
    handleDragEnd();
    handleResizeEnd();
  });

  // ==========================================
  // Phase 3: 初始化消息事件管理器（事件委托）
  // ==========================================
  if (window.ChatMessageEventManager) {
    try {
      window.chatMessageEventManager = new ChatMessageEventManager('#chatbot-body');
      console.log('[ChatbotUI] ✅ Phase 3: 消息事件管理器已初始化（事件委托模式）');
    } catch (error) {
      console.error('[ChatbotUI] ❌ Phase 3: 消息事件管理器初始化失败:', error);
    }
  } else {
    console.warn('[ChatbotUI] ⚠️ Phase 3: ChatMessageEventManager 类未加载，将使用内联事件（回滚模式）');
  }
}

// 将核心函数挂载到 window 对象和 ChatbotUI 命名空间下，便于外部调用
window.handleChatbotSend = handleChatbotSend;
window.handleChatbotStop = handleChatbotStop;
// handlePresetQuestion 使用 ChatbotPreset 中的版本（包含完整的 prompt 注入逻辑）
window.handlePresetQuestion = window.ChatbotPreset?.handlePresetQuestion || function(q) {
  // 降级方案：如果 ChatbotPreset 未加载，使用简单版本
  const input = document.getElementById('chatbot-input');
  if (!input) return;
  input.value = q;
  if (typeof window.handleChatbotSend === 'function') window.handleChatbotSend();
};
window.ChatbotUI = {
  updateChatbotUI,
  initChatbotUI
};

// 当DOM内容加载完成后，执行初始化函数
// 这是确保所有需要的DOM元素都已存在后再进行操作的标准做法
if (document.readyState === 'loading') {
  // 如果文档仍在加载中，则等待 DOMContentLoaded 事件
  document.addEventListener('DOMContentLoaded', initChatbotUI);
} else {
  // 如果文档已经加载完毕，则直接执行初始化
  initChatbotUI();
}
