// chatbot-utils.js

/**
 * 转义 HTML 特殊字符，防止 XSS 攻击。
 * 将 &, <, >, ", ' 替换为相应的 HTML 实体。
 * @param {string} str 需要转义的原始字符串。
 * @returns {string} 转义后的安全字符串。
 */
function escapeHtml(str) {
  return str.replace(/[&<>"']/g, function (c) {
    return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;','\'':'&#39;'}[c];
  });
}

/**
 * 显示一个短暂的浮动提示消息 (Toast)。
 * Toast 用于向用户反馈操作结果，如"已复制到剪贴板"。
 * 如果页面上不存在 ID 为 `chatbot-toast` 的元素，则会创建一个。
 * Toast 会在显示约2秒后自动淡出消失。
 *
 * @param {string} message 要在 Toast 中显示的消息文本。
 */
function showToast(message) {
  let toast = document.getElementById('chatbot-toast');
  if (!toast) {
    toast = document.createElement('div');
    toast.id = 'chatbot-toast';
    toast.style.position = 'fixed';
    toast.style.bottom = '100px';
    toast.style.left = '50%';
    toast.style.transform = 'translateX(-50%)';
    toast.style.padding = '8px 16px';
    toast.style.background = 'rgba(0,0,0,0.7)';
    toast.style.color = 'white';
    toast.style.borderRadius = '4px';
    toast.style.fontSize = '14px';
    toast.style.zIndex = '100001';
    toast.style.opacity = '0';
    toast.style.transition = 'opacity 0.3s';
    document.body.appendChild(toast);
  }
  toast.textContent = message;
  toast.style.opacity = '1';
  setTimeout(() => {
    toast.style.opacity = '0';
  }, 2000);
}

/**
 * 复制指定索引的助手消息内容到用户剪贴板。
 * 使用 `navigator.clipboard.writeText` API。
 * 成功或失败时会调用 `showToast` 显示反馈。
 *
 * @param {number} messageIndex `ChatbotCore.chatHistory` 数组中目标助手消息的索引。
 */
function copyAssistantMessage(messageIndex) {
  if (!window.ChatbotCore || !window.ChatbotCore.chatHistory[messageIndex]) return;
  const text = window.ChatbotCore.chatHistory[messageIndex].content;
  navigator.clipboard.writeText(text).then(() => {
    showToast('已复制到剪贴板');
  }).catch(err => {
    showToast('复制失败，请手动选择文本复制');
  });
}

/**
 * 将指定索引的助手消息内容导出为 PNG 图片。
 * 依赖 `html2canvas` 库。如果该库未加载，则会尝试动态加载它。
 * 实际的导出操作由 `doExportAsPng` 函数执行。
 *
 * @param {number} messageIndex `ChatbotCore.chatHistory` 数组中目标助手消息的索引。
 */
function exportMessageAsPng(messageIndex) {
  if (!window.ChatbotCore || !window.ChatbotCore.chatHistory[messageIndex]) return;
  if (typeof html2canvas === 'undefined') {
    showToast('正在加载图片导出组件...');
    const script = document.createElement('script');
    script.src = 'https://registry.npmmirror.com/html2canvas/1.4.1/files/dist/html2canvas.min.js';
    script.onload = function() {
      showToast('组件加载完成，正在生成图片...');
      doExportAsPng(messageIndex);
    };
    script.onerror = function() {
      showToast('导出组件加载失败，请检查网络连接');
    };
    document.head.appendChild(script);
    return;
  }
  doExportAsPng(messageIndex);
}

/**
 * 执行将指定消息元素导出为 PNG 图片的核心逻辑。
 * 此函数在 `html2canvas` 加载完成后被 `exportMessageAsPng` 调用，或者直接被调用（如果库已加载）。
 * 它会查找对应的消息 DOM 元素，如果找不到，则会尝试使用 `exportContentDirectly` 作为后备方案。
 *
 * @param {number} messageIndex 目标助手消息在 `ChatbotCore.chatHistory` 中的索引。
 */
function doExportAsPng(messageIndex) {
  // Phase 3.5: 添加导出状态锁，防止快速点击导致内存泄漏
  if (window.ChatbotRenderState && window.ChatbotRenderState.isExporting) {
    if (typeof showToast === 'function') {
      showToast('正在导出，请稍候...');
    }
    if (window.PerfLogger) {
      window.PerfLogger.warn('导出已在进行中，忽略重复请求');
    }
    return;
  }

  // 设置导出锁
  if (window.ChatbotRenderState) {
    window.ChatbotRenderState.isExporting = true;
  }

  try {
    const messageElements = document.querySelectorAll('.assistant-message');
    const targetElement = document.querySelector(`.assistant-message[data-message-index="${messageIndex}"]`);
    if ((!messageElements || messageElements.length <= messageIndex) && !targetElement) {
      exportContentDirectly(window.ChatbotCore.chatHistory[messageIndex].content);
      return;
    }
    const element = targetElement || messageElements[messageIndex];
    processExport(element);
  } catch (error) {
    if (window.PerfLogger) {
      window.PerfLogger.error('导出失败:', error);
    }
    // 确保即使出错也释放锁
    if (window.ChatbotRenderState) {
      window.ChatbotRenderState.isExporting = false;
    }
    if (typeof showToast === 'function') {
      showToast('导出失败，请重试');
    }
  }
}

/**
 * 为导出优化：内联 KaTeX 关键样式
 * 基于你的 hot-fix v3，只内联最关键的属性
 */
function inlineKatexStyles(container) {
  // 首先处理顶层 KaTeX 容器
  const katexContainers = container.querySelectorAll('.katex');
  katexContainers.forEach(el => {
    const computed = window.getComputedStyle(el);

    // 判断是行内还是行间公式（检查父容器）
    const parent = el.parentElement;
    const isInline = parent && (
      parent.classList.contains('katex-inline') ||
      parent.getAttribute('data-formula-display') === 'inline' ||
      parent.tagName === 'SPAN'
    );

    const containerProps = [
      'position', 'vertical-align',
      'font-size', 'line-height', 'font-family',
      'margin', 'padding',
      'text-align'
    ];

    const inlineStyles = [];

    // 根据类型手动设置 display
    if (isInline) {
      inlineStyles.push('display: inline');
    } else {
      inlineStyles.push('display: block');
    }

    containerProps.forEach(prop => {
      const value = computed.getPropertyValue(prop);
      if (value && value !== 'auto' && value !== 'normal' && value !== 'none' && value !== '0px') {
        inlineStyles.push(`${prop}: ${value}`);
      }
    });

    if (inlineStyles.length > 0) {
      const existing = el.getAttribute('style') || '';
      el.setAttribute('style', existing + '; ' + inlineStyles.join('; '));
    }
  });

  // 然后处理所有子元素
  const katexElements = container.querySelectorAll('.katex *');
  katexElements.forEach(el => {
    const computed = window.getComputedStyle(el);
    const critical = [
      'display', 'position', 'vertical-align',
      'font-size', 'line-height', 'font-family', 'font-weight', 'font-style',
      'margin-top', 'margin-bottom', 'margin-left', 'margin-right',
      'padding-top', 'padding-bottom', 'padding-left', 'padding-right',
      'width', 'height',
      'top', 'bottom', 'left', 'right',
      'transform', 'color', 'border-bottom'
    ];

    const inlineStyles = [];
    const existing = el.getAttribute('style') || '';

    // Special fix for html2canvas vertical alignment issue
    const verticalAlign = computed.getPropertyValue('vertical-align');
    if (verticalAlign && verticalAlign.endsWith('em')) {
        // Convert vertical-align to relative positioning for html2canvas
        const val = parseFloat(verticalAlign);
        if (!isNaN(val) && val !== 0) {
            // Negative vertical-align means "down", so positive top
            // However, html2canvas often renders text slightly lower, making elements look "high"
            // Experimentally, we add a small offset if it's baseline aligned or negative
            // Phase 10.15: 终极调整
            // 用户反馈"好了点，但还没完全好"，且"不够朝下"
            // 1. 进一步加大 KaTeX 下沉倍率到 1.7 (从 1.4)

            inlineStyles.push(`position: relative`);
            inlineStyles.push(`top: ${-1 * val * 1.7}em`);
            inlineStyles.push(`vertical-align: baseline`); // 保持基线重置
        }
    }

    critical.forEach(prop => {
      const value = computed.getPropertyValue(prop);
      // Skip vertical-align if we handled it above, or just let it be overwritten if we push it?
      // Actually, let's just copy everything else.
      if (prop === 'vertical-align') return;

      if (value && value !== 'auto' && value !== 'normal' && value !== 'none' && value !== '0px' && value !== 'rgba(0, 0, 0, 0)') {
        inlineStyles.push(`${prop}: ${value}`);
      }
    });

    if (inlineStyles.length > 0) {
      const existing = el.getAttribute('style') || '';
      el.setAttribute('style', existing + '; ' + inlineStyles.join('; '));
    }
  });
}

/**
 * 智能内联关键样式（仅用于特定元素）
 * 与旧版不同，这个版本不会盲目复制所有样式
 * @param {HTMLElement} element 需要内联样式的元素
 * @param {string[]} props 需要内联的属性列表
 */
function inlineSpecificStyles(element, props) {
  const computed = window.getComputedStyle(element);
  const existingStyle = element.getAttribute('style') || '';
  const inlineStyle = [];

  props.forEach(prop => {
    const value = computed.getPropertyValue(prop);
    if (value && value !== 'none' && value !== 'normal' && value !== 'auto' && value !== 'initial') {
      // 避免覆盖已有的内联样式
      if (!existingStyle.includes(prop + ':')) {
        inlineStyle.push(`${prop}: ${value}`);
      }
    }
  });

  if (inlineStyle.length > 0) {
    element.setAttribute('style', existingStyle + '; ' + inlineStyle.join('; '));
  }
}

/**
 * Phase 10.9: 内联部分 Markdown 元素的关键样式（简化版）
 * 只处理不会影响垂直对齐的元素，避免破坏列表等元素的布局
 * @param {HTMLElement} container 导出容器
 */
function inlineMarkdownStyles(container) {
  // 只内联不会影响垂直对齐的元素
  // 不处理 ul、ol、li，避免破坏列表的原生布局
  const markdownElements = [
    {
      selector: 'code',
      props: ['background-color', 'padding', 'border-radius', 'font-family', 'font-size', 'color']
    },
    {
      selector: 'pre',
      props: ['background-color', 'padding', 'border-radius', 'overflow-x', 'margin-top', 'margin-bottom']
    },
    {
      selector: 'blockquote',
      props: ['margin-left', 'margin-right', 'padding-left', 'border-left', 'color', 'font-style']
    }
  ];

  markdownElements.forEach(({ selector, props }) => {
    const elements = container.querySelectorAll(selector);
    elements.forEach(el => {
      const computed = window.getComputedStyle(el);
      const existingStyle = el.getAttribute('style') || '';
      const inlineStyles = [];

      props.forEach(prop => {
        const value = computed.getPropertyValue(prop);
        if (value && value !== 'none' && value !== 'normal' && value !== 'auto' && value !== 'initial' && value !== '0px') {
          // 避免覆盖已有的内联样式
          if (!existingStyle.includes(prop + ':')) {
            inlineStyles.push(`${prop}: ${value}`);
          }
        }
      });

      if (inlineStyles.length > 0) {
        el.setAttribute('style', existingStyle + '; ' + inlineStyles.join('; '));
      }
    });
  });
}

/**
 * 处理将消息 DOM 元素转换为 PNG 图片并下载的详细过程。
 *
 * Phase 10: 修复 CSS 重构后的导出问题
 * - 在导出容器中加载完整的 KaTeX CSS
 *
 * @param {HTMLElement} messageElement 要导出为图片的助手消息的 DOM 元素。
 */
function processExport(messageElement) {
  let questionText = "未知问题";
  try {
    const index = parseInt(messageElement.getAttribute('data-message-index'));
    if (!isNaN(index) && index > 0 && window.ChatbotCore.chatHistory[index-1] && window.ChatbotCore.chatHistory[index-1].role === 'user') {
      questionText = window.ChatbotCore.chatHistory[index-1].content;
      if (questionText.length > 60) {
        questionText = questionText.substring(0, 57) + '...';
      }
    }
  } catch (e) {}

  // Phase 10.5: 检查是否有未渲染的公式占位符
  const placeholders = messageElement.querySelectorAll('.katex-placeholder, .katex-block-placeholder, .katex-inline-placeholder');
  if (placeholders.length > 0) {
    showToast(`检测到 ${placeholders.length} 个公式正在渲染，请稍候...`);
    console.log(`[Export] 检测到 ${placeholders.length} 个占位符，等待渲染完成...`);

    // 等待渐进式渲染完成
    const checkInterval = setInterval(() => {
      const remaining = messageElement.querySelectorAll('.katex-placeholder, .katex-block-placeholder, .katex-inline-placeholder');
      if (remaining.length === 0) {
        clearInterval(checkInterval);
        console.log('[Export] 公式渲染完成，开始导出');
        showToast('开始导出...');
        // 短暂延迟确保 DOM 稳定
        setTimeout(() => doActualExport(messageElement), 100);
      }
    }, 200);

    // 超时保护：最多等待 10 秒
    setTimeout(() => {
      clearInterval(checkInterval);
      const remaining = messageElement.querySelectorAll('.katex-placeholder, .katex-block-placeholder, .katex-inline-placeholder');
      if (remaining.length > 0) {
        console.warn(`[Export] 等待超时，仍有 ${remaining.length} 个公式未渲染，强制导出`);
      }
      doActualExport(messageElement);
    }, 10000);

    return;
  }

  // 没有占位符，直接导出
  doActualExport(messageElement);
}

/**
 * 实际执行导出的函数
 */
function doActualExport(messageElement) {

  const exportContainer = document.createElement('div');
  exportContainer.style.position = 'absolute';
  exportContainer.style.left = '-9999px';
  exportContainer.style.padding = '20px';
  exportContainer.style.background = 'white';
  exportContainer.style.borderRadius = '8px';
  exportContainer.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)';
  exportContainer.style.maxWidth = '800px';
  exportContainer.style.width = '800px';
  exportContainer.style.boxSizing = 'border-box';
  exportContainer.style.color = '#111827';
  exportContainer.style.fontSize = '15px';
  exportContainer.style.lineHeight = '1.6';
  // 显式设置字体，确保 html2canvas 渲染时度量一致
  // 使用与 variables.css 中 --font-family-base 一致的字体栈
  exportContainer.style.fontFamily = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
  exportContainer.style.overflow = 'visible';
  exportContainer.style.height = 'auto';

  const watermark = document.createElement('div');
  watermark.style.position = 'absolute';
  watermark.style.bottom = '10px';
  watermark.style.right = '15px';
  watermark.style.fontSize = '8px';
  watermark.style.opacity = '0.4';
  watermark.textContent = 'Created with Paper Burner';

  // Phase 10.5: 使用 cloneNode 而不是 innerHTML，完整保留 KaTeX 结构
  const contentContainer = messageElement.cloneNode(true);

  // 移除克隆元素的 data-message-index 避免 ID 冲突
  contentContainer.removeAttribute('data-message-index');
  contentContainer.style.maxWidth = 'none';

  exportContainer.appendChild(contentContainer);
  exportContainer.appendChild(watermark);
  document.body.appendChild(exportContainer);

  // Phase 10: 内联 KaTeX 样式（hot-fix v3 优化版）
  inlineKatexStyles(exportContainer);

  // Phase 10.9: 内联所有 Markdown 元素的关键样式
  inlineMarkdownStyles(exportContainer);

  // Phase 10.10: 修复列表样式（从原始元素复制计算样式）
  const originalLists = messageElement.querySelectorAll('ul, ol');
  const exportLists = exportContainer.querySelectorAll('ul, ol');
  exportLists.forEach((list, index) => {
    if (originalLists[index]) {
      const computed = window.getComputedStyle(originalLists[index]);
      list.style.paddingLeft = computed.paddingLeft;
      list.style.marginBottom = computed.marginBottom;
      list.style.listStyleType = computed.listStyleType;
      list.style.listStylePosition = 'inside'; // 改回 inside
    }
  });

  const originalItems = messageElement.querySelectorAll('li');
  const exportItems = exportContainer.querySelectorAll('li');
  exportItems.forEach((li, index) => {
    if (originalItems[index]) {
      const computed = window.getComputedStyle(originalItems[index]);
      li.style.display = 'list-item';
      li.style.lineHeight = computed.lineHeight;
      li.style.marginBottom = computed.marginBottom;
      // Fix for list number alignment:
      // Phase 10.15: 继续微调
      // 1. 保持 line-height 1.6 和 vertical-align baseline
      // 2. 加大 padding-top 到 4px，确保明显下移
      li.style.lineHeight = '1.6';
      li.style.verticalAlign = 'baseline';
      li.style.paddingTop = '4px'; // 加大物理下推力度
    }
  });

  // Phase 10.7: 强制修正父容器的 display 属性
  const inlineContainers = exportContainer.querySelectorAll('.katex-inline, [data-formula-display="inline"]');
  inlineContainers.forEach(container => {
    container.style.display = 'inline';
    // container.style.verticalAlign = 'baseline'; // 移除强制基线对齐，保留 KaTeX 原生对齐 (通常是负值)
    container.style.margin = '0 2px';
  });

  const blockContainers = exportContainer.querySelectorAll('.katex-block, [data-formula-display="block"]');
  blockContainers.forEach(container => {
    container.style.display = 'block';
    container.style.margin = '16px auto';
    container.style.textAlign = 'center';
  });

  // Phase 10.6: 强制移除所有 KaTeX 的高度和溢出限制
  const katexBlocks = exportContainer.querySelectorAll('.katex-display, .katex-display-fixed, .katex-block');
  katexBlocks.forEach(block => {
    block.style.overflow = 'visible';
    block.style.overflowY = 'visible';
    block.style.maxHeight = 'none';
    block.style.height = 'auto';
  });

  // 移除所有 KaTeX 元素的高度限制
  const allKatexElements = exportContainer.querySelectorAll('.katex, .katex *');
  allKatexElements.forEach(el => {
    if (el.style.overflow === 'hidden') el.style.overflow = 'visible';
    if (el.style.overflowY === 'hidden') el.style.overflowY = 'visible';
    if (el.style.maxHeight && el.style.maxHeight !== 'none') el.style.maxHeight = 'none';
  });

  // Phase 3.5: 导出前临时展开所有表格，确保完整显示
  const tables = exportContainer.querySelectorAll('.markdown-content table');
  const originalTableStyles = [];
  tables.forEach((table, index) => {
    originalTableStyles[index] = {
      overflow: table.style.overflow || '',
      maxWidth: table.style.maxWidth || '',
      display: table.style.display || ''
    };
    table.style.overflow = 'visible';
    table.style.maxWidth = 'none';
    table.style.display = 'table';
  });

  // 同时移除表格容器的宽度限制
  const messageContainer = exportContainer.querySelector('.assistant-message');
  let originalContainerMaxWidth = '';
  if (messageContainer) {
    originalContainerMaxWidth = messageContainer.style.maxWidth || '';
    messageContainer.style.maxWidth = 'none';
  }

  // Phase 3.5: 动态计算导出容器的最大宽度
  const originalExportContainerMaxWidth = exportContainer.style.maxWidth;
  const originalExportContainerWidth = exportContainer.style.width;

  // 计算所有表格的最大宽度
  let maxTableWidth = 0;
  tables.forEach(table => {
    const tableWidth = table.scrollWidth || 0;
    if (maxTableWidth < tableWidth) {
      maxTableWidth = tableWidth;
    }
  });

  // 根据实际内容动态设置宽度
  const config = window.PerformanceConfig?.EXPORT || { MAX_WIDTH: 800, ABSOLUTE_MAX_WIDTH: 1000 };
  const calculatedWidth = maxTableWidth > 0 && maxTableWidth > 800
    ? Math.min(maxTableWidth + 40, config.ABSOLUTE_MAX_WIDTH)
    : 800;

  exportContainer.style.maxWidth = `${calculatedWidth}px`;
  exportContainer.style.width = `${calculatedWidth}px`;

  // Phase 10: KaTeX 已完全渲染，短暂延迟让 DOM 布局稳定
  const hasKatex = exportContainer.querySelectorAll('.katex').length > 0;
  const layoutDelay = hasKatex ? 300 : 50;

  // 等待DOM重新布局
  setTimeout(() => {
    showToast('正在生成图片...');

    // 强制重排
    exportContainer.offsetHeight;

    const scale = window.PerformanceConfig?.EXPORT?.SCALE || 2;
    html2canvas(exportContainer, {
      scale: scale,
      useCORS: true,
      backgroundColor: 'white',
      logging: false,
      allowTaint: false,
      foreignObjectRendering: false
    }).then(canvas => {
      try {
        const link = document.createElement('a');
        link.download = `paper-burner-ai-${new Date().toISOString().slice(0,10)}.png`;
        link.href = canvas.toDataURL('image/png');
        link.click();
        showToast('图片已导出');
      } catch (err) {
        showToast('导出图片失败');
      } finally {
        // 清理前恢复所有样式
        tables.forEach((table, index) => {
          if (originalTableStyles[index]) {
            table.style.overflow = originalTableStyles[index].overflow;
            table.style.maxWidth = originalTableStyles[index].maxWidth;
            table.style.display = originalTableStyles[index].display;
          }
        });
        if (messageContainer && originalContainerMaxWidth) {
          messageContainer.style.maxWidth = originalContainerMaxWidth;
        }
        exportContainer.style.maxWidth = originalExportContainerMaxWidth;
        exportContainer.style.width = originalExportContainerWidth;
        document.body.removeChild(exportContainer);

        // 释放导出锁
        if (window.ChatbotRenderState) {
          window.ChatbotRenderState.isExporting = false;
        }
      }
    }).catch(err => {
      if (window.PerfLogger) {
        window.PerfLogger.error('生成图片失败:', err);
      }
      showToast('生成图片失败');
      // 错误时也要清理所有样式
      tables.forEach((table, index) => {
        if (originalTableStyles[index]) {
          table.style.overflow = originalTableStyles[index].overflow;
          table.style.maxWidth = originalTableStyles[index].maxWidth;
          table.style.display = originalTableStyles[index].display;
        }
      });
      if (messageContainer && originalContainerMaxWidth) {
        messageContainer.style.maxWidth = originalContainerMaxWidth;
      }
      exportContainer.style.maxWidth = originalExportContainerMaxWidth;
      exportContainer.style.width = originalExportContainerWidth;
      document.body.removeChild(exportContainer);

      // 释放导出锁
      if (window.ChatbotRenderState) {
        window.ChatbotRenderState.isExporting = false;
      }
    });
  }, layoutDelay);
}

/**
 * 当无法直接定位到消息的 DOM 元素时，提供一个后备方案来导出纯文本内容为图片。
 * 这通常发生在 `doExportAsPng` 找不到对应的 `.assistant-message` 元素时。
 * 它会创建一个包含纯文本内容的容器，并尝试将其导出为图片，流程与 `processExport` 类似，
 * 但内容源是直接的字符串而不是 DOM 元素的 innerHTML。
 *
 * @param {string} content 要导出为图片的纯文本内容。
 */
function exportContentDirectly(content) {
  let questionText = "未知问题";
  try {
    // Try to find the last user question in history to associate with this content
    const history = window.ChatbotCore && window.ChatbotCore.chatHistory ? window.ChatbotCore.chatHistory : [];
    for (let i = history.length - 1; i >= 0; i--) {
      if (history[i].role === 'user' && history[i+1] && history[i+1].content === content) {
        questionText = history[i].content;
        if (questionText.length > 60) {
          questionText = questionText.substring(0, 57) + '...';
        }
        break;
      } else if (history[i].role === 'user' && i === history.length -2) { // Fallback if current content is the last one
        questionText = history[i].content;
         if (questionText.length > 60) {
          questionText = questionText.substring(0, 57) + '...';
        }
        break;
      }
    }
  } catch (e) {
    console.error("Error finding question for direct export:", e);
  }
  const exportContainer = document.createElement('div');
  exportContainer.style.position = 'absolute';
  exportContainer.style.left = '-9999px';
  exportContainer.style.padding = '20px';
  exportContainer.style.background = 'white';
  exportContainer.style.borderRadius = '8px';
  exportContainer.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)';
  exportContainer.style.maxWidth = '720px';
  exportContainer.style.width = '80vw';
  exportContainer.style.color = '#111827';
  exportContainer.style.fontSize = '15px';
  exportContainer.style.lineHeight = '1.5';
  const watermark = document.createElement('div');
  watermark.style.position = 'absolute';
  watermark.style.bottom = '10px';
  watermark.style.right = '15px';
  watermark.style.fontSize = '8px';
  watermark.style.opacity = '0.4';
  watermark.textContent = 'Created with Paper Burner';
  const contentContainer = document.createElement('div');
  // For direct content, we should escape it before setting textContent if it might contain HTML
  // However, if the 'content' is already supposed to be plain text, textContent is fine.
  // If 'content' is markdown that was rendered to HTML, we'd need a different approach
  // Assuming 'content' here is mostly plain text or pre-formatted for display.
  contentContainer.style.whiteSpace = 'pre-wrap'; // Preserve line breaks
  contentContainer.style.wordBreak = 'break-word';
  contentContainer.textContent = content;
  exportContainer.appendChild(contentContainer);
  exportContainer.appendChild(watermark);
  document.body.appendChild(exportContainer);
  showToast('正在生成图片...');
  html2canvas(exportContainer, {
    scale: 2,
    useCORS: true,
    backgroundColor: 'white',
    logging: false
  }).then(canvas => {
    try {
      const link = document.createElement('a');
      link.download = `paper-burner-ai-${new Date().toISOString().slice(0,10)}.png`;
      link.href = canvas.toDataURL('image/png');
      link.click();
      showToast('图片已导出');
    } catch (err) {
      showToast('导出图片失败');
    } finally {
      document.body.removeChild(exportContainer);
    }
  }).catch(err => {
    showToast('生成图片失败');
    document.body.removeChild(exportContainer);
  });
}

/**
 * 根据 Markdown 文本生成思维导图的静态 HTML 预览 (虚影效果)。
 * 主要用于在聊天界面快速展示思维导图的结构概览。
 *
 * 实现逻辑：
 * 1. **解析 Markdown 为树结构 (`parseTree`)**：
 *    - 按行分割 Markdown 文本。
 *    - 识别 `#` (一级)、`##` (二级)、`###` (三级) 标题，构建层级关系。
 *    - 返回一个包含 `text` 和 `children` 属性的树状对象。
 * 2. **递归渲染树节点 (`renderNode`)**：
 *    - 接受节点对象、当前层级和是否为最后一个兄弟节点的标记。
 *    - 为不同层级的节点应用不同的背景色、圆点颜色和字体样式，以区分层级。
 *    - 使用绝对定位和相对定位创建连接线和层级缩进的视觉效果。
 *    - 递归渲染子节点。
 * 3. **调用与返回**：
 *    - 调用 `parseTree` 解析传入的 `md` 文本。
 *    - 调用 `renderNode` 渲染根节点。
 *    - 如果生成的 HTML 为空或解析失败，返回一个提示"暂无结构化内容"的 div。
 *
 * @param {string} md Markdown 格式的思维导图文本。
 * @returns {string} 生成的思维导图预览 HTML 字符串。
 */
function renderMindmapShadow(md) {
  // 解析 markdown 为树结构
  function parseTree(md) {
    const lines = md.split(/\r?\n/).filter(l => l.trim());
    const root = { text: '', children: [] };
    let last1 = null, last2 = null;
    lines.forEach(line => {
      let m1 = line.match(/^# (.+)/);
      let m2 = line.match(/^## (.+)/);
      let m3 = line.match(/^### (.+)/);
      if (m1) {
        last1 = { text: m1[1], children: [] };
        root.children.push(last1);
        last2 = null;
      } else if (m2 && last1) {
        last2 = { text: m2[1], children: [] };
        last1.children.push(last2);
      } else if (m3 && last2) {
        last2.children.push({ text: m3[1], children: [] });
      }
    });
    return root;
  }
  // 递归渲染树状结构
  function renderNode(node, level = 0, isLast = true) {
    if (!node.text && node.children.length === 0) return '';
    if (!node.text) {
      // 根节点
      return `<div class=\"mindmap-shadow-root\">${node.children.map((c,i,a)=>renderNode(c,0,i===a.length-1)).join('')}</div>`;
    }
    // 节点样式
    const colors = [
      'rgba(59,130,246,0.13)', // 主节点
      'rgba(59,130,246,0.09)', // 二级
      'rgba(59,130,246,0.06)'  // 三级
    ];
    const dotColors = [
      'rgba(59,130,246,0.35)',
      'rgba(59,130,246,0.22)',
      'rgba(59,130,246,0.15)'
    ];
    let html = `<div class=\"mindmap-shadow-node level${level}\" style=\"position:relative;margin-left:${level*28}px;padding:3px 8px 3px 12px;background:${colors[level]||colors[2]};border-radius:8px;min-width:60px;max-width:260px;margin-bottom:2px;opacity:0.7;border:1px dashed rgba(59,130,246,0.2);\">`;
    // 圆点
    html += `<span style=\"position:absolute;left:-10px;top:50%;transform:translateY(-50%);width:7px;height:7px;border-radius:4px;background:${dotColors[level]||dotColors[2]};box-shadow:0 0 0 1px #e0e7ef;\"></span>`;
    // 线条（如果不是根节点且不是最后一个兄弟）
    if (level > 0) {
      html += `<span style=\"position:absolute;left:-6px;top:0;height:100%;width:1.5px;background:linear-gradient(to bottom,rgba(59,130,246,0.10),rgba(59,130,246,0.03));z-index:0;\"></span>`;
    }
    // Use escapeHtml from the same utils file
    html += `<span style=\"color:#2563eb;font-weight:${level===0?'bold':'normal'};font-size:${level===0?'1.08em':'1em'};\">${escapeHtml(node.text)}</span>`;
    if (node.children && node.children.length > 0) {
      html += `<div class=\"mindmap-shadow-children\" style=\"margin-top:4px;\">${node.children.map((c,i,a)=>renderNode(c,level+1,i===a.length-1)).join('')}</div>`;
    }
    html += '</div>';
    return html;
  }
  const tree = parseTree(md);
  const html = renderNode(tree);
  return html || '<div style=\"color:#94a3b8;opacity:0.5;\">暂无结构化内容</div>';
}

/**
 * 压缩图片到目标大小和尺寸。
 * @param {string} base64Src - Base64 编码的源图片数据。
 * @param {number} targetSizeBytes - 目标文件大小（字节）。
 * @param {number} maxDimension - 图片的最大宽度/高度。
 * @param {number} initialQuality - 初始压缩质量 (0-1)。
 * @returns {Promise<string>} - 压缩后的 Base64 图片数据。
 */
async function compressImage(base64Src, targetSizeBytes, maxDimension, initialQuality = 0.85) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => {
      let canvas = document.createElement('canvas');
      let ctx = canvas.getContext('2d');
      let width = img.width;
      let height = img.height;

      if (width > height) {
        if (width > maxDimension) {
          height = Math.round(height * (maxDimension / width));
          width = maxDimension;
        }
      } else {
        if (height > maxDimension) {
          width = Math.round(width * (maxDimension / height));
          height = maxDimension;
        }
      }
      canvas.width = width;
      canvas.height = height;
      ctx.drawImage(img, 0, 0, width, height);

      let quality = initialQuality;
      let compressedBase64 = canvas.toDataURL('image/jpeg', quality);
      let iterations = 0;
      const maxIterations = 10; // Prevent infinite loop

      // Iteratively reduce quality to meet size target (simplified)
      while (compressedBase64.length * 0.75 > targetSizeBytes && quality > 0.1 && iterations < maxIterations) {
        quality -= 0.1;
        compressedBase64 = canvas.toDataURL('image/jpeg', Math.max(0.1, quality));
        iterations++;
      }

      if (compressedBase64.length * 0.75 > targetSizeBytes && targetSizeBytes < 100 * 1024) { // if still too large for small targets, warn but proceed
         console.warn(`Image compression for small target (${targetSizeBytes}B) resulted in ${Math.round(compressedBase64.length * 0.75 / 1024)}KB. Quality: ${quality.toFixed(2)}`);
      }
      resolve(compressedBase64);
    };
    img.onerror = (err) => {
      console.error("Image loading error for compression:", err, base64Src.substring(0,100));
      reject(new Error('无法加载图片进行压缩'));
    };
    img.src = base64Src;
  });
}

/**
 * 显示带进度条的Toast提示
 * @param {string} message 初始消息
 * @param {number} percent 初始进度 (0-100)
 * @returns {object} 包含update和close方法的对象
 */
function showProgressToast(message, percent = 0) {
  let toast = document.getElementById('chatbot-progress-toast');
  if (!toast) {
    toast = document.createElement('div');
    toast.id = 'chatbot-progress-toast';
    toast.style.cssText = `
      position: fixed;
      top: 20px;
      right: 20px;
      min-width: 300px;
      background: white;
      border: 1px solid #e5e7eb;
      border-radius: 8px;
      box-shadow: 0 4px 12px rgba(0,0,0,0.15);
      padding: 16px;
      z-index: 100002;
      font-family: system-ui, -apple-system, sans-serif;
    `;

    const messageEl = document.createElement('div');
    messageEl.id = 'progress-toast-message';
    messageEl.style.cssText = 'font-size: 14px; color: #374151; margin-bottom: 8px;';

    const progressBg = document.createElement('div');
    progressBg.style.cssText = 'width: 100%; height: 8px; background: #e5e7eb; border-radius: 4px; overflow: hidden;';

    const progressBar = document.createElement('div');
    progressBar.id = 'progress-toast-bar';
    progressBar.style.cssText = 'height: 100%; background: linear-gradient(90deg, #3b82f6, #2563eb); transition: width 0.3s ease; width: 0%;';

    const percentEl = document.createElement('div');
    percentEl.id = 'progress-toast-percent';
    percentEl.style.cssText = 'font-size: 12px; color: #6b7280; margin-top: 4px; text-align: right;';

    progressBg.appendChild(progressBar);
    toast.appendChild(messageEl);
    toast.appendChild(progressBg);
    toast.appendChild(percentEl);
    document.body.appendChild(toast);
  }

  const messageEl = document.getElementById('progress-toast-message');
  const progressBar = document.getElementById('progress-toast-bar');
  const percentEl = document.getElementById('progress-toast-percent');

  messageEl.textContent = message;
  progressBar.style.width = percent + '%';
  percentEl.textContent = percent + '%';

  return {
    update: function(newMessage, newPercent) {
      if (messageEl) messageEl.textContent = newMessage;
      if (progressBar) progressBar.style.width = newPercent + '%';
      if (percentEl) percentEl.textContent = newPercent + '%';
    },
    close: function() {
      if (toast && toast.parentNode) {
        toast.style.opacity = '0';
        toast.style.transition = 'opacity 0.3s';
        setTimeout(() => {
          if (toast.parentNode) toast.parentNode.removeChild(toast);
        }, 300);
      }
    }
  };
}

window.ChatbotUtils = {
  escapeHtml,
  showToast,
  showProgressToast,
  copyAssistantMessage,
  exportMessageAsPng,
  doExportAsPng,
  exportContentDirectly,
  renderMindmapShadow,
  compressImage
};