function _escapeNodeLabel(nodeLabel) {
    // Escapes the node label for Mermaid syntax.
    return nodeLabel.replace(/[^a-zA-Z-_0-9]/g, "_");
}
const MARKDOWN_SPECIAL_CHARS = ["*", "_", "`"];
function _generateMermaidGraphStyles(nodeColors) {
    let styles = "";
    for (const [className, color] of Object.entries(nodeColors)) {
        styles += `\tclassDef ${className} ${color};\n`;
    }
    return styles;
}
/**
 * Draws a Mermaid graph using the provided graph data
 */
export function drawMermaid(nodes, edges, config) {
    const { firstNode, lastNode, nodeColors, withStyles = true, curveStyle = "linear", wrapLabelNWords = 9, } = config ?? {};
    // Initialize Mermaid graph configuration
    let mermaidGraph = withStyles
        ? `%%{init: {'flowchart': {'curve': '${curveStyle}'}}}%%\ngraph TD;\n`
        : "graph TD;\n";
    if (withStyles) {
        // Node formatting templates
        const defaultClassLabel = "default";
        const formatDict = {
            [defaultClassLabel]: "{0}({1})",
        };
        if (firstNode !== undefined) {
            formatDict[firstNode] = "{0}([{1}]):::first";
        }
        if (lastNode !== undefined) {
            formatDict[lastNode] = "{0}([{1}]):::last";
        }
        // Add nodes to the graph
        for (const [key, node] of Object.entries(nodes)) {
            const nodeName = node.name.split(":").pop() ?? "";
            const label = MARKDOWN_SPECIAL_CHARS.some((char) => nodeName.startsWith(char) && nodeName.endsWith(char))
                ? `<p>${nodeName}</p>`
                : nodeName;
            let finalLabel = label;
            if (Object.keys(node.metadata ?? {}).length) {
                finalLabel += `<hr/><small><em>${Object.entries(node.metadata ?? {})
                    .map(([k, v]) => `${k} = ${v}`)
                    .join("\n")}</em></small>`;
            }
            const nodeLabel = (formatDict[key] ?? formatDict[defaultClassLabel])
                .replace("{0}", _escapeNodeLabel(key))
                .replace("{1}", finalLabel);
            mermaidGraph += `\t${nodeLabel}\n`;
        }
    }
    // Group edges by their common prefixes
    const edgeGroups = {};
    for (const edge of edges) {
        const srcParts = edge.source.split(":");
        const tgtParts = edge.target.split(":");
        const commonPrefix = srcParts
            .filter((src, i) => src === tgtParts[i])
            .join(":");
        if (!edgeGroups[commonPrefix]) {
            edgeGroups[commonPrefix] = [];
        }
        edgeGroups[commonPrefix].push(edge);
    }
    const seenSubgraphs = new Set();
    function addSubgraph(edges, prefix) {
        const selfLoop = edges.length === 1 && edges[0].source === edges[0].target;
        if (prefix && !selfLoop) {
            const subgraph = prefix.split(":").pop();
            if (seenSubgraphs.has(subgraph)) {
                throw new Error(`Found duplicate subgraph '${subgraph}' -- this likely means that ` +
                    "you're reusing a subgraph node with the same name. " +
                    "Please adjust your graph to have subgraph nodes with unique names.");
            }
            seenSubgraphs.add(subgraph);
            mermaidGraph += `\tsubgraph ${subgraph}\n`;
        }
        for (const edge of edges) {
            const { source, target, data, conditional } = edge;
            let edgeLabel = "";
            if (data !== undefined) {
                let edgeData = data;
                const words = edgeData.split(" ");
                if (words.length > wrapLabelNWords) {
                    edgeData = Array.from({ length: Math.ceil(words.length / wrapLabelNWords) }, (_, i) => words
                        .slice(i * wrapLabelNWords, (i + 1) * wrapLabelNWords)
                        .join(" ")).join("&nbsp;<br>&nbsp;");
                }
                edgeLabel = conditional
                    ? ` -. &nbsp;${edgeData}&nbsp; .-> `
                    : ` -- &nbsp;${edgeData}&nbsp; --> `;
            }
            else {
                edgeLabel = conditional ? " -.-> " : " --> ";
            }
            mermaidGraph += `\t${_escapeNodeLabel(source)}${edgeLabel}${_escapeNodeLabel(target)};\n`;
        }
        // Recursively add nested subgraphs
        for (const nestedPrefix in edgeGroups) {
            if (nestedPrefix.startsWith(`${prefix}:`) && nestedPrefix !== prefix) {
                addSubgraph(edgeGroups[nestedPrefix], nestedPrefix);
            }
        }
        if (prefix && !selfLoop) {
            mermaidGraph += "\tend\n";
        }
    }
    // Start with the top-level edges (no common prefix)
    addSubgraph(edgeGroups[""] ?? [], "");
    // Add remaining subgraphs
    for (const prefix in edgeGroups) {
        if (!prefix.includes(":") && prefix !== "") {
            addSubgraph(edgeGroups[prefix], prefix);
        }
    }
    // Add custom styles for nodes
    if (withStyles) {
        mermaidGraph += _generateMermaidGraphStyles(nodeColors ?? {});
    }
    return mermaidGraph;
}
/**
 * Renders Mermaid graph using the Mermaid.INK API.
 */
export async function drawMermaidPng(mermaidSyntax, config) {
    let { backgroundColor = "white" } = config ?? {};
    // Use btoa for compatibility, assume ASCII
    const mermaidSyntaxEncoded = btoa(mermaidSyntax);
    // Check if the background color is a hexadecimal color code using regex
    if (backgroundColor !== undefined) {
        const hexColorPattern = /^#(?:[0-9a-fA-F]{3}){1,2}$/;
        if (!hexColorPattern.test(backgroundColor)) {
            backgroundColor = `!${backgroundColor}`;
        }
    }
    const imageUrl = `https://mermaid.ink/img/${mermaidSyntaxEncoded}?bgColor=${backgroundColor}`;
    const res = await fetch(imageUrl);
    if (!res.ok) {
        throw new Error([
            `Failed to render the graph using the Mermaid.INK API.`,
            `Status code: ${res.status}`,
            `Status text: ${res.statusText}`,
        ].join("\n"));
    }
    const content = await res.blob();
    return content;
}
