// ========== Phase 2.1: 性能优化 - DOM 缓存和防抖 ==========
// DOM 元素缓存：避免重复查询
const DOM_CACHE = {
  tabs: {
    ocr: null,
    translation: null,
    chunkCompare: null,
    pdfCompare: null,
    originalFile: null
  },
  layout: {
    title: null,
    meta: null,
    tabsContainer: null
  },
  // 初始化缓存
  init: function() {
    this.tabs.ocr = document.getElementById('tab-ocr');
    this.tabs.translation = document.getElementById('tab-translation');
    this.tabs.chunkCompare = document.getElementById('tab-chunk-compare');
    this.tabs.pdfCompare = document.getElementById('tab-pdf-compare');
    this.tabs.originalFile = document.getElementById('tab-original-file');
    this.layout.title = document.getElementById('fileName');
    this.layout.meta = document.getElementById('fileMeta');
    this.layout.tabsContainer = document.querySelector('.tabs-container');
  },
  // 懒初始化：第一次使用时自动初始化
  ensureInitialized: function() {
    if (!this.tabs.ocr) {
      this.init();
    }
  }
};

// 防抖定时器：防止快速切换标签导致重复渲染
let showTabDebounceTimer = null;
let pendingTab = null;

/**
 * 带防抖的标签切换函数（用户接口）
 * 快速点击多个标签时，只渲染最后一个
 */
function showTab(tab) {
  // 记录待处理的标签
  pendingTab = tab;

  // 清除之前的定时器
  if (showTabDebounceTimer) {
    clearTimeout(showTabDebounceTimer);
  }

  // 设置新的延迟执行（100ms）
  showTabDebounceTimer = setTimeout(() => {
    showTabDebounceTimer = null;
    showTabImmediate(pendingTab);
  }, 100);
}

/**
 * 立即执行标签切换（内部函数）
 * 原 showTab 逻辑移至此处
 */
function showTabImmediate(tab) {
  // 确保 DOM 缓存已初始化
  DOM_CACHE.ensureInitialized();

  // Phase 2.3: 清空批注系统缓存（标签切换时）
  if (window.AnnotationDOMCache && window.AnnotationDOMCache.initialized) {
    window.AnnotationDOMCache.clear();
  }

  // ========== 先尝试清理上一视图的资源（尤其 chunk-compare） ==========
  try {
    const prevTab = window.currentVisibleTabId;
    if (prevTab === 'chunk-compare' && tab !== 'chunk-compare') {
      // 断开观察器并清空优化器缓存
      if (window.ChunkCompareOptimizer && window.ChunkCompareOptimizer.instance && typeof window.ChunkCompareOptimizer.instance.cleanup === 'function') {
        window.ChunkCompareOptimizer.instance.cleanup();
      }
      // 清空分块解析缓存，释放大数组
      if (window.chunkParseCache) {
        Object.keys(window.chunkParseCache).forEach(k => delete window.chunkParseCache[k]);
      }
      // 释放块级原始内容引用
      if (typeof window.__lastChunkCompareTotalBlocks === 'number') {
        for (let i = 0; i <= window.__lastChunkCompareTotalBlocks; i++) {
          try { delete window[`blockRawContent_${i}`]; } catch(e) { /* ignore */ }
        }
        window.__lastChunkCompareTotalBlocks = 0;
      }
      // 清理大型临时数据
      if (window.largeDocumentData) window.largeDocumentData = null;
    }
  } catch (e) {
    console.warn('[showTab] 清理上一视图资源时出错:', e);
  }
  // === 新增：没有翻译内容时禁止切换到翻译和对比页 ===
  if ((tab === 'translation' || tab === 'chunk-compare') && (!data || !data.translation || data.translation.trim() === "")) {
    alert('没有翻译内容，无法显示该页面');
    return;
  }
  // ========== 保证内容标识符始终正确 ==========
  if (tab === 'ocr') {
    window.globalCurrentContentIdentifier = 'ocr';
  } else if (tab === 'translation') {
    window.globalCurrentContentIdentifier = 'translation';
  } else {
    window.globalCurrentContentIdentifier = '';
  }
  // ========== 防抖锁：防止同一 tab 重复渲染 ==========
  if (renderingTab === tab) {
    console.log(`[showTab] Tab ${tab} 正在渲染中，跳过重复渲染`);
    return;
  }
  renderingTab = tab;
  // ================================================
  // 性能测试断点 - 总渲染
  console.time('[性能] showTab_总渲染');
  currentVisibleTabId = tab; // Update global current tab ID
  window.currentVisibleTabId = tab; // 同时更新挂载到 window 对象上的变量
  window.currentBlockTokensForCopy = window.currentBlockTokensForCopy || {}; // Initialize if not exists

  // ========== 新增：用局部变量保存内容标识符 ==========
  let contentIdentifier = '';
  if (tab === 'ocr') {
    contentIdentifier = 'ocr';
  } else if (tab === 'translation') {
    contentIdentifier = 'translation';
  }
  window.globalCurrentContentIdentifier = contentIdentifier;
  console.log('[showTab] 设置 window.globalCurrentContentIdentifier =', contentIdentifier);
  // ==================================================

  if (docIdForLocalStorage) {
    const activeTabKey = `activeTab_${docIdForLocalStorage}`;
    localStorage.setItem(activeTabKey, tab);
    // console.log(`Saved active tab for ${docIdForLocalStorage}: ${tab}`);
  }

  // 使用缓存的 DOM 元素：移除所有标签的 active 状态
  DOM_CACHE.tabs.ocr.classList.remove('active');
  DOM_CACHE.tabs.translation.classList.remove('active');
  // document.getElementById('tab-compare').classList.remove('active'); // 对应按钮已注释，此行也可注释
  DOM_CACHE.tabs.chunkCompare.classList.remove('active');
  if (DOM_CACHE.tabs.pdfCompare) DOM_CACHE.tabs.pdfCompare.classList.remove('active');
  if (DOM_CACHE.tabs.originalFile) DOM_CACHE.tabs.originalFile.classList.remove('active');

  // 恢复顶部区域显示（退出 PDF 对照模式时）- 使用缓存
  if (DOM_CACHE.layout.title) DOM_CACHE.layout.title.style.display = '';
  if (DOM_CACHE.layout.meta) DOM_CACHE.layout.meta.style.display = '';
  if (DOM_CACHE.layout.tabsContainer) DOM_CACHE.layout.tabsContainer.style.display = '';

  let html = '';
  let contentContainerId = ''; // 用于 applyAnnotationsToContent
  let activeContentElement = null; // 用于 applyAnnotationsToContent
  const significantTokenTypes = ['paragraph', 'heading', 'code', 'table', 'blockquote', 'list', 'html', 'hr'];

  // ---- 增加日志 ----
  // 日志现在可以准确反映 globalCurrentContentIdentifier
  console.log(`[showTab - ${tab}] 即将渲染。当前 window.globalCurrentContentIdentifier:`, window.globalCurrentContentIdentifier);
  if (data && data.annotations) {
      console.log(`[showTab - ${tab}] data.annotations (长度 ${data.annotations.length}):`, JSON.parse(JSON.stringify(data.annotations)));
  } else {
      console.log(`[showTab - ${tab}] data.annotations 不可用或为空。`);
  }
  // ---- 日志结束 ----

  if (tab === 'chunk-compare') {
    // 安全校验：需要 ocrChunks/transChunks 同步存在且长度一致
    if (!data || !Array.isArray(data.ocrChunks) || !Array.isArray(data.translatedChunks) || data.ocrChunks.length === 0 || data.translatedChunks.length === 0 || data.ocrChunks.length !== data.translatedChunks.length) {
      DOM_CACHE.tabs.chunkCompare.classList.add('active');
      const warn = `<div class="warning-box" style="padding:12px;border:1px solid #fbbf24;background:#fffbeb;color:#92400e;border-radius:8px;">`
                 + `无法进入"分块对比"：当前记录的原文分块数量与译文分块数量不一致，或缺少分块信息。`
                 + `<br>请先查看"仅OCR/仅翻译"，或重新生成分块以使用对比功能。`
                 + `</div>`;
      document.getElementById('tabContent').innerHTML = warn;
      if (typeof window.refreshTocList === 'function') window.refreshTocList();
      renderingTab = null;
      console.timeEnd && console.timeEnd('[性能] showTab_总渲染');
      return;
    }
  }

  if(tab === 'ocr') {
    DOM_CACHE.tabs.ocr.classList.add('active');
    contentContainerId = 'ocr-content-wrapper';
    let ocrText = data.ocr || '';
    // 性能测试断点 - OCR渲染
    console.time('[性能] OCR分批渲染');
    html = `<h3>OCR内容</h3><div id="${contentContainerId}" class="markdown-body content-wrapper"></div>`;
  } else if(tab === 'translation') {
    DOM_CACHE.tabs.translation.classList.add('active');
    contentContainerId = 'translation-content-wrapper';
    html = `<h3>翻译内容</h3><div id="${contentContainerId}" class="markdown-body content-wrapper"></div>`;
    console.time('[性能] 翻译分批渲染');
  } else if (tab === 'original-file') {
    // ========== 原始 PDF 查看器（iframe 嵌入官方 pdf.js viewer） ==========
    if (DOM_CACHE.tabs.originalFile) DOM_CACHE.tabs.originalFile.classList.add('active');

    // 检查数据
    if (!data.metadata || !data.metadata.originalPdfBase64) {
      document.getElementById('tabContent').innerHTML = `
        <div style="padding:40px;text-align:center;color:#666;">
          <i class="fa fa-file-pdf-o" style="font-size:48px;margin-bottom:12px;display:block;"></i>
          <p>当前记录没有保存原始 PDF 数据。</p>
        </div>`;
      if (typeof window.refreshTocList === 'function') window.refreshTocList();
      renderingTab = null;
      console.timeEnd && console.timeEnd('[性能] showTab_总渲染');
      return;
    }

    // 构建 iframe 容器
    document.getElementById('tabContent').innerHTML = `
      <div id="pdf-iframe-wrapper" style="
        width: 100%; height: calc(100vh - 180px);
        min-height: 500px; position: relative;
        background: #525659;
      ">
        <div id="pdf-viewer-loading" style="
          position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
          color:#ccc;font-size:16px;text-align:center;
        ">
          <i class="fa fa-spinner fa-spin" style="font-size:32px;display:block;margin-bottom:12px;"></i>
          正在加载 PDF 查看器...
        </div>
        <iframe id="pdf-viewer-iframe" style="
          width:100%; height:100%; border:none; display:none;
        " allowfullscreen></iframe>
      </div>`;

    // 同源直接调用 viewer API 打开 PDF
    (async () => {
      try {
        // base64 → Uint8Array
        const base64 = data.metadata.originalPdfBase64;
        const raw = atob(base64);
        const bytes = new Uint8Array(raw.length);
        for (let i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i);

        // 加载 viewer.html
        const viewerBase = '../../public/pdfjs/web/viewer.html';

        const iframe = document.getElementById('pdf-viewer-iframe');
        const loading = document.getElementById('pdf-viewer-loading');

        if (!iframe) return; // 标签已切换

        iframe.onload = () => {
          if (loading) loading.style.display = 'none';
          if (iframe) iframe.style.display = 'block';
          // 等待 viewer.js 初始化完成后直接调用 open()
          let retries = 0;
          const maxRetries = 30;
          const openPdf = () => {
            retries++;
            try {
              const viewerApp = iframe.contentWindow && iframe.contentWindow.PDFViewerApplication;
              if (viewerApp && viewerApp.initialized) {
                // 直接在 iframe 上下文中创建 Blob URL 并打开（与官方文件上传一致）
                const iframeBlob = new iframe.contentWindow.Blob([bytes], { type: 'application/pdf' });
                const iframeBlobUrl = iframe.contentWindow.URL.createObjectURL(iframeBlob);
                viewerApp.open({ url: iframeBlobUrl, originalUrl: data.name || 'document.pdf' });
                console.log('[PDF Viewer] 直接调用 open() 成功 (第' + retries + '次尝试)');
              } else if (retries < maxRetries) {
                setTimeout(openPdf, 200);
              } else {
                console.error('[PDF Viewer] viewer 初始化超时');
              }
            } catch(e) {
              console.error('[PDF Viewer] open 调用失败:', e);
              if (retries < maxRetries) setTimeout(openPdf, 200);
            }
          };
          setTimeout(openPdf, 300);
        };
        iframe.src = viewerBase;

      } catch (err) {
        console.error('[PDF Viewer] 加载失败:', err);
        const wrapper = document.getElementById('pdf-iframe-wrapper');
        if (wrapper) wrapper.innerHTML = `
          <div style="padding:40px;color:#ff6b6b;text-align:center;">
            <i class="fa fa-exclamation-triangle" style="font-size:32px;display:block;margin-bottom:12px;"></i>
            PDF 加载失败: ${err.message}
          </div>`;
      } finally {
        renderingTab = null;
        console.timeEnd && console.timeEnd('[性能] showTab_总渲染');
      }
    })();

    if (typeof window.refreshTocList === 'function') window.refreshTocList();
    return; // 异步渲染，提前返回

  } else if (tab === 'pdf-compare') {
    // ========== MinerU PDF 对照视图 ==========
    if (DOM_CACHE.tabs.pdfCompare) DOM_CACHE.tabs.pdfCompare.classList.add('active');

    // 隐藏顶部区域以获得更大空间 - 使用缓存
    if (DOM_CACHE.layout.title) DOM_CACHE.layout.title.style.display = 'none';
    if (DOM_CACHE.layout.meta) DOM_CACHE.layout.meta.style.display = 'none';
    if (DOM_CACHE.layout.tabsContainer) DOM_CACHE.layout.tabsContainer.style.display = 'none';

    // 验证必要数据
    if (!data.metadata || !data.metadata.originalPdfBase64 || !data.metadata.contentListJson || !data.metadata.translatedContentList) {
      const warn = `<div class="warning-box" style="padding:12px;border:1px solid #fbbf24;background:#fffbeb;color:#92400e;border-radius:8px;">`
                 + `无法进入"PDF对照"：缺少必要的 MinerU 结构化翻译数据。`
                 + `</div>`;
      document.getElementById('tabContent').innerHTML = warn;
      if (typeof window.refreshTocList === 'function') window.refreshTocList();
      renderingTab = null;
      console.timeEnd && console.timeEnd('[性能] showTab_总渲染');
      return;
    }

    // 设置 HTML 容器
    document.getElementById('tabContent').innerHTML = '<div id="pdf-compare-container"></div>';

    // 创建并初始化 PDF 对照视图
    (async () => {
      try {
        // 清理之前的实例
        if (window.pdfCompareViewInstance) {
          window.pdfCompareViewInstance.destroy();
        }

        // 创建新实例
        const pdfCompareView = new PDFCompareView();
        window.pdfCompareViewInstance = pdfCompareView;

        console.log('[PDFCompareView] 开始初始化 PDF 对照视图');
        await pdfCompareView.initialize(
          data.metadata.originalPdfBase64,
          data.metadata.contentListJson,
          data.metadata.translatedContentList,
          data.metadata.layoutJson  // 传入 layoutJson
        );

        // 为多轮检索生成chunks（如果还没有的话）
        if (!data.ocrChunks || data.ocrChunks.length === 0) {
          console.log('[PDFCompareView] 检测到缺少chunks数据，尝试从contentListJson生成');

          if (typeof generateChunksFromContentList === 'function') {
            const chunks = generateChunksFromContentList(
              data.metadata.contentListJson,
              data.metadata.translatedContentList
            );
            window.data.ocrChunks = chunks.ocrChunks;
            window.data.translatedChunks = chunks.translatedChunks;
            console.log(`[PDFCompareView] 已生成 ${chunks.ocrChunks.length} 个chunks用于多轮检索`);
          } else {
            // 备用方案：从完整文本生成chunks
            if (data.ocr && typeof generateChunksFromFullText === 'function') {
              const chunks = generateChunksFromFullText(data.ocr, data.translation);
              window.data.ocrChunks = chunks.ocrChunks;
              window.data.translatedChunks = chunks.translatedChunks;
              console.log(`[PDFCompareView] 使用备用方案从完整文本生成了 ${chunks.ocrChunks.length} 个chunks`);
            } else {
              console.warn('[PDFCompareView] 无法生成chunks，多轮检索功能将受限');
            }
          }
        } else {
          console.log(`[PDFCompareView] 使用现有的 ${data.ocrChunks.length} 个chunks`);
        }

        await pdfCompareView.render('pdf-compare-container');
        console.log('[PDFCompareView] PDF 对照视图渲染完成');
      } catch (error) {
        console.error('[PDFCompareView] 渲染失败:', error);
        document.getElementById('pdf-compare-container').innerHTML = `
          <div class="error-box" style="padding:12px;border:1px solid #ef4444;background:#fef2f2;color:#991b1b;border-radius:8px;">
            PDF 对照视图加载失败: ${error.message}
          </div>
        `;
      } finally {
        renderingTab = null;
        console.timeEnd && console.timeEnd('[性能] showTab_总渲染');
      }
    })();

    // 提前返回，因为是异步渲染
    if (typeof window.refreshTocList === 'function') window.refreshTocList();
    return;
  } else if (tab === 'chunk-compare') {
    // 性能监控：记录分块对比开始时间
    window.chunkCompareStartTime = performance.now();
    console.log(`[性能] 开始渲染分块对比，总块数: ${data.ocrChunks ? data.ocrChunks.length : 0}`);

    // ========== 超长文本降级策略开关 ==========
    (function computeLargeDocFlag(){
      const LARGE_TEXT_THRESHOLD = 120000; // 8万字阈值
      let totalLen = 0;
      try {
        if (Array.isArray(data.ocrChunks)) {
          for (let i = 0; i < data.ocrChunks.length; i++) totalLen += (data.ocrChunks[i] ? data.ocrChunks[i].length : 0);
        }
        if (Array.isArray(data.translatedChunks)) {
          for (let i = 0; i < data.translatedChunks.length; i++) totalLen += (data.translatedChunks[i] ? data.translatedChunks[i].length : 0);
        }
      } catch(e) { /* ignore */ }
      window.disableEqualizeForLargeDoc = totalLen > LARGE_TEXT_THRESHOLD;
      console.log(`[Chunk-Compare] 文本总长度=${totalLen}，等高降级=${window.disableEqualizeForLargeDoc}`);
      // 记录用于后续清理的块数量
      try { window.__lastChunkCompareTotalBlocks = (Array.isArray(data.ocrChunks) ? data.ocrChunks.length : 0); } catch(e) { window.__lastChunkCompareTotalBlocks = 0; }
    })();

    // window.globalCurrentContentIdentifier = ''; // 已在函数开头正确设置
    DOM_CACHE.tabs.chunkCompare.classList.add('active');
    if (data.ocrChunks && data.ocrChunks.length > 0 && data.translatedChunks && data.translatedChunks.length === data.ocrChunks.length) {
        // 使用优化器进行分块对比渲染
        if (false && window.ChunkCompareOptimizer && window.ChunkCompareOptimizer.instance) {
            console.log('[性能] 使用优化后的分块对比渲染器');
            html = window.ChunkCompareOptimizer.instance.optimizeChunkComparison(
                data.ocrChunks,
                data.translatedChunks,
                {
                    images: data.images,
                    isOriginalFirst: window.isOriginalFirstInChunkCompare !== false
                }
            );
        } else {
            // 回退到原有渲染逻辑
            console.log('[性能] 使用旧版分块对比渲染');
            html = `
              <div class="chunk-compare-title-bar">
                <h3>分块对比</h3>
                <button id="swap-chunks-btn" title="切换原文/译文位置">⇆</button>
              </div>
              <div class="chunk-compare-container">
            `;
            // 继续使用原有的渲染逻辑...
        }
        // 强制走旧版渲染逻辑（禁用优化器路径）
        if (true) {
        /**
         * 解析Markdown文本为逻辑块数组，主要基于标题进行分割。
         * 代码块 (```...```) 会被视为单个块的一部分，不会被分割。
         * @param {string} md - Markdown文本。
         * @returns {Array<Object>} 每个对象包含 `{ content: string }`。
         */
        function parseMarkdownBlocks(md) {
          const lines = (md || '').split(/\r?\n/);
          const blocks = [];
          let buffer = [];
          let inCode = false;
          let isFirstBlock = true;
          function flush() {
            if (buffer.length) {
              blocks.push({ content: buffer.join('\n') });
              buffer = [];
            }
          }
          for (let i = 0; i < lines.length; i++) {
            const line = lines[i];
            if (/^\s*```/.test(line)) { // 代码块
              inCode = !inCode;
              buffer.push(line);
              continue;
            }
            if (inCode) {
              buffer.push(line);
              continue;
            }
            if (/^\s*#/.test(line)) { // 标题作为新分块的起点
              if (!isFirstBlock) flush();
              isFirstBlock = false;
              buffer.push(line);
              continue;
            }
            // 普通内容、列表、空行等都合并到当前块
            buffer.push(line);
          }
          flush();
          return blocks;
        }
        /**
         * 对齐两组Markdown逻辑块，用于并排显示。
         * 简单地按索引逐个配对，如果某一组块少，则对应位置为空字符串。
         * @param {Array<Object>} blocks1 - 第一组块。
         * @param {Array<Object>} blocks2 - 第二组块。
         * @returns {Array<Array<string>>} 每个内部数组包含两个字符串 `[block1_content, block2_content]`。
         */
        function alignBlocks(blocks1, blocks2) {
          // 简单按类型和顺序对齐
          const maxLen = Math.max(blocks1.length, blocks2.length);
          const aligned = [];
          for (let i = 0; i < maxLen; i++) {
            aligned.push([
              blocks1[i] ? blocks1[i].content : '',
              blocks2[i] ? blocks2[i].content : ''
            ]);
          }
          return aligned;
        }
        /**
         * 渲染单个OCR块和其对应的翻译块的对齐视图，支持分层结构。
         * - 它首先使用 `parseMarkdownBlocks` 将OCR和翻译文本分割成小块（基于标题）。
         * - 然后使用 `alignBlocks` 对齐这些小块。
         * - 为每个对齐的小块对生成并排的HTML结构，用于显示原文和译文。
         * - 提供工具栏按钮，用于切换显示模式（对比、仅原文、仅译文）、复制整块内容以及导航到上下块。
         * - 原始块内容存储在 `window.blockRawContent_[blockIndex]` 中，供复制功能使用。
         *
         * @param {string} ocrChunk - OCR文本块。
         * @param {string} translatedChunk - 对应的翻译文本块。
         * @param {Array<Object>} images - 与此文档关联的图片数据。
         * @param {number} blockIndex - 当前大块在整个文档分块中的索引。
         * @param {number} totalBlocks - 文档分块的总数。
         * @returns {string} 生成的HTML字符串，用于显示对齐的块内容。
         */
        function renderLevelAlignedFlex(ocrChunk, translatedChunk, images, blockIndex, totalBlocks) {
          // 性能优化：缓存解析结果避免重复计算
          const cacheKey = `${blockIndex}_${ocrChunk.length}_${translatedChunk.length}`;
          if (window.chunkParseCache && window.chunkParseCache[cacheKey]) {
            const cachedResult = window.chunkParseCache[cacheKey];
            const ocrBlocks = cachedResult.ocrBlocks;
            const transBlocks = cachedResult.transBlocks;
            const aligned = cachedResult.aligned;
            let showMode = window[`showMode_block_${blockIndex}`] || 'both';

            // 使用缓存的解析结果渲染HTML
            return renderAlignedHTML(ocrBlocks, transBlocks, aligned, images, blockIndex, totalBlocks, showMode);
          }

          const ocrBlocks = parseMarkdownBlocks(ocrChunk);
          const transBlocks = parseMarkdownBlocks(translatedChunk);
          const aligned = alignBlocks(ocrBlocks, transBlocks);

          // 缓存解析结果
          if (!window.chunkParseCache) window.chunkParseCache = {};
          window.chunkParseCache[cacheKey] = { ocrBlocks, transBlocks, aligned };

          let showMode = window[`showMode_block_${blockIndex}`] || 'both';
          return renderAlignedHTML(ocrBlocks, transBlocks, aligned, images, blockIndex, totalBlocks, showMode);
        }

        /**
         * 渲染对齐后的HTML内容
         */
        function renderAlignedHTML(ocrBlocks, transBlocks, aligned, images, blockIndex, totalBlocks, showMode) {
          // ========== 辅助：媒体与段落对齐增强 ==========
          function normalizeHtml(html) {
            return (html || '')
              .replace(/\s+/g, ' ')
              .replace(/\u00A0/g, ' ')
              .trim();
          }
          function extractFirstMatch(html, regex) {
            const m = (html || '').match(regex);
            if (!m) return { match: null, rest: html };
            const before = html.slice(0, m.index);
            const after = html.slice(m.index + m[0].length);
            return { match: m[0], rest: before + after };
          }
          function extractFirstTable(html) {
            // 支持 HTML 表格；Markdown 表格在本阶段不易可靠识别，先处理 HTML
            const res = extractFirstMatch(html, /<table[\s\S]*?<\/table>/i);
            return { table: res.match, rest: res.rest };
          }
          function extractFirstImage(html) {
            // 支持 <img> 或 markdown 图片
            const imgHtml = extractFirstMatch(html, /<img\b[\s\S]*?>/i);
            if (imgHtml.match) return { image: imgHtml.match, rest: imgHtml.rest };
            const mdImg = extractFirstMatch(html, /!\[[^\]]*\]\([^\)]+\)/);
            return { image: mdImg.match, rest: mdImg.rest };
          }
          function isSameImage(imgA, imgB) {
            if (!imgA || !imgB) return false;
            const srcA = (imgA.match(/src\s*=\s*"([^"]+)"/) || [])[1] || (imgA.match(/\]\(([^\)]+)\)/) || [])[1];
            const srcB = (imgB.match(/src\s*=\s*"([^"]+)"/) || [])[1] || (imgB.match(/\]\(([^\)]+)\)/) || [])[1];
            if (!srcA || !srcB) return false;
            return normalizeHtml(srcA) === normalizeHtml(srcB);
          }
          function areTablesSimilar(tblA, tblB) {
            if (!tblA || !tblB) return false;
            const a = normalizeHtml(tblA.replace(/<thead[\s\S]*?<\/thead>/ig, '').replace(/<tbody[\s\S]*?<\/tbody>/ig, '').replace(/<[^>]+>/g, ''));
            const b = normalizeHtml(tblB.replace(/<thead[\s\S]*?<\/thead>/ig, '').replace(/<tbody[\s\S]*?<\/tbody>/ig, '').replace(/<[^>]+>/g, ''));
            if (!a || !b) return false;
            const lenRatio = Math.min(a.length, b.length) / Math.max(a.length, b.length);
            return lenRatio >= 0.7;
          }
          function splitParagraphs(text) {
            return (text || '')
              .split(/\n{2,}/)
              .map(s => s.trim())
              .filter(Boolean);
          }

          // 统一去掉段落首尾多余空格/空行，避免不可见换行导致高度不一致
          function stripEdgeWhitespace(md) {
            if (!md) return md;
            // 标准化不可见空白（NBSP、零宽字符）
            md = md.replace(/\u00A0/g, ' '); // NBSP → 普通空格
            md = md.replace(/[\u200B-\u200D\uFEFF]/g, ''); // 零宽空白
            md = md.replace(/^\uFEFF/, ''); // BOM
            md = md.replace(/^[\s\t\r\n]+/, '');    // 开头空白/换行
            md = md.replace(/[\s\t\r\n]+$/, '');    // 结尾空白/换行
            // 再次清理尾随 NBSP（有的浏览器不把 NBSP 视为 \s）
            md = md.replace(/\u00A0+$/, '');
            return md;
          }

          // 判断是否包含表格语法（markdown 管道表格或已渲染 HTML 表格）
          function containsTableSyntax(src) {
            if (!src) return false;
            if (/<table\b/i.test(src)) return true; // 已渲染 HTML 表格
            // 仅当“块的起始位置”就是 Markdown 表格表头，才认为是表格
            const lines = src.split(/\n/).map(l => (l || '').trim());
            // 找到首个非空行
            let i = 0; while (i < lines.length && lines[i] === '') i++;
            if (i >= lines.length - 1) return false;
            const a = lines[i];
            // 找到表头后的首个非空行
            let j = i + 1; while (j < lines.length && lines[j] === '') j++;
            if (j >= lines.length) return false;
            const b = lines[j];
            const looksLikeHeader = /^\|.*\|$/.test(a);
            const looksLikeDivider = /^\|\s*:?[-]{2,}.*\|$/.test(b);
            return looksLikeHeader && looksLikeDivider;
          }

          // ========== 新增：对比模式“软换行” ===========
          function softWrapLongFormulasInCompare(container) {
            if (!container || !window.katex) return;
            const blocks = container.querySelectorAll('.align-content .katex-block');
            blocks.forEach(function(el){
              try {
                const parent = el.parentElement;
                const cw = parent ? parent.clientWidth : 0;
                const rect = el.getBoundingClientRect();
                const w = rect.width || 0;
                const tex = el.getAttribute('data-original-text') || '';
                if (!tex || /\\begin\{|\\\\\\\n/.test(tex)) return; // 已有环境/显式换行不处理
                if (cw > 0 && w > cw * 1.05) {
                  const wrapped = buildWrappedTeX(tex, Math.max(48, Math.floor(cw / 7)));
                  try {
                    const html = katex.renderToString(wrapped, { displayMode: true, throwOnError: true, strict: 'ignore', output: 'html' });
                    el.innerHTML = html;
                  } catch (e) { /* ignore */ }
                }
              } catch (e) { /* ignore */ }
            });
          }

          function buildWrappedTeX(tex, maxLen) {
            const src = String(tex || '').replace(/\s+/g, ' ').trim();
            if (/\\begin\{|\\end\{|aligned|matrix|cases|array/.test(src)) return src;
            const hasEq = /=/.test(src);
            const parts = [];
            let buf = '';
            let depthBrace = 0;
            for (let i = 0; i < src.length; i++) {
              const ch = src[i];
              if (ch === '{') depthBrace++;
              if (ch === '}') depthBrace = Math.max(0, depthBrace - 1);
              buf += ch;
              if (depthBrace === 0 && (ch === ',' || ch === ';' || ch === '+' || ch === '-' || ch === '=')) {
                if (buf.length >= maxLen) { parts.push(buf.trim()); buf = ''; }
              }
              if (buf.length >= maxLen * 1.5) { parts.push(buf.trim()); buf = ''; }
            }
            if (buf.trim()) parts.push(buf.trim());
            if (parts.length <= 1) return src;
            const lines = parts.map(function(line){ return hasEq ? line.replace(/=\s*/, '&= ') : line; });
            return `\\begin{aligned} ${lines.join(' \\ ')} \\ \\end{aligned}`;
          }

          // 针对整块层级做一次媒体“拿出来”与对齐增强
          let oWhole = (ocrBlocks || []).map(b => b.content).join('\n\n');
          let tWhole = (transBlocks || []).map(b => b.content).join('\n\n');

          let hoistedParts = [];

          // 表格不再抽出合并，保持左右对齐并在开屏时逐对等高微调

          // 3) 图片相同则抽出一个显示
          const exImgO = extractFirstImage(oWhole);
          const exImgT = extractFirstImage(tWhole);
          if (exImgO.image && exImgT.image && isSameImage(exImgO.image, exImgT.image)) {
            hoistedParts.push({ type: 'merged-image', html: exImgO.image });
            oWhole = exImgO.rest.trim();
            tWhole = exImgT.rest.trim();
          }

          // 4) 对剩余内容尝试逐段落对齐（仅当段落数一致）
          let paragraphPairs = null;
          const parasO = splitParagraphs(oWhole);
          const parasT = splitParagraphs(tWhole);
          if (parasO.length > 0 && parasO.length === parasT.length) {
            paragraphPairs = parasO.map((p, i) => [p, parasT[i]]);
          }

          // 在分块对比内部也尝试使用自定义渲染器
          // 注意：这里的 annotations 应该是整个文档的，contentIdentifier 需要根据当前块是原文还是译文来确定
          // 为了简化，我们暂时假设分块对比中的内容不直接参与这种精细的预标注，
          // 或者需要更复杂的逻辑来传递正确的 contentIdentifier
          // MODIFIED: Pass empty array for annotations in chunk-compare mode to disable highlights/annotations
          const annotationsForChunkRender = [];
          const ocrRenderer = createCustomMarkdownRenderer(annotationsForChunkRender, 'ocr', MarkdownProcessor.renderWithKatexFailback);
          const transRenderer = createCustomMarkdownRenderer(annotationsForChunkRender, 'translation', MarkdownProcessor.renderWithKatexFailback);

          // 整块复制按钮
          let html = `
            <div class="block-toolbar" data-block-toolbar="${blockIndex}">
              <div class="block-toolbar-left">
                <span class="block-mode-btn ${showMode === 'both' ? 'active' : ''}" data-mode="both" data-block="${blockIndex}">对比</span>
                <span class="block-mode-btn ${showMode === 'ocr' ? 'active' : ''}" data-mode="ocr" data-block="${blockIndex}">原文</span>
                <span class="block-mode-btn ${showMode === 'trans' ? 'active' : ''}" data-mode="trans" data-block="${blockIndex}">译文</span>
                <button class="block-copy-btn" data-block="${blockIndex}" title="复制本块内容">复制本块</button>
              </div>
              <div class="block-toolbar-right">
                ${blockIndex > 0 ? `<button class="block-nav-btn" data-dir="prev" data-block="${blockIndex}" title="上一段">↑</button>` : ''}
                ${blockIndex < totalBlocks-1 ? `<button class="block-nav-btn" data-dir="next" data-block="${blockIndex}" title="下一段">↓</button>` : ''}
              </div>
            </div>
          `;

          // 性能优化：批量构建HTML字符串（按序：先 hoisted，再对齐对，再fallback）
          const alignedHTML = [];
          // 先渲染抽出的媒体（图片单列）
          if (hoistedParts.length > 0) {
            hoistedParts.forEach((part, idx) => {
              if (part.type === 'merged-image') {
                const rendered = (window.MarkdownProcessor && window.MarkdownProcessor.renderWithKatexFailback)
                  ? window.MarkdownProcessor.renderWithKatexFailback(window.MarkdownProcessor.safeMarkdown(part.html, images))
                  : part.html;
                alignedHTML.push(`
                  <div class="align-flex block-flex block-flex-${blockIndex} block-mode-both" data-block="${blockIndex}" data-align-index="hoisted-${idx}">
                    <div class="align-block" style="flex:1 1 100%">
                      <div class="align-title"><span>图片</span></div>
                      <div class="align-content markdown-body">${rendered}</div>
                    </div>
                  </div>
                `);
              }
            });
          }

          // 再渲染逐段落对齐（若可用）
          if (paragraphPairs) {
            for (let i = 0; i < paragraphPairs.length; i++) {
              const [pO, pT] = paragraphPairs[i];
              const isTablePair = containsTableSyntax(pO) && containsTableSyntax(pT);
              const titleLeft = isTablePair ? '表格' : '原文';
              const titleRight = isTablePair ? '表格' : '译文';
              alignedHTML.push(`
                <div class="align-flex block-flex block-flex-${blockIndex} ${isTablePair ? 'table-pair ' : ''}${showMode==='ocr'?'block-mode-ocr-only':showMode==='trans'?'block-mode-trans-only':'block-mode-both'}" data-block="${blockIndex}" data-align-index="para-${i}">
                  <div class="align-block align-block-ocr">
                    <div class="align-title">
                      <span>${titleLeft}</span>
                      <div class="align-actions">
                        <button class="block-struct-copy-btn block-icon-btn" data-block="${blockIndex}" data-type="ocr" data-idx="${i}" title="复制原文结构">
                          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
                        </button>
                        <button class="block-edit-btn block-icon-btn" data-block="${blockIndex}" data-type="ocr" data-idx="${i}" title="编辑此段（仅供预览）">
                          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4Z"></path></svg>
                        </button>
                        <button class="block-edit-reset-btn block-icon-btn" data-block="${blockIndex}" data-type="ocr" data-idx="${i}" title="重置为原文" style="display:none;">
                          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg>
                        </button>
                      </div>
                    </div>
                    <div class="align-content markdown-body" data-raw-markdown="${encodeURIComponent(stripEdgeWhitespace(pO))}">${MarkdownProcessor.renderWithKatexFailback(MarkdownProcessor.safeMarkdown(stripEdgeWhitespace((()=>{const key = `chunkOverride_${window.docIdForLocalStorage||'default'}_${blockIndex}_${i}_ocr`; const ov = localStorage.getItem(key); return ov ? ov : pO;})()), images), isOriginalFirstInChunkCompare ? ocrRenderer : transRenderer)}</div>
                    <div class="align-edit-panel" style="display:none;">
                      <textarea class="align-edit-area" style="width:100%;min-height:120px;box-sizing:border-box;"></textarea>
                      <div class="align-edit-actions" style="margin-top:6px;display:flex;gap:8px;">
                        <button class="align-edit-save" data-block="${blockIndex}" data-type="ocr" data-idx="para-${i}">保存</button>
                        <button class="align-edit-cancel" data-block="${blockIndex}" data-type="ocr" data-idx="para-${i}">取消</button>
                      </div>
                    </div>
                  </div>
                  <div class="splitter" title="拖动调整比例"></div>
                  <div class="align-block align-block-trans">
                    <div class="align-title">
                      <span>${titleRight}</span>
                      <div class="align-actions">
                        <button class="block-struct-copy-btn block-icon-btn" data-block="${blockIndex}" data-type="trans" data-idx="${i}" title="复制译文结构">
                          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
                        </button>
                        <button class="block-edit-btn block-icon-btn" data-block="${blockIndex}" data-type="trans" data-idx="${i}" title="编辑此段（仅供预览）">
                          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4Z"></path></svg>
                        </button>
                        <button class="block-edit-reset-btn block-icon-btn" data-block="${blockIndex}" data-type="trans" data-idx="${i}" title="重置为原文" style="display:none;">
                          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg>
                        </button>
                      </div>
                    </div>
                    <div class="align-content markdown-body" data-raw-markdown="${encodeURIComponent(stripEdgeWhitespace(pT))}">${MarkdownProcessor.renderWithKatexFailback(MarkdownProcessor.safeMarkdown(stripEdgeWhitespace((()=>{const key = `chunkOverride_${window.docIdForLocalStorage||'default'}_${blockIndex}_${i}_trans`; const ov = localStorage.getItem(key); return ov ? ov : pT;})()), images), isOriginalFirstInChunkCompare ? transRenderer : ocrRenderer)}</div>
                    <div class="align-edit-panel" style="display:none;">
                      <textarea class="align-edit-area" style="width:100%;min-height:120px;"></textarea>
                      <div class="align-edit-actions" style="margin-top:6px;display:flex;gap:8px;">
                        <button class="align-edit-save" data-block="${blockIndex}" data-type="trans" data-idx="para-${i}">保存</button>
                        <button class="align-edit-cancel" data-block="${blockIndex}" data-type="trans" data-idx="para-${i}">取消</button>
                      </div>
                    </div>
                  </div>
                </div>
              `);
            }
          } else {
            // 最后 fallback：使用原有的 aligned 对
            for (let i = 0; i < aligned.length; i++) {
              const isTablePair = containsTableSyntax(aligned[i][0]) && containsTableSyntax(aligned[i][1]);
              const titleLeft = isTablePair ? '表格' : '原文';
              const titleRight = isTablePair ? '表格' : '译文';
              alignedHTML.push(`
                <div class="align-flex block-flex block-flex-${blockIndex} ${isTablePair ? 'table-pair ' : ''}${showMode==='ocr'?'block-mode-ocr-only':showMode==='trans'?'block-mode-trans-only':'block-mode-both'}" data-block="${blockIndex}" data-align-index="${i}">
                   <div class="align-block align-block-ocr">
                     <div class="align-title">
                      <span>${titleLeft}</span>
                      <div class="align-actions">
                        <button class="block-struct-copy-btn block-icon-btn" data-block="${blockIndex}" data-type="ocr" data-idx="${i}" title="复制原文结构">
                          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
                        </button>
                        <button class="block-edit-btn block-icon-btn" data-block="${blockIndex}" data-type="ocr" data-idx="${i}" title="编辑此段（仅供预览）">
                          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4Z"></path></svg>
                        </button>
                        <button class="block-edit-reset-btn block-icon-btn" data-block="${blockIndex}" data-type="ocr" data-idx="${i}" title="重置为原文" style="display:none;">
                          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg>
                        </button>
                      </div>
                     </div>
                    <div class="align-content markdown-body" data-raw-markdown="${encodeURIComponent(stripEdgeWhitespace(aligned[i][0]))}">${MarkdownProcessor.renderWithKatexFailback(MarkdownProcessor.safeMarkdown(stripEdgeWhitespace((()=>{const key = `chunkOverride_${window.docIdForLocalStorage||'default'}_${blockIndex}_${i}_ocr`; const ov = localStorage.getItem(key); return ov ? ov : aligned[i][0];})()), images), isOriginalFirstInChunkCompare ? ocrRenderer : transRenderer)}</div>
                    <div class="align-edit-panel" style="display:none;">
                      <textarea class="align-edit-area" style="width:100%;min-height:120px;"></textarea>
                      <div class="align-edit-actions" style="margin-top:6px;display:flex;gap:8px;">
                        <button class="align-edit-save" data-block="${blockIndex}" data-type="ocr" data-idx="${i}">保存</button>
                        <button class="align-edit-cancel" data-block="${blockIndex}" data-type="ocr" data-idx="${i}">取消</button>
                      </div>
                    </div>
                  </div>
                  <div class="splitter" title="拖动调整比例"></div>
                   <div class="align-block align-block-trans">
                     <div class="align-title">
                      <span>${titleRight}</span>
                      <div class="align-actions">
                        <button class="block-struct-copy-btn block-icon-btn" data-block="${blockIndex}" data-type="trans" data-idx="${i}" title="复制译文结构">
                          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
                        </button>
                        <button class="block-edit-btn block-icon-btn" data-block="${blockIndex}" data-type="trans" data-idx="${i}" title="编辑此段（仅供预览）">
                          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4Z"></path></svg>
                        </button>
                        <button class="block-edit-reset-btn block-icon-btn" data-block="${blockIndex}" data-type="trans" data-idx="${i}" title="重置为原文" style="display:none;">
                          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg>
                        </button>
                      </div>
                     </div>
                    <div class="align-content markdown-body" data-raw-markdown="${encodeURIComponent(stripEdgeWhitespace(aligned[i][1]))}">${MarkdownProcessor.renderWithKatexFailback(MarkdownProcessor.safeMarkdown(stripEdgeWhitespace((()=>{const key = `chunkOverride_${window.docIdForLocalStorage||'default'}_${blockIndex}_${i}_trans`; const ov = localStorage.getItem(key); return ov ? ov : aligned[i][1];})()), images), isOriginalFirstInChunkCompare ? transRenderer : ocrRenderer)}</div>
                    <div class="align-edit-panel" style="display:none;">
                      <textarea class="align-edit-area" style="width:100%;min-height:120px;"></textarea>
                      <div class="align-edit-actions" style="margin-top:6px;display:flex;gap:8px;">
                        <button class="align-edit-save" data-block="${blockIndex}" data-type="trans" data-idx="${i}">保存</button>
                        <button class="align-edit-cancel" data-block="${blockIndex}" data-type="trans" data-idx="${i}">取消</button>
                      </div>
                    </div>
                  </div>
                </div>
              `);
            }
          }

          html += alignedHTML.join('');

          // 记录原始内容，供复制用
          window[`blockRawContent_${blockIndex}`] = aligned;
          return html;
        }

        // 恢复原始渲染逻辑：渲染每个分块，增加唯一id
        for (let i = 0; i < data.ocrChunks.length; i++) {
            const ocrChunk = data.ocrChunks[i] || '';
            const translatedChunk = data.translatedChunks[i] || '';
            let blockHtmlToRender;
            let outerBlockTitle;

            if (isOriginalFirstInChunkCompare) {
                // 当原文在左侧时，调用 renderLevelAlignedFlex(原文, 译文)
                // window[`blockRawContent_${i}`] 将存储 [原文子块, 译文子块]
                blockHtmlToRender = renderLevelAlignedFlex(ocrChunk, translatedChunk, data.images, i, data.ocrChunks.length);
                outerBlockTitle = `原文块 ${i+1}`;
            } else {
                // 当译文在左侧时，调用 renderLevelAlignedFlex(译文, 原文)
                // window[`blockRawContent_${i}`] 将存储 [译文子块, 原文子块]
                blockHtmlToRender = renderLevelAlignedFlex(translatedChunk, ocrChunk, data.images, i, data.ocrChunks.length);
                outerBlockTitle = `译文块 ${i+1}`; // 标题也反映左侧内容
            }
            html += `<div class="chunk-pair">`;
            html += `<div id="block-${i}" class="block-outer">`; // id 用于导航
            html += `<h4>${outerBlockTitle}</h4>`;
            html += blockHtmlToRender;
            html += `</div></div>`;
        }
        // 绑定每个分块的切换按钮和导航按钮事件
        setTimeout(() => {
          // 拖动分割条实现 - 使用CSS变量而不是直接设置样式
          let ratio = window.chunkCompareRatio;
          if (typeof ratio !== 'number' || isNaN(ratio)) ratio = 0.5;
          window.chunkCompareRatio = ratio;

          function applyRatioToAll() {
            const currentRatio = window.chunkCompareRatio || 0.5;
            document.querySelectorAll('.align-flex').forEach(flex => {
              // 优先使用每对的等高比例
              if (flex.hasAttribute('data-equalized')) {
                const ratioSaved = parseFloat(flex.getAttribute('data-equalized'));
                if (isFinite(ratioSaved)) {
                  flex.style.setProperty('--ocr-ratio', (ratioSaved * 100) + '%');
                  flex.style.setProperty('--trans-ratio', ((1 - ratioSaved) * 100) + '%');
                  return;
                }
              }
              const hasTableOCR = !!flex.querySelector('.align-block-ocr table');
              const hasTableTRANS = !!flex.querySelector('.align-block-trans table');
              const anyTable = hasTableOCR || hasTableTRANS;
              // 表格对若未等高，临时使用0.5；文本对使用全局
              const ratio = anyTable ? 0.5 : currentRatio;
              flex.style.setProperty('--ocr-ratio', (ratio * 100) + '%');
              flex.style.setProperty('--trans-ratio', ((1 - ratio) * 100) + '%');
            });
          }

          // 一次性、逐卡片的简化等高（避免视口进入时抖动）：
          // 在当前布局基础上，以 0.5 为初始，按 hL/(hL+hR) 估算每对的专属比例，仅设置一次
          function equalizePairsOnce() {
            if (window.disableEqualizeForLargeDoc) {
              // 超长文本时跳过逐对等高，避免大量 reflow
              return;
            }
            const pairs = document.querySelectorAll('.align-flex');
            pairs.forEach(flex => {
              if (flex.hasAttribute('data-equalized')) return;
              const left = flex.querySelector('.align-block-ocr .align-content');
              const right = flex.querySelector('.align-block-trans .align-content');
              if (!left || !right) return;
              // 统一先设为 0.5 以获得一致的初始测量
              flex.style.setProperty('--ocr-ratio', '50%');
              flex.style.setProperty('--trans-ratio', '50%');
              const hL = left.getBoundingClientRect().height;
              const hR = right.getBoundingClientRect().height;
              if (!isFinite(hL) || !isFinite(hR) || (hL + hR) === 0) return;
              let r = hL / (hL + hR);
              r = Math.max(0.3, Math.min(0.7, r));
              flex.setAttribute('data-equalized', String(r));
              flex.style.setProperty('--ocr-ratio', (r * 100) + '%');
              flex.style.setProperty('--trans-ratio', ((1 - r) * 100) + '%');
            });
          }
          // 超长文本时不对所有对齐对写入比例，保留默认 50/50（避免全量 DOM 循环）
          if (!window.disableEqualizeForLargeDoc) {
            applyRatioToAll();
            // 简化的一次性等高（无复验、无视口触发，避免抖动）
            try { equalizePairsOnce(); } catch {}
          }
          // 渲染后为对比区域的大公式做软换行
          try {
            const container = document.querySelector('.chunk-compare-container');
            softWrapLongFormulasInCompare(container);
          } catch (e) { /* ignore */ }

          // 性能优化：缓存DOM查询结果
          const blockModeButtons = document.querySelectorAll('.block-mode-btn');
          const allSplitters = document.querySelectorAll('.splitter');
          const blockCopyButtons = document.querySelectorAll('.block-copy-btn');
          const blockStructCopyButtons = document.querySelectorAll('.block-struct-copy-btn');
          const blockNavButtons = document.querySelectorAll('.block-nav-btn');

          // 性能优化：使用事件委托减少事件监听器数量
          const chunkCompareContainer = document.querySelector('.chunk-compare-container');
          if (chunkCompareContainer && !chunkCompareContainer.dataset.delegateSet) {
            // 为复制按钮添加事件委托
            chunkCompareContainer.addEventListener('click', function(e) {
              // 段落编辑：进入编辑
              if (e.target.classList.contains('block-edit-btn') || e.target.closest('.block-edit-btn')) {
                const btn = e.target.closest('.block-edit-btn');
                const blockIndex = btn.dataset.block;
                const idx = btn.dataset.idx;
                const type = btn.dataset.type; // 'ocr' | 'trans'
                const flex = btn.closest('.align-block');
                const content = flex.querySelector('.align-content.markdown-body');
                const panel = flex.querySelector('.align-edit-panel');
                const textarea = panel && panel.querySelector('.align-edit-area');
                if (!content || !panel || !textarea) return;
                // 初始文案：优先已保存覆盖，其次 data-raw-markdown
                const key = `chunkOverride_${window.docIdForLocalStorage||'default'}_${blockIndex}_${idx}_${type}`;
                const saved = localStorage.getItem(key);
                const raw = decodeURIComponent(content.getAttribute('data-raw-markdown') || '') || '';
                textarea.value = saved || raw;
                // 让编辑区域尽量占满当前卡片高度
                const h = Math.max(content.offsetHeight, 120);
                textarea.style.minHeight = h + 'px';
                content.style.display = 'none';
                panel.style.display = '';
                // 显示重置按钮（如果存在覆盖）
                const resetBtn = flex.querySelector('.block-edit-reset-btn[data-block="'+blockIndex+'"][data-type="'+type+'"][data-idx="'+idx+'"]');
                if (resetBtn) resetBtn.style.display = saved ? '' : 'none';
                return;
              }
              // 段落编辑：保存
              if (e.target.classList.contains('align-edit-save')) {
                const btn = e.target;
                const blockIndex = btn.dataset.block;
                const idx = btn.dataset.idx;
                const type = btn.dataset.type; // 'ocr' | 'trans'
                const block = btn.closest('.align-block');
                const content = block.querySelector('.align-content.markdown-body');
                const panel = block.querySelector('.align-edit-panel');
                const textarea = panel && panel.querySelector('.align-edit-area');
                if (!content || !panel || !textarea) return;
                const md = (textarea.value || '').trim();
                const key = `chunkOverride_${window.docIdForLocalStorage||'default'}_${blockIndex}_${idx}_${type}`;
                if (md) localStorage.setItem(key, md); else localStorage.removeItem(key);
                // 重新渲染该侧
                try {
                  const imgs = (window.data && window.data.images) || [];
                  const html = window.MarkdownProcessor && window.MarkdownProcessor.renderWithKatexFailback
                    ? window.MarkdownProcessor.renderWithKatexFailback(window.MarkdownProcessor.safeMarkdown(md, imgs))
                    : md.replace(/\n/g, '<br>');
                  content.innerHTML = html;
                  content.setAttribute('data-raw-markdown', encodeURIComponent(md));
                } catch { content.textContent = md; }
                panel.style.display = 'none';
                content.style.display = '';
                // 显示重置按钮
                const resetBtn = block.querySelector('.block-edit-reset-btn[data-block="'+blockIndex+'"][data-type="'+type+'"][data-idx="'+idx+'"]');
                if (resetBtn) resetBtn.style.display = md ? '' : 'none';
                return;
              }
              // 段落编辑：取消
              if (e.target.classList.contains('align-edit-cancel')) {
                const btn = e.target;
                const block = btn.closest('.align-block');
                const content = block.querySelector('.align-content.markdown-body');
                const panel = block.querySelector('.align-edit-panel');
                if (!content || !panel) return;
                panel.style.display = 'none';
                content.style.display = '';
                return;
              }
              // 段落编辑：重置覆盖
              if (e.target.classList.contains('block-edit-reset-btn')) {
                const btn = e.target;
                const blockIndex = btn.dataset.block;
                const idx = btn.dataset.idx;
                const type = btn.dataset.type;
                const key = `chunkOverride_${window.docIdForLocalStorage||'default'}_${blockIndex}_${idx}_${type}`;
                localStorage.removeItem(key);
                // 还原为原始 data-raw-markdown
                const block = btn.closest('.align-block');
                const content = block.querySelector('.align-content.markdown-body');
                const panel = block.querySelector('.align-edit-panel');
                if (content) {
                  const md = decodeURIComponent(content.getAttribute('data-raw-markdown') || '') || '';
                  try {
                    const imgs = (window.data && window.data.images) || [];
                    const html = window.MarkdownProcessor && window.MarkdownProcessor.renderWithKatexFailback
                      ? window.MarkdownProcessor.renderWithKatexFailback(window.MarkdownProcessor.safeMarkdown(md, imgs))
                      : md.replace(/\n/g, '<br>');
                    content.innerHTML = html;
                  } catch { content.textContent = md; }
                }
                if (panel) panel.style.display = 'none';
                if (content) content.style.display = '';
                // 隐藏重置按钮
                btn.style.display = 'none';
                return;
              }
              if (e.target.classList.contains('block-copy-btn')) {
                // 复制按钮逻辑保持不变，但通过事件委托触发
                const btn = e.target;
                const blockIndex = btn.dataset.block;
                const rawBlockContent = window[`blockRawContent_${blockIndex}`];
                const currentMode = window[`showMode_block_${blockIndex}`] || 'both';

                // 复制逻辑保持原样...
                if (rawBlockContent && Array.isArray(rawBlockContent)) {
                  let textToCopy = "";
                  let alertMessage = "";

                  if (currentMode === 'ocr') {
                    rawBlockContent.forEach(pair => {
                      const ocrText = isOriginalFirstInChunkCompare ? (pair && pair[0]) : (pair && pair[1]);
                      if (ocrText) textToCopy += ocrText + "\n\n";
                    });
                    textToCopy = textToCopy.trim();
                    alertMessage = `第 ${parseInt(blockIndex) + 1} 块的 原文 已复制!`;
                  } else if (currentMode === 'trans') {
                    rawBlockContent.forEach(pair => {
                      const transText = isOriginalFirstInChunkCompare ? (pair && pair[1]) : (pair && pair[0]);
                      if (transText) textToCopy += transText + "\n\n";
                    });
                    textToCopy = textToCopy.trim();
                    alertMessage = `第 ${parseInt(blockIndex) + 1} 块的 译文 已复制!`;
                  } else {
                    rawBlockContent.forEach(pair => {
                      const ocrText = isOriginalFirstInChunkCompare ? (pair && pair[0]) : (pair && pair[1]);
                      const transText = isOriginalFirstInChunkCompare ? (pair && pair[1]) : (pair && pair[0]);
                      if (ocrText) textToCopy += "原文:\n" + ocrText + "\n\n";
                      if (transText) textToCopy += "译文:\n" + transText + "\n\n";
                    });
                    textToCopy = textToCopy.trim();
                    alertMessage = `第 ${parseInt(blockIndex) + 1} 块的 原文和译文 已复制!`;
                  }

                  if (textToCopy) {
                    navigator.clipboard.writeText(textToCopy)
                      .then(() => alert(alertMessage))
                      .catch(err => {
                        console.error('复制失败:', err);
                        alert('复制失败，请查看控制台。');
                      });
                  } else {
                    alert('没有内容可复制。');
                  }
                } else {
                  alert('没有内容可复制。');
                }
              }
            });

            chunkCompareContainer.dataset.delegateSet = 'true';
          }

          // 保留原有的独立事件绑定（为了兼容性）
          blockModeButtons.forEach(btn => {
            btn.onclick = function() {
              const blockIndex = this.dataset.block;
              const mode = this.dataset.mode;
              window[`showMode_block_${blockIndex}`] = mode;

              // 更新按钮激活状态
              document.querySelectorAll(`.block-mode-btn[data-block="${blockIndex}"]`).forEach(b => {
                b.classList.remove('active');
              });
              this.classList.add('active');

              // 使用CSS类管理显示模式，而不是直接操作样式
              document.querySelectorAll(`.block-flex-${blockIndex}`).forEach(flexPair => {
                // 移除所有模式类
                flexPair.classList.remove('block-mode-ocr-only', 'block-mode-trans-only', 'block-mode-both');

                // 添加对应的模式类
                if (mode === 'ocr') {
                  flexPair.classList.add('block-mode-ocr-only');
                } else if (mode === 'trans') {
                  flexPair.classList.add('block-mode-trans-only');
                } else { // mode === 'both'
                  flexPair.classList.add('block-mode-both');
                  // 当切换回 both 模式时，重新应用拖动条的比例
                  applyRatioToAllFlexPairsInBlock(blockIndex);
                }
              });
            };
          });

          // 辅助函数：将当前拖动比例应用到指定blockIndex的所有flexPair
          function applyRatioToAllFlexPairsInBlock(blockIndexToUpdate) {
            const currentRatio = window.chunkCompareRatio || 0.5;
            document.querySelectorAll(`.block-flex-${blockIndexToUpdate}`).forEach(flex => {
              if (flex.hasAttribute('data-equalized')) {
                const ratioSaved = parseFloat(flex.getAttribute('data-equalized'));
                if (isFinite(ratioSaved)) {
                  flex.style.setProperty('--ocr-ratio', (ratioSaved * 100) + '%');
                  flex.style.setProperty('--trans-ratio', ((1 - ratioSaved) * 100) + '%');
                  return;
                }
              }
              const hasTableOCR = !!flex.querySelector('.align-block-ocr table');
              const hasTableTRANS = !!flex.querySelector('.align-block-trans table');
              const anyTable = hasTableOCR || hasTableTRANS;
              const ratio = anyTable ? 0.5 : currentRatio;
              flex.style.setProperty('--ocr-ratio', (ratio * 100) + '%');
              flex.style.setProperty('--trans-ratio', ((1 - ratio) * 100) + '%');
            });
          }

          // 初始化拖动比例应用到所有分块的所有对比对
          // 确保在按钮事件绑定之后，但在第一次渲染时就能正确设置
          if (document.querySelector('.align-flex')) { // 确保有可操作的元素
            const allBlockIndexes = new Set();
            document.querySelectorAll('[data-block]').forEach(el => allBlockIndexes.add(el.dataset.block));
            allBlockIndexes.forEach(idx => {
                if(window[`showMode_block_${idx}`] === undefined || window[`showMode_block_${idx}`] === 'both') {
                    applyRatioToAllFlexPairsInBlock(idx);
                }
            });
          }

          // 拖动分割条实现
          let draggingSplitterInfo = null; // {splitter, flexContainer, startX, initialOcrBasisPx}

          allSplitters.forEach(splitter => {
            splitter.onmousedown = function(e) {
              const flexContainer = e.target.closest('.align-flex');
              if (!flexContainer) return;
              const ocrBlock = flexContainer.querySelector('.align-block-ocr');
              if (!ocrBlock || getComputedStyle(ocrBlock).display === 'none') return; // 只在对比模式下拖动

              // 用户手动操作该对比对，清除自动等高的固定比例标记
              if (flexContainer.hasAttribute('data-equalized')) {
                flexContainer.removeAttribute('data-equalized');
              }

              draggingSplitterInfo = {
                splitter: e.target,
                flexContainer: flexContainer,
                startX: e.clientX,
                initialOcrBasisPx: ocrBlock.offsetWidth
              };

              e.target.classList.add('active');
              // 使用CSS类而不是直接设置样式
              document.body.classList.add('dragging-cursor');
              e.preventDefault();
            };
          });

          document.addEventListener('mousemove', function(e) {
            if (!draggingSplitterInfo) return;

            const { splitter, flexContainer, startX, initialOcrBasisPx } = draggingSplitterInfo;
            const ocrBlock = flexContainer.querySelector('.align-block-ocr');
            const transBlock = flexContainer.querySelector('.align-block-trans');
            if (!ocrBlock || !transBlock) return;

            const dx = e.clientX - startX;
            const containerWidth = flexContainer.offsetWidth;
            if (containerWidth === 0) return;

            let newOcrWidthPx = initialOcrBasisPx + dx;
            // 限制最小/最大宽度，例如总宽度的20%到80%
            const minWidthPx = containerWidth * 0.2;
            const maxWidthPx = containerWidth * 0.8;
            newOcrWidthPx = Math.max(minWidthPx, Math.min(newOcrWidthPx, maxWidthPx));

            const newOcrRatio = newOcrWidthPx / containerWidth;

            // 更新当前拖动的块的比例
            flexContainer.style.setProperty('--ocr-ratio', (newOcrRatio * 100) + '%');
            flexContainer.style.setProperty('--trans-ratio', ((1 - newOcrRatio) * 100) + '%');

            // 仅更新当前对比对比例（取消联动）
            draggingSplitterInfo.currentRatio = newOcrRatio;
          });

          document.addEventListener('mouseup', function() {
            if (draggingSplitterInfo) {
              draggingSplitterInfo.splitter.classList.remove('active');
              // 使用CSS类而不是直接设置样式
              document.body.classList.remove('dragging-cursor');
              // 将当前对比对的最终比例写入专属 data-equalized，保持独立
              try {
                const { flexContainer, currentRatio } = draggingSplitterInfo;
                if (flexContainer && typeof currentRatio === 'number' && isFinite(currentRatio)) {
                  flexContainer.setAttribute('data-equalized', String(currentRatio));
                  // 若之前有硬等高 min-height，拖动后移除，完全以用户设定为准
                  const left = flexContainer.querySelector('.align-block-ocr .align-content');
                  const right = flexContainer.querySelector('.align-block-trans .align-content');
                  if (left) left.style.minHeight = '';
                  if (right) right.style.minHeight = '';
                }
              } catch {}
              draggingSplitterInfo = null;
            }
          });

          // ============= 智能比例建议（仅默认比例、每文档一次） =============
          (function smartRatioBootstrap() {
            try {
              const docId = window.docIdForLocalStorage;
              if (!docId) return;
              const promptFlagKey = `chunkCompareSmartRatioPromptShown_${docId}`;
              const alreadyPrompted = localStorage.getItem(promptFlagKey) === 'true';
              // 若已有用户自定义比例，或已弹过提示，则不再计算
              const savedRatioText = localStorage.getItem(`chunkCompareRatio_${docId}`);
              const hasCustomRatio = savedRatioText !== null && !isNaN(parseFloat(savedRatioText)) && parseFloat(savedRatioText) !== 0.5;
              if (alreadyPrompted || hasCustomRatio) return;

              // 延迟执行，等待列表 DOM 渲染完成
              setTimeout(() => maybeSuggestSmartRatio(docId, promptFlagKey), 800);
            } catch (e) { console.warn('smartRatioBootstrap error:', e); }
          })();

          // 等高工具：更稳的帧同步+测量、粗采样+细化、困难对硬等高
          if (false) (function setupPairEqualization() {
            const docId = window.docIdForLocalStorage || 'default';
            window.__pairEqualizer = window.__pairEqualizer || {};
            const state = window.__pairEqualizer;

            const raf2 = () => new Promise(res => requestAnimationFrame(() => requestAnimationFrame(res)));
            async function waitImages(el, ms=150) {
              const imgs = el.querySelectorAll('img');
              if (imgs.length === 0) { await new Promise(r=>setTimeout(r, ms)); return; }
              await Promise.race([
                new Promise(r=>setTimeout(r, ms)),
                Promise.all(Array.from(imgs).map(img=> new Promise(r=>{ if (img.complete) return r(); img.addEventListener('load', r, {once:true}); img.addEventListener('error', r, {once:true}); })))
              ]);
            }
            async function measureDiff(flex, leftEl, rightEl, ratio) {
              flex.style.setProperty('--ocr-ratio', (ratio * 100) + '%');
              flex.style.setProperty('--trans-ratio', ((1 - ratio) * 100) + '%');
              await raf2();
              const hL = leftEl.getBoundingClientRect().height;
              const hR = rightEl.getBoundingClientRect().height;
              return { diff: hL - hR, hL, hR };
            }
            async function equalizePair(flex) {
              try {
                if (flex.dataset.equalizedDone === 'true') return;
                const left = flex.querySelector('.align-block-ocr .align-content');
                const right = flex.querySelector('.align-block-trans .align-content');
                if (!left || !right) { flex.dataset.equalizedDone = 'true'; return; }
                if (flex.classList.contains('block-mode-ocr-only') || flex.classList.contains('block-mode-trans-only')) { return; }
                await waitImages(flex, 120);
                await raf2();
                const isTablePair = !!flex.querySelector('.align-block-ocr table') && !!flex.querySelector('.align-block-trans table');
                const coarse = [0.35,0.45,0.5,0.55,0.65];
                let best = 0.5, bestAbs = Infinity;
                for (const r of coarse) {
                  const m = await measureDiff(flex, left, right, r);
                  const a = Math.abs(m.diff);
                  if (a < bestAbs) { bestAbs = a; best = r; }
                }
                let low = Math.max(0.3, best - 0.1);
                let high = Math.min(0.7, best + 0.1);
                const tol = isTablePair ? 4 : 6;
                for (let i=0;i<(isTablePair?7:6);i++) {
                  const mid = (low+high)/2;
                  const m = await measureDiff(flex, left, right, mid);
                  const a = Math.abs(m.diff);
                  if (a < bestAbs) { bestAbs = a; best = mid; }
                  if (a <= tol) break;
                  if (m.diff > 0) { low = mid; } else { high = mid; }
                }
                // 复验微调
                for (let step=0.04, k=0; k<3; k++, step*=0.5) {
                  const candidates = [best-step, best, best+step].map(r=> Math.max(0.3, Math.min(0.7, r)));
                  for (const r of candidates) {
                    const m = await measureDiff(flex, left, right, r);
                    const a = Math.abs(m.diff);
                    if (a < bestAbs) { bestAbs = a; best = r; }
                  }
                  if (bestAbs <= (isTablePair?2:3)) break;
                }
                // 困难对硬等高：误差仍过大
                if (bestAbs > (isTablePair?4:6)) {
                  const last = await measureDiff(flex, left, right, best);
                  const target = Math.max(last.hL, last.hR);
                  left.style.minHeight = target + 'px';
                  right.style.minHeight = target + 'px';
                  flex.setAttribute('data-equalized', 'hard');
                } else {
                  flex.setAttribute('data-equalized', String(best));
                }
                flex.dataset.equalizedDone = 'true';
              } catch (e) { /* ignore per pair */ }
            }

            // 进入视口再等高（首屏优先）
            if (!state.observer) {
              state.observer = new IntersectionObserver(async entries => {
                for (const entry of entries) {
                  if (entry.isIntersecting) {
                    const flex = entry.target;
                    state.observer.unobserve(flex);
                    await equalizePair(flex);
                  }
                }
              }, { root: null, rootMargin: '200px', threshold: 0.1 });
            }
            document.querySelectorAll('.align-flex').forEach(flex => {
              // 清理因硬等高设置的 min-height（避免历史残留影响）
              const left = flex.querySelector('.align-block-ocr .align-content');
              const right = flex.querySelector('.align-block-trans .align-content');
              if (left) left.style.minHeight = '';
              if (right) right.style.minHeight = '';
              state.observer.observe(flex);
            });
          })();

          // 控制台调试入口：window.forceSmartRatioPrompt({ resetPrompt:true, resetRatio:true })
          window.forceSmartRatioPrompt = function(opts={}) {
            try {
              const docId = window.docIdForLocalStorage;
              if (!docId) return console.warn('forceSmartRatioPrompt: missing docId');
              const promptFlagKey = `chunkCompareSmartRatioPromptShown_${docId}`;
              if (opts.resetPrompt) localStorage.removeItem(promptFlagKey);
              if (opts.resetRatio) localStorage.removeItem(`chunkCompareRatio_${docId}`);
              maybeSuggestSmartRatio(docId, promptFlagKey);
            } catch(e) { console.warn('forceSmartRatioPrompt error:', e); }
          };

          async function maybeSuggestSmartRatio(docId, promptFlagKey) {
            try {
              // 再次确认是否已经设置过比例
              const savedRatioText = localStorage.getItem(`chunkCompareRatio_${docId}`);
              const hasCustomRatio = savedRatioText !== null && !isNaN(parseFloat(savedRatioText)) && parseFloat(savedRatioText) !== 0.5;
              if (hasCustomRatio) return;

              if (!window.data || !Array.isArray(window.data.ocrChunks) || !Array.isArray(window.data.translatedChunks)) return;
              const total = Math.min(window.data.ocrChunks.length, window.data.translatedChunks.length);
              if (total === 0) return;

              // 选取最多15个候选块（优先无图、长度>=150）
              const candidates = selectCandidateBlockIndices(window.data.ocrChunks, window.data.translatedChunks, 15);
              // 调试日志
              try { console.log('[SmartRatio] candidates:', candidates); } catch {}
              // 需要至少2个候选块
              if (candidates.length < 2) return;

              // 统一设置当前比率为0.5，确保测量一致
              window.chunkCompareRatio = 0.5;
              applyRatioToAll();

              const ratios = [];
              // 逐个确保加载完整块并测量
              for (const idx of candidates) {
                const ok = await ensureChunkPresent(idx, 4000);
                if (!ok) continue;
                const loaded = await ensureChunkLoaded(idx, 6000);
                if (!loaded) continue;

                // 对新加载的flex对也应用0.5比例
                applyRatioToAll();

                const r = measureRecommendedRatioForBlock(idx);
                if (typeof r === 'number' && isFinite(r) && r > 0 && r < 1) {
                  ratios.push(r);
                }
                if (ratios.length >= 15) break;
              }

              // 至少需要2个有效测量结果
              if (ratios.length < 2) { try { console.log('[SmartRatio] Not enough measured ratios:', ratios); } catch {}; return; }
              // 剔除极端值：简单去头去尾（10%）；n>=6时各去1个
              ratios.sort((a,b)=>a-b);
              let trimmed = ratios.slice();
              if (ratios.length >= 6) {
                trimmed = ratios.slice(1, ratios.length - 1);
              }
              const avg = trimmed.reduce((s,v)=>s+v,0) / trimmed.length;
              let suggested = Math.max(0.3, Math.min(0.7, avg));
              try { console.log('[SmartRatio] ratios:', ratios, 'trimmed:', trimmed, 'avg:', avg, 'suggested:', suggested); } catch {}

              // 主动弹窗（每文档只弹一次）
              localStorage.setItem(promptFlagKey, 'true');
              const pct = Math.round(suggested * 100);
              const use = confirm(`已根据前 ${trimmed.length} 个块估算出建议对比比例为 ${pct}%（原文）/ ${100-pct}%（译文）。是否应用？`);
              if (use) {
                window.chunkCompareRatio = suggested;
                applyRatioToAll();
                localStorage.setItem(`chunkCompareRatio_${docId}`, String(suggested));
                if (typeof showNotification === 'function') {
                  showNotification(`已应用智能比例：原文 ${pct}%`, 'success');
                }
              }
            } catch (e) {
              console.warn('maybeSuggestSmartRatio error:', e);
            }
          }

          function selectCandidateBlockIndices(ocrChunks, transChunks, limit) {
            const n = Math.min(ocrChunks.length, transChunks.length);
            const items = [];
            for (let i = 0; i < n; i++) {
              const o = ocrChunks[i] || '';
              const t = transChunks[i] || '';
              const lenOk = (o.length >= 150) && (t.length >= 150);
              const hasImg = /!\[[^\]]*\]\([^)]*\)|<img\b/i.test(o) || /!\[[^\]]*\]\([^)]*\)|<img\b/i.test(t);
              const hasMdTable = /(^|[\r\n])\s*\|.*\|/m.test(o) || /(^|[\r\n])\s*\|.*\|/m.test(t) || /<table\b/i.test(o) || /<table\b/i.test(t);
              items.push({ idx: i, lenOk, hasImg, hasMdTable });
            }
            // 过滤：长度达标
            const filtered = items.filter(it => it.lenOk && !it.hasMdTable);
            // 优先无图，再有图，再按索引
            filtered.sort((a,b)=> (a.hasImg===b.hasImg?0:(a.hasImg?1:-1)) || (a.idx-b.idx));
            return filtered.slice(0, limit).map(it=>it.idx);
          }

          function measureRecommendedRatioForBlock(blockIndex) {
            try {
              // 兼容旧版与新版容器：优先旧版 #block-{i}，再尝试 #chunk-{i} 或 data-chunk-index
              const container =
                document.getElementById(`block-${blockIndex}`) ||
                document.getElementById(`chunk-${blockIndex}`) ||
                document.querySelector(`.chunk-pair[data-chunk-index="${blockIndex}"]`);
              if (!container) return null;
              // 跳过包含表格的块，避免对建议比例产生干扰
              if (container.querySelector('table')) return null;
              const ocrNodes = container.querySelectorAll('.align-block-ocr .align-content');
              const transNodes = container.querySelectorAll('.align-block-trans .align-content');
              if (ocrNodes.length === 0 || transNodes.length === 0) return null;
              let hOcr = 0, hTrans = 0;
              ocrNodes.forEach(n => { hOcr += n.getBoundingClientRect().height; });
              transNodes.forEach(n => { hTrans += n.getBoundingClientRect().height; });
              if (!isFinite(hOcr) || !isFinite(hTrans) || hOcr <= 0 || hTrans <= 0) return null;
              // 基于 h ~ A/r, 推导 r* = hOcr / (hOcr + hTrans)
              const r = hOcr / (hOcr + hTrans);
              return r;
            } catch (e) {
              return null;
            }
          }

          function wait(ms) { return new Promise(res => setTimeout(res, ms)); }

          async function ensureChunkPresent(index, timeoutMs = 4000) {
            const start = Date.now();
            while (Date.now() - start < timeoutMs) {
              const el = document.getElementById(`block-${index}`) ||
                          document.getElementById(`chunk-${index}`) ||
                          document.querySelector(`.chunk-pair[data-chunk-index="${index}"]`);
              if (el) return true;
              await wait(60);
            }
            return false;
          }

          async function ensureChunkLoaded(index, timeoutMs = 6000) {
            try {
              const container = document.getElementById(`block-${index}`) ||
                                document.getElementById(`chunk-${index}`) ||
                                document.querySelector(`.chunk-pair[data-chunk-index="${index}"]`);
              if (!container) return false;
              // 旧版一次性渲染，存在 .align-flex 即视为已加载
              if (container.querySelector('.align-flex')) return true;
              // 新版需触发懒加载
              if (window.ChunkCompareOptimizer && window.ChunkCompareOptimizer.instance && typeof window.ChunkCompareOptimizer.instance.loadFullChunk === 'function') {
                window.ChunkCompareOptimizer.instance.loadFullChunk(index);
              }
              const start = Date.now();
              while (Date.now() - start < timeoutMs) {
                if (container.querySelector('.align-flex')) return true;
                await wait(80);
              }
            } catch {}
            return false;
          }

          blockCopyButtons.forEach(btn => {
            btn.onclick = function() {
              const blockIndex = this.dataset.block;
              const rawBlockContent = window[`blockRawContent_${blockIndex}`];
              const currentMode = window[`showMode_block_${blockIndex}`] || 'both'; // 获取当前模式

              if (rawBlockContent && Array.isArray(rawBlockContent)) {
                let textToCopy = "";
                let alertMessage = "";

                if (currentMode === 'ocr') {
                  rawBlockContent.forEach(pair => {
                    // 如果原文在左，pair[0]是原文；如果译文在左，pair[0]是译文
                    const ocrText = isOriginalFirstInChunkCompare ? (pair && pair[0]) : (pair && pair[1]);
                    if (ocrText) textToCopy += ocrText + "\n\n";
                  });
                  textToCopy = textToCopy.trim();
                  alertMessage = `第 ${parseInt(blockIndex) + 1} 块的 原文 已复制!`;
                } else if (currentMode === 'trans') {
                  rawBlockContent.forEach(pair => {
                    // 如果原文在左，pair[1]是译文；如果译文在左，pair[1]是原文
                    const transText = isOriginalFirstInChunkCompare ? (pair && pair[1]) : (pair && pair[0]);
                    if (transText) textToCopy += transText + "\n\n";
                  });
                  textToCopy = textToCopy.trim();
                  alertMessage = `第 ${parseInt(blockIndex) + 1} 块的 译文 已复制!`;
                } else { // mode === 'both'
                  rawBlockContent.forEach(pair => {
                    const ocrText = isOriginalFirstInChunkCompare ? (pair && pair[0]) : (pair && pair[1]);
                    const transText = isOriginalFirstInChunkCompare ? (pair && pair[1]) : (pair && pair[0]);
                    if (ocrText) textToCopy += "原文:\n" + ocrText + "\n\n";
                    if (transText) textToCopy += "译文:\n" + transText + "\n\n";
                  });
                  textToCopy = textToCopy.trim();
                  alertMessage = `第 ${parseInt(blockIndex) + 1} 块的 原文和译文 已复制!`;
                }

                if (textToCopy) {
                  navigator.clipboard.writeText(textToCopy)
                    .then(() => alert(alertMessage))
                    .catch(err => {
                      console.error('复制失败:', err);
                      alert('复制失败，请查看控制台。');
                    });
                } else {
                  alert('没有内容可复制。');
                }
              }
            };
          });

          blockStructCopyButtons.forEach(btn => {
            btn.onclick = function() {
              const blockIndex = this.dataset.block;
              const type = this.dataset.type; // 'ocr' or 'trans'
              const structIdx = parseInt(this.dataset.idx);
              const rawBlockContent = window[`blockRawContent_${blockIndex}`];
              if (rawBlockContent && rawBlockContent[structIdx]) {
                const textToCopy = (type === 'ocr') ? rawBlockContent[structIdx][0] : rawBlockContent[structIdx][1];
                navigator.clipboard.writeText(textToCopy)
                  .then(() => alert(`第 ${parseInt(blockIndex) + 1} 块的 ${type === 'ocr' ? '原文' : '译文'} (结构 ${structIdx + 1}) 已复制!`))
                  .catch(err => console.error('复制失败:', err));
              }
            };
          });

          blockNavButtons.forEach(btn => {
            btn.onclick = function() {
              const blockIndex = parseInt(this.dataset.block);
              const direction = this.dataset.dir;
              let targetIndex = direction === 'prev' ? blockIndex - 1 : blockIndex + 1;
              const targetElement = document.getElementById(`block-${targetIndex}`);
              if (targetElement) {
                targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
                // 可选：添加高亮效果
                targetElement.classList.add('block-highlight');
                setTimeout(() => targetElement.classList.remove('block-highlight'), 1500);
              }
            };
          });

          // 性能监控：记录分块对比渲染时间
          const renderEndTime = performance.now();
          console.log(`[性能] 分块对比渲染完成，总块数: ${data.ocrChunks.length}，耗时: ${(renderEndTime - window.chunkCompareStartTime || 0).toFixed(2)}ms`);

          // 内存优化：清理旧的缓存（如果太多的话）
          if (window.chunkParseCache && Object.keys(window.chunkParseCache).length > 100) {
            console.log('[性能] 清理部分解析缓存以释放内存');
            // 超长文档更激进地清理缓存
            const MAX_CACHE_ITEMS = (window.disableEqualizeForLargeDoc ? 5 : 50);
            const cacheKeys = Object.keys(window.chunkParseCache);
            const keysToDelete = cacheKeys.slice(0, cacheKeys.length - MAX_CACHE_ITEMS);
            keysToDelete.forEach(key => delete window.chunkParseCache[key]);
          }

        }, 0);
        } // end fallback (no optimizer)
    } else {
        html = '<h3>分块对比</h3><p>此记录没有有效的分块对比数据。</p>';
        if (!data.ocrChunks || !data.translatedChunks) {
             html += '<p>原因：缺少分块数据 (ocrChunks or translatedChunks missing)。</p>';
        } else if (data.ocrChunks.length !== data.translatedChunks.length) {
             html += `<p>原因：原文块数量 (${data.ocrChunks.length}) 与译文块数量 (${data.translatedChunks.length}) 不匹配。</p>`;
        } else {
             html += '<p>原因：分块数据为空。</p>';
        }
    }
  }
  document.getElementById('tabContent').innerHTML = html;
  // 分批渲染逻辑（仅对 OCR 和翻译标签页生效）
  if(tab === 'ocr' || tab === 'translation') {
    const contentText = tab === 'ocr' ? (data.ocr || '') : (data.translation || '');
    const contentContainer = document.getElementById(contentContainerId);
    const batchSize = 30; // 每批渲染的段落数，可调整
    const tokens = marked.lexer(contentText).filter(token => ['paragraph','heading','code','table','blockquote','list','html','hr'].includes(token.type));
    window.currentBlockTokensForCopy[tab] = tokens;
    // 性能优化：禁用预标注（inline span 注入），统一改为渲染完成后由 applyBlockAnnotations 处理
    // 这样可避免在长文本上进行大量正则匹配与 DOM 拼接。
    const customRenderer = createCustomMarkdownRenderer([], tab, MarkdownProcessor.renderWithKatexFailback);

    // Define segmentInBatches here, so it's in scope for renderBatch's callback
    function segmentInBatches(containerElement, batchSize = 10, delay = 50, onDone) {
        const blocks = Array.from(containerElement.children).filter(node => node.nodeType === Node.ELEMENT_NODE);
        let i = 0;
        function runBatch() {
            const end = Math.min(i + batchSize, blocks.length);
            for (; i < end; i++) {
                const el = blocks[i];
                el.dataset.blockIndex = String(i);
                if (window.SubBlockSegmenter && typeof window.SubBlockSegmenter.segment === 'function') {
                    // 强制分段，保证英文/短段也有子块，便于精确高亮
                    window.SubBlockSegmenter.segment(el, i, true);
                } else {
                    console.error("SubBlockSegmenter.segment is not available.");
                }
            }
            if (i < blocks.length) {
                setTimeout(runBatch, delay);
            } else {
                // 所有父块的子块分割完成
                onDone && onDone();
            }
        }
        runBatch();
    }

  function renderBatch(startIdx, onDoneAllBatchesCallback) { // Added onDoneAllBatchesCallback parameter
      const fragment = document.createDocumentFragment();
      for(let i=startIdx;i<Math.min(tokens.length, startIdx+batchSize);i++){
        const tokenRaw = tokens[i].raw || '';

        // 检测：如果 token 类型是 paragraph 但包含表格语法，强制作为表格处理
        const hasTableSyntax = /\|(:?-+:?\|)+/.test(tokenRaw);
        if (tokens[i].type === 'paragraph' && hasTableSyntax) {
          console.log('[renderBatch] 检测到 paragraph token 包含表格语法，强制作为表格处理');
          tokens[i].type = 'table'; // 强制改为 table 类型
        }

        // 优先使用 AST 处理器（支持压缩表格修复）
        let htmlStr;
        if (typeof MarkdownIntegration !== 'undefined' && MarkdownIntegration.smartRender) {
          htmlStr = MarkdownIntegration.smartRender(tokenRaw, data.images, customRenderer, contentIdentifier);
        } else if (typeof MarkdownProcessorAST !== 'undefined' && MarkdownProcessorAST.render) {
          htmlStr = MarkdownProcessorAST.render(tokenRaw, data.images);
        } else {
          // 降级到旧版
          htmlStr = MarkdownProcessor.renderWithKatexFailback(MarkdownProcessor.safeMarkdown(tokenRaw, data.images), customRenderer);
        }

        // 后验检查：如果渲染后仍然是 <p> 但包含表格 Markdown，尝试直接渲染表格
        if (hasTableSyntax && htmlStr.trim().startsWith('<p')) {
          console.warn('[renderBatch] 渲染后仍然是 <p>，尝试直接提取并渲染表格部分');
          // 提取 <p> 中的文本内容
          const tempDiv = document.createElement('div');
          tempDiv.innerHTML = htmlStr;
          const pElement = tempDiv.querySelector('p');
          if (pElement && pElement.textContent.includes('|')) {
            const tableMarkdown = pElement.textContent;
            // 重新渲染表格
            if (typeof MarkdownProcessorAST !== 'undefined' && MarkdownProcessorAST.render) {
              htmlStr = MarkdownProcessorAST.render(tableMarkdown, data.images);
              console.log('[renderBatch] 重新渲染表格成功');
            }
          }
        }

        const wrap = document.createElement('div');
        wrap.innerHTML = htmlStr;
        while(wrap.firstChild) fragment.appendChild(wrap.firstChild);
      }
      contentContainer.appendChild(fragment);
      if(startIdx+batchSize<tokens.length) {
        setTimeout(()=>renderBatch(startIdx+batchSize, onDoneAllBatchesCallback),0); // Pass callback along
      } else {
        // 所有批次渲染完成
        if(tab==='ocr') console.timeEnd('[性能] OCR分批渲染');
        if(tab==='translation') console.timeEnd('[性能] 翻译分批渲染');

        // Use requestAnimationFrame to allow browser to paint/layout before further DOM manipulation
        requestAnimationFrame(() => {
          console.log(`[showTab - ${tab}] RAF triggered after all batches rendered.`);
          const currentTabContentWrapper = document.getElementById(contentContainerId);
          if (currentTabContentWrapper) {
              adjustLongHeadingsToParagraphs(currentTabContentWrapper);
              // 兜底修复：将相对图片 src 替换为 data URL（若前置替换未命中）
              try {
                const imgs = (window.data && Array.isArray(window.data.images)) ? window.data.images : [];
                if (imgs && imgs.length > 0) {
                  const map = new Map();
                  imgs.forEach((im, idx) => {
                    const id = (im.id || '').toString();
                    const name = (im.name || id || `img-${idx+1}.jpg`).toString();
                    const base = id || name;
                    const keys = [
                      base,
                      name,
                      `images/${base}`,
                      `images/${name}`,
                      base.replace(/\.[^.]+$/, ''),
                      name.replace(/\.[^.]+$/, '')
                    ];
                    const dataUri = (im.data && im.data.startsWith && im.data.startsWith('data:')) ? im.data : `data:image/jpeg;base64,${im.data || ''}`;
                    keys.forEach(k => { if (k) map.set(k, dataUri); });
                  });
                  currentTabContentWrapper.querySelectorAll('img').forEach(imgEl => {
                    const src = imgEl.getAttribute('src') || '';
                    if (!src || /^data:|^https?:|^\/\//i.test(src)) return;
                    const clean = src.split('?')[0].split('#')[0].replace(/^\.\//, '');
                    const nameOnly = clean.split('/').pop();
                    const candidates = [clean, nameOnly, `images/${nameOnly}`];
                    for (const key of candidates) {
                      if (map.has(key)) {
                        imgEl.setAttribute('src', map.get(key));
                        break;
                      }
                    }
                  });
                }
              } catch (e) { console.warn('[showTab] fix images fallback failed:', e); }
          }

          // Now, call segmentInBatches on the fully rendered content
          activeContentElement = document.getElementById(contentContainerId); // Re-affirm activeContentElement
          if (activeContentElement) {
            segmentInBatches(activeContentElement, 10, 50, () => {
                // This is the onDone callback for segmentInBatches
                // All segmentation is complete, now apply annotations and listeners

                // 后处理：渲染表格和其他地方的纯文本公式（异步版本，不阻塞主线程）
                console.log('[showTab] 开始后处理公式（异步）');
                console.time('[性能] 公式渲染');

                // 优先使用异步 Worker 版本（页面显示时）
                if (typeof FormulaPostProcessorAsync !== 'undefined') {
                    FormulaPostProcessorAsync.processFormulasInElement(activeContentElement, {
                        useWorker: true,  // 页面显示时使用 Worker，避免阻塞主线程
                        onComplete: () => {
                            console.timeEnd('[性能] 公式渲染');
                            console.log('[showTab] 公式渲染完成');
                        }
                    });
                } else if (typeof FormulaPostProcessor !== 'undefined' && FormulaPostProcessor.processFormulasInElement) {
                    // 回退到同步版本（如果异步版本未加载）
                    console.warn('[showTab] 异步公式处理器不可用，使用同步版本');
                    FormulaPostProcessor.processFormulasInElement(activeContentElement);
                    console.timeEnd('[性能] 公式渲染');
                }

                if (data && data.annotations && typeof window.applyBlockAnnotations === 'function') {
                    window.applyBlockAnnotations(activeContentElement, data.annotations, contentIdentifier);
                }
                // Finally, update Dock stats and TOC as content and structure are finalized
                if (window.DockLogic && typeof window.DockLogic.updateStats === 'function') {
                  console.log(`[showTab - ${tab}] OCR/Translation: segmentInBatches done, forcing Dock stats update.`);
                  window.DockLogic.updateStats(window.data, currentVisibleTabId);
                }
                if (typeof window.refreshTocList === 'function') {
                  console.log(`[showTab - ${tab}] OCR/Translation: segmentInBatches done, forcing TOC refresh.`);
                  window.refreshTocList();
                }
                // ========== 渲染完成，解锁 ==========='
                renderingTab = null;
                // ====================================
                // ========== 内容加载完成 =============
                window.contentReady = true;
                console.log('[DEBUG] window.contentReady = true (after OCR/Translation segmentInBatches)');

                // Phase 2.3: 初始化批注系统 DOM 缓存
                if (window.AnnotationDOMCache) {
                  window.AnnotationDOMCache.init();
                }
                // ====================================
            });
          } else {
            // ========== 渲染完成，解锁 ==========='
            renderingTab = null;
            // ====================================
            // ========== 内容加载完成 =============
            window.contentReady = true;
            console.log('[DEBUG] window.contentReady = true (after OCR/Translation segmentInBatches, no activeContentElement)');

            // Phase 2.3: 初始化批注系统 DOM 缓存
            if (window.AnnotationDOMCache) {
              window.AnnotationDOMCache.init();
            }
            // ====================================
          }
        }); // End of requestAnimationFrame
      }
    }

    // Start rendering batches and provide a callback for when all are done
    renderBatch(0, () => {
      // This callback is executed after all batches for OCR/Translation are rendered
      console.log(`[showTab - ${tab}] All batches rendered. Proceeding with DOM processing.`);

      // Use requestAnimationFrame to allow browser to paint/layout before further DOM manipulation
      requestAnimationFrame(() => {
        console.log(`[showTab - ${tab}] RAF triggered after all batches rendered.`);
        const currentTabContentWrapper = document.getElementById(contentContainerId);
        if (currentTabContentWrapper) {
            adjustLongHeadingsToParagraphs(currentTabContentWrapper);
        }

        // Now, call segmentInBatches on the fully rendered content
        activeContentElement = document.getElementById(contentContainerId); // Re-affirm activeContentElement
        if (activeContentElement) {
          segmentInBatches(activeContentElement, 10, 50, () => {
              // This is the onDone callback for segmentInBatches
              // All segmentation is complete, now apply annotations and listeners

              // 后处理：渲染表格和其他地方的纯文本公式（异步版本，不阻塞主线程）
              console.log('[showTab] 开始后处理公式（异步）');
              console.time('[性能] 公式渲染');

              // 优先使用异步 Worker 版本（页面显示时）
              if (typeof FormulaPostProcessorAsync !== 'undefined') {
                  FormulaPostProcessorAsync.processFormulasInElement(activeContentElement, {
                      useWorker: true,  // 页面显示时使用 Worker，避免阻塞主线程
                      onComplete: () => {
                          console.timeEnd('[性能] 公式渲染');
                          console.log('[showTab] 公式渲染完成');
                      }
                  });
              } else if (typeof FormulaPostProcessor !== 'undefined' && FormulaPostProcessor.processFormulasInElement) {
                  // 回退到同步版本（如果异步版本未加载）
                  console.warn('[showTab] 异步公式处理器不可用，使用同步版本');
                  FormulaPostProcessor.processFormulasInElement(activeContentElement);
                  console.timeEnd('[性能] 公式渲染');
              }

              if (data && data.annotations && typeof window.applyBlockAnnotations === 'function') {
                  window.applyBlockAnnotations(activeContentElement, data.annotations, contentIdentifier);
              }
              // Finally, update Dock stats and TOC as content and structure are finalized
              if (window.DockLogic && typeof window.DockLogic.updateStats === 'function') {
                console.log(`[showTab - ${tab}] OCR/Translation: segmentInBatches done, forcing Dock stats update.`);
                window.DockLogic.updateStats(window.data, currentVisibleTabId);
              }
              if (typeof window.refreshTocList === 'function') {
                console.log(`[showTab - ${tab}] OCR/Translation: segmentInBatches done, forcing TOC refresh.`);
                window.refreshTocList();
              }
              // ========== 渲染完成，解锁 ==========='
              renderingTab = null;
              // ====================================
              // ========== 内容加载完成 =============
              window.contentReady = true;
              console.log('[DEBUG] window.contentReady = true (after OCR/Translation segmentInBatches)');

              // Phase 2.3: 初始化批注系统 DOM 缓存
              if (window.AnnotationDOMCache) {
                window.AnnotationDOMCache.init();
              }

              try {
                // 通知其他模块（例如参考文献管理器）内容已渲染
                document.dispatchEvent(new CustomEvent('contentRendered', { detail: { tab } }));
              } catch (e) { /* no-op */ }
              // ====================================
          });
        } else {
          // ========== 渲染完成，解锁 ==========='
          renderingTab = null;
          // ====================================
          // ========== 内容加载完成 =============
          window.contentReady = true;
          console.log('[DEBUG] window.contentReady = true (after OCR/Translation segmentInBatches, no activeContentElement)');

          // Phase 2.3: 初始化批注系统 DOM 缓存
          if (window.AnnotationDOMCache) {
            window.AnnotationDOMCache.init();
          }

          try {
            // 通知其他模块（例如参考文献管理器）内容已渲染
            document.dispatchEvent(new CustomEvent('contentRendered', { detail: { tab } }));
          } catch (e) { /* no-op */ }
          // ====================================
        }
      }); // End of requestAnimationFrame
    });
  }

  // NEW: Adjust long headings. This should be called AFTER innerHTML is set
  // and BEFORE refreshTocList is called.
  // MOVED: adjustLongHeadingsToParagraphs is now called after renderBatch completes for OCR/Translation
  // const tabContentElement = document.getElementById('tabContent');
  // if (tabContentElement) {
  //     adjustLongHeadingsToParagraphs(tabContentElement);
  // }

  // MOVED: refreshTocList is now called later for OCR/Translation
  // if (typeof window.refreshTocList === 'function') {
  //   window.refreshTocList(); // 更新TOC
  // }

  // Update reading progress when tab changes and content is rendered - CALLING DOCK_LOGIC
  if (window.DockLogic && typeof window.DockLogic.forceUpdateReadingProgress === 'function') {
    window.DockLogic.forceUpdateReadingProgress();
  }

  // 如果是分块对比视图，并且按钮存在，则绑定事件
  if (tab === 'chunk-compare') {
    const swapBtn = document.getElementById('swap-chunks-btn');
    if (swapBtn) {
        swapBtn.onclick = function() {
            isOriginalFirstInChunkCompare = !isOriginalFirstInChunkCompare;
            showTab('chunk-compare'); // 重新渲染分块对比视图
        };
    }
  }

  // After tab content is updated, refresh chatbot UI if it's open
  if (window.isChatbotOpen && typeof window.ChatbotUI !== 'undefined' && typeof window.ChatbotUI.updateChatbotUI === 'function') {
    window.ChatbotUI.updateChatbotUI();
  }

  // 应用高亮和批注
  // MOVED: The logic for applying annotations and adding listeners for OCR/Translation
  // is now inside the callback chain starting from renderBatch a few lines above.
  /*
  if ((tab === 'ocr' || tab === 'translation') && contentContainerId) {
    activeContentElement = document.getElementById(contentContainerId);
    if (activeContentElement) {
        // 分批异步分割子块，避免一次性阻塞
        function segmentInBatches(containerElement, batchSize = 10, delay = 50, onDone) {
            const blocks = Array.from(containerElement.children).filter(node => node.nodeType === Node.ELEMENT_NODE);
            let i = 0;
            function runBatch() {
                const end = Math.min(i + batchSize, blocks.length);
                for (; i < end; i++) {
                    const el = blocks[i];
                    el.dataset.blockIndex = String(i);
                    if (window.SubBlockSegmenter && typeof window.SubBlockSegmenter.segment === 'function') {
                        // 强制分段，保证英文/短段也有子块，便于精确高亮
                        window.SubBlockSegmenter.segment(el, i, true);
                    } else {
                        console.error("SubBlockSegmenter.segment is not available.");
                    }
                }
                if (i < blocks.length) {
                    setTimeout(runBatch, delay);
                } else {
                    // 所有父块的子块分割完成
                    onDone && onDone();
                }
            }
            runBatch();
        }

        segmentInBatches(activeContentElement, 10, 50, () => {
            // 所有分割完成后，应用批注和监听器
            if (data && data.annotations && typeof window.applyBlockAnnotations === 'function') {
                window.applyBlockAnnotations(activeContentElement, data.annotations, window.globalCurrentContentIdentifier);
            }
            // **** 新增：在所有子块分割和批注应用完成后，再次更新Dock统计 ****
            if (window.DockLogic && typeof window.DockLogic.updateStats === 'function') {
              console.log(`[showTab - ${tab}] OCR/Translation content and annotations processed, forcing Dock stats update.`);
              window.DockLogic.updateStats(window.data, currentVisibleTabId);
            }
            // TOC refresh will be handled later
        });
    }
  } else if (tab === 'chunk-compare') {
  */
  // The chunk-compare logic for annotations and Dock stats remains, as it has its own processing path.
  // We only moved the OCR/Translation specific part.
  if (tab === 'chunk-compare') { // This is the original start of the else if block
     // 对于分块对比视图，为每个原文和译文块单独处理
     setTimeout(() => { //确保DOM更新完毕
        // 清理工具：移除区域末尾的换行/空白/空段落，避免不可见换行导致高度不齐
        function trimTrailingBreaks(area) {
          if (!area) return;
          try {
            function isWhitespaceText(n) {
              if (!n || n.nodeType !== Node.TEXT_NODE) return false;
              let t = n.textContent || '';
              t = t.replace(/\u00A0/g, ' '); // NBSP → space
              t = t.replace(/[\u200B-\u200D\uFEFF]/g, ''); // zero-width
              return /^\s*$/.test(t);
            }
            function isEmptyElement(n) {
              if (!n || n.nodeType !== Node.ELEMENT_NODE) return false;
              // 没有可见文本与可见子节点（如图片/表格）
              const hasMedia = n.querySelector('img, table, video, svg');
              let txt = (n.textContent || '');
              txt = txt.replace(/\u00A0/g, ' ');
              txt = txt.replace(/[\u200B-\u200D\uFEFF]/g, '');
              txt = txt.replace(/\s+/g, '');
              return !hasMedia && txt.length === 0;
            }
            function removeTrailingIn(el) {
              let node = el && el.lastChild;
              // 先清理内部子节点的末尾 <br> 与空白
              if (node && node.nodeType === Node.ELEMENT_NODE) {
                while (node.lastChild && (node.lastChild.nodeType === Node.ELEMENT_NODE && node.lastChild.tagName.toLowerCase() === 'br' || isWhitespaceText(node.lastChild))) {
                  node.removeChild(node.lastChild);
                }
              }
              // 再清理当前容器的末尾
              while (node) {
                if (isWhitespaceText(node)) {
                  const prev = node.previousSibling; el.removeChild(node); node = prev; continue;
                }
                if (node.nodeType === Node.ELEMENT_NODE) {
                  const tag = node.tagName.toLowerCase();
                  if (tag === 'br') { const prev = node.previousSibling; el.removeChild(node); node = prev; continue; }
                  // 清理元素内末尾 <br> 以及纯空元素
                  while (node.lastChild && (node.lastChild.nodeType === Node.ELEMENT_NODE && node.lastChild.tagName.toLowerCase() === 'br' || isWhitespaceText(node.lastChild))) {
                    node.removeChild(node.lastChild);
                  }
                  if (isEmptyElement(node)) { const prev = node.previousSibling; el.removeChild(node); node = prev; continue; }
                }
                break;
              }
            }
            removeTrailingIn(area);
          } catch (e) { /* ignore */ }
        }
        const ocrContentAreas = document.querySelectorAll('.chunk-compare-container .align-block-ocr .align-content.markdown-body');
        const transContentAreas = document.querySelectorAll('.chunk-compare-container .align-block-trans .align-content.markdown-body');
        let areasProcessed = 0;
        const totalAreasToProcess = ocrContentAreas.length + transContentAreas.length;

        function singleAreaProcessed() {
            areasProcessed++;
            if (areasProcessed === totalAreasToProcess) {
                // 所有分块对比区域处理完毕后更新Dock统计和TOC
                if (window.DockLogic && typeof window.DockLogic.updateStats === 'function') {
                    console.log(`[showTab - ${tab}] Chunk-compare: all areas processed, forcing Dock stats update.`);
                    window.DockLogic.updateStats(window.data, currentVisibleTabId);
                }
                if (typeof window.refreshTocList === 'function') {
                    console.log(`[showTab - ${tab}] Chunk-compare: all areas processed, forcing TOC refresh.`);
                    window.refreshTocList();
                }
            }
        }

        function processContentAreaAsync(area, isOcrArea, callback) { // Renamed for clarity
            if (!area.id) {
                area.id = 'chunk-content-' + _page_generateUUID();
            }
            // 渲染完成后立即清理尾部换行/空白
            trimTrailingBreaks(area);
            const effectiveContentIdentifier = isOriginalFirstInChunkCompare ? (isOcrArea ? 'ocr' : 'translation') : (isOcrArea ? 'translation' : 'ocr');
            const blockElements = Array.from(area.children).filter(node => node.nodeType === Node.ELEMENT_NODE);

            let i = 0;
            const batchSize = 5;
            function runChunkSubBatch() {
                const end = Math.min(i + batchSize, blockElements.length);
                for (; i < end; i++) {
                    const element = blockElements[i];
                    element.dataset.blockIndex = String(i);
                    if (typeof window.SubBlockSegmenter !== 'undefined' && typeof window.SubBlockSegmenter.segment === 'function') {
                        window.SubBlockSegmenter.segment(element, i);
                    } else {
                        console.error("SubBlockSegmenter.segment is not available for chunk processing.");
                    }
                }
                if (i < blockElements.length) {
                    setTimeout(runChunkSubBatch, 20);
                } else {
                    // 后处理：渲染表格和其他地方的纯文本公式
                    if (typeof FormulaPostProcessor !== 'undefined' && FormulaPostProcessor.processFormulasInElement) {
                        FormulaPostProcessor.processFormulasInElement(area);
                    }

                    if (data && data.annotations && typeof window.applyBlockAnnotations === 'function') {
                        const annotationsToApply = (currentVisibleTabId === 'chunk-compare') ? [] : data.annotations;
                        window.applyBlockAnnotations(area, annotationsToApply, effectiveContentIdentifier);
                    }
                    callback(); // Signal completion for this area
                }
            }
            runChunkSubBatch();
        }

        if (totalAreasToProcess === 0) { // Handle case with no content areas
             if (window.DockLogic && typeof window.DockLogic.updateStats === 'function') {
                console.log(`[showTab - ${tab}] Chunk-compare has no content areas, forcing Dock stats update.`);
                window.DockLogic.updateStats(window.data, currentVisibleTabId);
            }
            // TOC refresh will be handled later -> Actually, should be called here too if no areas.
            if (typeof window.refreshTocList === 'function') {
                console.log(`[showTab - ${tab}] Chunk-compare has no content areas, forcing TOC refresh.`);
                window.refreshTocList();
            }
        } else {
            ocrContentAreas.forEach(area => processContentAreaAsync(area, true, singleAreaProcessed));
            transContentAreas.forEach(area => processContentAreaAsync(area, false, singleAreaProcessed));
        }
     }, 0);
     // ========== 渲染完成，解锁 ===========
     renderingTab = null;
     // ====================================
  }

  // Attempt to restore scroll position for the current tab
  if (docIdForLocalStorage && currentVisibleTabId) {
    // 添加模式标识到存储键中
    const isImmersive = window.ImmersiveLayout && window.ImmersiveLayout.isActive();
    const modePrefix = isImmersive ? 'immersive_' : 'normal_';
    const scrollKey = `scrollPos_${modePrefix}${docIdForLocalStorage}_${currentVisibleTabId}`;
    const savedScrollTop = localStorage.getItem(scrollKey);
    console.log(`[showTab] 尝试恢复滚动位置: ${scrollKey}, 保存的值: ${savedScrollTop}, 沉浸模式: ${isImmersive ? '是' : '否'}`);

    if (savedScrollTop !== null && !isNaN(parseInt(savedScrollTop, 10))) {
      const scrollableElement = getCurrentScrollableElementForHistoryDetail(); // MODIFIED
      if (scrollableElement) {
        console.log(`[showTab] 找到可滚动元素:`, {
          元素ID: scrollableElement.id || '无ID',
          元素类名: scrollableElement.className || '无类名',
          元素标签: scrollableElement.tagName,
          当前scrollTop: scrollableElement.scrollTop,
          将要设置的scrollTop: parseInt(savedScrollTop, 10),
          scrollHeight: scrollableElement.scrollHeight,
          clientHeight: scrollableElement.clientHeight,
          路径: getElementPath(scrollableElement)
        });

        // 使用多次尝试确保滚动位置被正确设置
        const scrollTopToSet = parseInt(savedScrollTop, 10);
        let attemptCount = 0;

        function attemptToSetScroll() {
          if (currentVisibleTabId !== tab) {
            console.log(`[showTab] 标签已切换，取消恢复滚动位置`);
            return;
          }

          attemptCount++;
          console.log(`[showTab] 第${attemptCount}次尝试设置滚动位置: ${scrollTopToSet}`);
          scrollableElement.scrollTop = scrollTopToSet;

          // 检查是否成功设置
          setTimeout(() => {
            const currentScrollTop = scrollableElement.scrollTop;
            const difference = Math.abs(scrollTopToSet - currentScrollTop);
            console.log(`[showTab] 设置后检查: 预期=${scrollTopToSet}, 实际=${currentScrollTop}, 差值=${difference}`);

            // 如果差异大于阈值且尝试次数小于最大次数，则重试
            if (difference > 5 && attemptCount < 8) {
              console.warn(`[showTab] 警告: 滚动位置设置可能未生效! 将在300ms后重试...`);
              // 等待布局稳定（scrollHeight 两次相等）后再重试
              let checks = 0;
              let lastHeight = scrollableElement.scrollHeight;
              const interval = setInterval(() => {
                const h = scrollableElement.scrollHeight;
                if (h === lastHeight || checks > 5) {
                  clearInterval(interval);
                  requestAnimationFrame(() => setTimeout(attemptToSetScroll, 50));
                } else {
                  lastHeight = h;
                  checks++;
                }
              }, 80);
            } else if (difference > 5) {
              console.warn(`[showTab] 警告: 滚动位置设置失败，已达到最大尝试次数`);
            } else {
              console.log(`[showTab] 滚动位置设置成功!`);
            }
          }, 50);
        }

        // 使用requestAnimationFrame确保DOM已更新
        requestAnimationFrame(() => {
          if(currentVisibleTabId === tab) { // Ensure tab hasn't changed during async operation
            attemptToSetScroll();
          } else {
            console.log(`[showTab] 标签已切换，取消恢复滚动位置`);
          }
        });
      } else {
        console.warn(`[showTab] 未找到可滚动元素，无法恢复滚动位置`);
      }
    } else {
      console.log(`[showTab] 没有保存的滚动位置或值无效: ${savedScrollTop}`);
    }
  } else {
    console.log(`[showTab] 缺少必要参数: docId=${docIdForLocalStorage}, tabId=${currentVisibleTabId}`);
  }

  // Call updateReadingProgress after potential scroll restoration and content rendering - CALLING DOCK_LOGIC
  // 延迟调用，确保滚动位置恢复后再更新阅读进度
  setTimeout(() => {
    if (window.DockLogic && typeof window.DockLogic.forceUpdateReadingProgress === 'function') {
      console.log("[showTab] 延迟调用 forceUpdateReadingProgress 更新阅读进度");
      window.DockLogic.forceUpdateReadingProgress();
    }
  }, 300);

  // 新增：每次渲染后都绑定 scroll 事件到正确容器
  if (window.DockLogic && typeof window.DockLogic.bindScrollForCurrentScrollable === 'function') {
    window.DockLogic.bindScrollForCurrentScrollable();
  }

  // 重新绑定滚动事件以保存滚动位置
  if (typeof bindScrollForSavePosition === 'function') {
    bindScrollForSavePosition();
  }

  // 性能测试断点 - 总渲染结束
  console.timeEnd('[性能] showTab_总渲染');
}
