// markdown 渲染 + 代码高亮。依赖 vendor 全局(window.marked / DOMPurify / hljs)。
// 三个库任一缺失 → 优雅降级回 <pre>escapeHtml</pre>(plain text wrap)。
import { escapeHtml } from "./format.js";

if (window.marked && window.marked.setOptions) {
  window.marked.setOptions({ gfm: true, breaks: true, headerIds: false, mangle: false });
}

const FENCE_RE = /^( {0,3})(`{3,}|~{3,})([^\r\n]*)(\r?\n)?$/;
const MERMAID_CONFIG = Object.freeze({
  startOnLoad: false,
  securityLevel: "strict",
  suppressErrorRendering: true,
  theme: "neutral",
  maxTextSize: 50000,
  maxEdges: 500,
  fontFamily: '-apple-system, "Segoe UI", "Microsoft YaHei", sans-serif',
});
let mermaidInitialized = false;
let mermaidSeq = 0;

function parseFence(line) {
  const m = String(line || "").match(FENCE_RE);
  if (!m) return null;
  return { indent: m[1], char: m[2][0], len: m[2].length, info: m[3].trim() };
}

function nextNonblank(lines, start) {
  for (let i = start; i < lines.length; i++) {
    if (lines[i].trim()) return i;
  }
  return -1;
}

function replaceFence(line, length) {
  const m = String(line || "").match(FENCE_RE);
  if (!m) return line;
  return m[1] + m[2][0].repeat(length) + m[3] + (m[4] || "");
}

// 只修复明确的「markdown 外层与内层语言块使用同长围栏」形态。
// 其他残缺 Markdown 原样交给 marked，避免猜测作者意图。
export function normalizeMarkdownFences(text) {
  const lines = String(text || "").match(/.*(?:\r\n|\n|$)/g).filter(Boolean);
  for (let i = 0; i < lines.length;) {
    const outer = parseFence(lines[i]);
    if (!outer || !["markdown", "md"].includes(outer.info.toLowerCase())) {
      i++;
      continue;
    }
    const innerIdx = nextNonblank(lines, i + 1);
    const inner = innerIdx >= 0 ? parseFence(lines[innerIdx]) : null;
    if (!inner || !inner.info || inner.char !== outer.char || inner.len < outer.len) {
      i++;
      continue;
    }
    let innerCloseIdx = -1;
    for (let j = innerIdx + 1; j < lines.length; j++) {
      const close = parseFence(lines[j]);
      if (close && !close.info && close.char === inner.char && close.len >= inner.len) {
        innerCloseIdx = j;
        break;
      }
    }
    if (innerCloseIdx < 0) {
      i++;
      continue;
    }
    const outerCloseIdx = nextNonblank(lines, innerCloseIdx + 1);
    const outerClose = outerCloseIdx >= 0 ? parseFence(lines[outerCloseIdx]) : null;
    if (!outerClose || outerClose.info || outerClose.char !== outer.char || outerClose.len < outer.len) {
      i++;
      continue;
    }
    const repairedLen = Math.max(outer.len, inner.len) + 1;
    lines[i] = replaceFence(lines[i], repairedLen);
    lines[outerCloseIdx] = replaceFence(lines[outerCloseIdx], repairedLen);
    i = outerCloseIdx + 1;
  }
  return lines.join("");
}

export function renderMd(text) {
  const raw = normalizeMarkdownFences(text);
  if (!window.marked || !window.marked.parse) {
    return `<pre style="white-space:pre-wrap;word-break:break-word;font-family:inherit;margin:0;">${escapeHtml(raw)}</pre>`;
  }
  let html = window.marked.parse(raw);
  if (window.DOMPurify) {
    html = window.DOMPurify.sanitize(html, { USE_PROFILES: { html: true } });
  }
  return html;
}

function getMermaidApi() {
  const api = window.mermaid;
  if (!api || typeof api.initialize !== "function" || typeof api.render !== "function") {
    return null;
  }
  if (!mermaidInitialized) {
    api.initialize(MERMAID_CONFIG);
    mermaidInitialized = true;
  }
  return api;
}

function mermaidNotice(pre, className, text) {
  const doc = pre.ownerDocument || document;
  const notice = doc.createElement("div");
  notice.className = className;
  notice.textContent = text;
  pre.before(notice);
}

/** Render completed assistant Mermaid blocks, preserving source on failure. */
export async function renderMermaidIn(container) {
  const result = { rendered: 0, failed: 0, unavailable: 0 };
  if (!container || typeof container.querySelectorAll !== "function") return result;

  const blocks = Array.from(container.querySelectorAll("pre > code.language-mermaid"))
    .filter((code) => !code.dataset.mermaidState);
  if (!blocks.length) return result;

  const api = getMermaidApi();
  if (!api) {
    for (const code of blocks) {
      code.dataset.mermaidState = "unavailable";
      mermaidNotice(code.parentElement, "mermaid-notice", "图表组件未加载，已保留 Mermaid 源码。");
      result.unavailable++;
    }
    return result;
  }

  // Mermaid owns shared configuration/state; render sequentially to avoid races.
  for (const code of blocks) {
    const pre = code.parentElement;
    code.dataset.mermaidState = "rendering";
    try {
      const id = `zcbot-mermaid-${Date.now()}-${++mermaidSeq}`;
      const rendered = await api.render(id, code.textContent || "");
      if (!pre || !pre.isConnected) continue;

      const doc = pre.ownerDocument || document;
      const figure = doc.createElement("figure");
      figure.className = "mermaid-diagram";
      figure.setAttribute("role", "img");
      figure.setAttribute("aria-label", "Mermaid 图表");
      const viewport = doc.createElement("div");
      viewport.className = "mermaid-viewport";
      viewport.innerHTML = rendered.svg;
      figure.appendChild(viewport);
      pre.replaceWith(figure);
      if (typeof rendered.bindFunctions === "function") rendered.bindFunctions(viewport);
      result.rendered++;
    } catch (_error) {
      code.dataset.mermaidState = "error";
      if (pre && pre.isConnected) {
        mermaidNotice(pre, "mermaid-notice error", "Mermaid 图表语法有误，已保留源码。");
      }
      result.failed++;
    }
  }
  return result;
}

export function highlightIn(container) {
  if (!window.hljs || !container) return;
  container.querySelectorAll("pre code").forEach((b) => {
    if (b.dataset.hl === "1") return;
    if (b.classList && b.classList.contains("language-mermaid")) return;
    try { window.hljs.highlightElement(b); b.dataset.hl = "1"; } catch (e) {}
  });
}
