import { marked } from "marked";

type MarkdownToHtmlOptions = {
    breaks?: boolean;
};

type CodeFenceBlock = {
    placeholder: string;
    html: string;
};

function escapeHtml(raw: string): string {
    return raw
        .replaceAll("&", "&amp;")
        .replaceAll("<", "&lt;")
        .replaceAll(">", "&gt;")
        .replaceAll('"', "&quot;")
        .replaceAll("'", "&#39;");
}

function escapeRegExp(raw: string): string {
    return raw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function buildCodeBlockHtml(langRaw: string, codeRaw: string): string {
    const language = (langRaw || "").trim().split(/\s+/)[0];
    const classAttr = language ? ` class="language-${escapeHtml(language)}"` : "";
    return `<pre><code${classAttr}>${escapeHtml(codeRaw)}</code></pre>`;
}

function parseWithMarked(markdown: string, breaks: boolean): string {
    return marked.parse(markdown, { gfm: true, breaks }) as string;
}

function scanAndMaskFencedCodeBlocks(markdown: string): { masked: string; blocks: CodeFenceBlock[] } {
    const normalized = markdown.replace(/\r\n?/g, "\n");
    const lines = normalized.split("\n");
    const output: string[] = [];
    const blocks: CodeFenceBlock[] = [];
    let blockIndex = 0;

    let inFence = false;
    let fenceChar = "";
    let fenceLength = 0;
    let fenceInfo = "";
    let placeholder = "";
    let contentLines: string[] = [];

    for (const line of lines) {
        if (!inFence) {
            const open = line.match(/^ {0,3}(`{3,}|~{3,})([^\n]*)\s*$/);
            if (!open) {
                output.push(line);
                continue;
            }

            inFence = true;
            fenceChar = open[1][0];
            fenceLength = open[1].length;
            fenceInfo = open[2] || "";
            contentLines = [];
            placeholder = `@@CODE_BLOCK_${blockIndex++}@@`;
            output.push(placeholder);
            continue;
        }

        const closeRe = new RegExp(`^ {0,3}${escapeRegExp(fenceChar)}{${fenceLength},}\\s*$`);
        if (closeRe.test(line)) {
            blocks.push({
                placeholder,
                html: buildCodeBlockHtml(fenceInfo, contentLines.join("\n")),
            });
            inFence = false;
            fenceChar = "";
            fenceLength = 0;
            fenceInfo = "";
            placeholder = "";
            contentLines = [];
            continue;
        }

        contentLines.push(line);
    }

    if (inFence && placeholder) {
        blocks.push({
            placeholder,
            html: buildCodeBlockHtml(fenceInfo, contentLines.join("\n")),
        });
    }

    return { masked: output.join("\n"), blocks };
}

function parseWithExplicitFences(markdown: string, breaks: boolean): string {
    const { masked, blocks } = scanAndMaskFencedCodeBlocks(markdown);
    let html = parseWithMarked(masked, breaks);

    for (const block of blocks) {
        html = html.replace(`<p>${block.placeholder}</p>`, block.html);
        html = html.split(block.placeholder).join(block.html);
    }

    return html;
}

export function cleanupAccidentalStandaloneInlineCode(html: string): string {
    const standaloneMatches = html.match(/<(p|h[1-6]|li)><code>[\s\S]*?<\/code><\/\1>/g) || [];
    if (standaloneMatches.length < 3) return html;

    const headingCodeMatches = html.match(/<h[1-6]><code>[\s\S]*?<\/code><\/h[1-6]>/g) || [];
    const suspiciousCount = standaloneMatches.filter((block) => /https?:\/\/|@\w|\/\s*\d{3,}|[一-龥]/.test(block)).length;
    const hasPreBlock = /<pre>\s*<code(?:\s|>)/.test(html);

    // 如果文档已包含正确的 <pre><code> 块，说明代码块解析正常
    // 此时只在 heading 中出现 <code> 时才触发清理，避免对正常文档误删
    const looksMalformed =
        headingCodeMatches.length > 0 ||
        (!hasPreBlock && standaloneMatches.length >= 5 && suspiciousCount >= 3);
    if (!looksMalformed) return html;

    return html.replace(/<(p|h[1-6]|li)><code>([\s\S]*?)<\/code><\/\1>/g, "<$1>$2</$1>");
}

export function markdownToImportHtml(markdown: string, options: MarkdownToHtmlOptions = {}): string {
    const breaks = options.breaks ?? true;
    const html = parseWithExplicitFences(markdown, breaks);
    return cleanupAccidentalStandaloneInlineCode(html);
}
