const STYLE_ORDER = [
    "blockquote",
    "code_block",
    "code",
    "bold",
    "italic",
    "strikethrough",
    "spoiler",
];
const STYLE_RANK = new Map(STYLE_ORDER.map((style, index) => [style, index]));
function sortStyleSpans(spans) {
    return [...spans].sort((a, b) => {
        if (a.start !== b.start)
            return a.start - b.start;
        if (a.end !== b.end)
            return b.end - a.end;
        return (STYLE_RANK.get(a.style) ?? 0) - (STYLE_RANK.get(b.style) ?? 0);
    });
}
export function renderMarkdownWithMarkers(ir, options) {
    const text = ir.text ?? "";
    if (!text)
        return "";
    const styleMarkers = options.styleMarkers;
    const styled = sortStyleSpans(ir.styles.filter((span) => Boolean(styleMarkers[span.style])));
    const boundaries = new Set();
    boundaries.add(0);
    boundaries.add(text.length);
    const startsAt = new Map();
    for (const span of styled) {
        if (span.start === span.end)
            continue;
        boundaries.add(span.start);
        boundaries.add(span.end);
        const bucket = startsAt.get(span.start);
        if (bucket)
            bucket.push(span);
        else
            startsAt.set(span.start, [span]);
    }
    for (const spans of startsAt.values()) {
        spans.sort((a, b) => {
            if (a.end !== b.end)
                return b.end - a.end;
            return (STYLE_RANK.get(a.style) ?? 0) - (STYLE_RANK.get(b.style) ?? 0);
        });
    }
    const linkStarts = new Map();
    if (options.buildLink) {
        for (const link of ir.links) {
            if (link.start === link.end)
                continue;
            const rendered = options.buildLink(link, text);
            if (!rendered)
                continue;
            boundaries.add(rendered.start);
            boundaries.add(rendered.end);
            const openBucket = linkStarts.get(rendered.start);
            if (openBucket)
                openBucket.push(rendered);
            else
                linkStarts.set(rendered.start, [rendered]);
        }
    }
    const points = [...boundaries].sort((a, b) => a - b);
    // Unified stack for both styles and links, tracking close string and end position
    const stack = [];
    let out = "";
    for (let i = 0; i < points.length; i += 1) {
        const pos = points[i];
        // Close ALL elements (styles and links) in LIFO order at this position
        while (stack.length && stack[stack.length - 1]?.end === pos) {
            const item = stack.pop();
            if (item)
                out += item.close;
        }
        const openingItems = [];
        const openingLinks = linkStarts.get(pos);
        if (openingLinks && openingLinks.length > 0) {
            for (const [index, link] of openingLinks.entries()) {
                openingItems.push({
                    end: link.end,
                    open: link.open,
                    close: link.close,
                    kind: "link",
                    index,
                });
            }
        }
        const openingStyles = startsAt.get(pos);
        if (openingStyles) {
            for (const [index, span] of openingStyles.entries()) {
                const marker = styleMarkers[span.style];
                if (!marker)
                    continue;
                openingItems.push({
                    end: span.end,
                    open: marker.open,
                    close: marker.close,
                    kind: "style",
                    style: span.style,
                    index,
                });
            }
        }
        if (openingItems.length > 0) {
            openingItems.sort((a, b) => {
                if (a.end !== b.end)
                    return b.end - a.end;
                if (a.kind !== b.kind)
                    return a.kind === "link" ? -1 : 1;
                if (a.kind === "style" && b.kind === "style") {
                    return (STYLE_RANK.get(a.style) ?? 0) - (STYLE_RANK.get(b.style) ?? 0);
                }
                return a.index - b.index;
            });
            // Open outer spans first (larger end) so LIFO closes stay valid for same-start overlaps.
            for (const item of openingItems) {
                out += item.open;
                stack.push({ close: item.close, end: item.end });
            }
        }
        const next = points[i + 1];
        if (next === undefined)
            break;
        if (next > pos) {
            out += options.escapeText(text.slice(pos, next));
        }
    }
    return out;
}
