import { trimTrailingBlank } from './types.js';
const HEADING = /^(#{1,6})\s+(.+?)\s*#*\s*$/;
const FENCE = /^\s*(```|~~~)/;
/**
 * Classify Markdown lines with the extractor's fence semantics.
 *
 * Keeping this as the single fence-state implementation matters beyond heading
 * extraction: consumers that inspect prose must not rediscover identifiers from
 * examples the entity model deliberately ignored.
 */
export function scanMarkdownLines(content) {
    let fence;
    return content.split(/\r?\n/).map((text, position) => {
        const fenceMatch = FENCE.exec(text);
        if (fenceMatch) {
            if (fence === undefined)
                fence = fenceMatch[1];
            else if (text.trimStart().startsWith(fence))
                fence = undefined;
            return { lineNumber: position + 1, text, fenced: true };
        }
        return { lineNumber: position + 1, text, fenced: fence !== undefined };
    });
}
/**
 * Markdown and MDX heading sections.
 *
 * Fenced code blocks are tracked, so a shell comment inside a fence is not
 * mistaken for a heading — a `# install` line inside a ```bash block is
 * extremely common in a README, and treating it as a section produces an entity
 * that appears and disappears as the example is edited.
 */
export function extractMarkdownEntities(content) {
    const lines = content.split(/\r?\n/);
    const lineStates = scanMarkdownLines(content);
    const entities = [];
    const open = [];
    const seenNames = new Map();
    let preamble;
    const close = (level, endLine) => {
        while (open.length > 0 && open[open.length - 1].level >= level) {
            const section = open.pop();
            if (section)
                section.entity.endLine = trimTrailingBlank(lines, endLine, section.entity.startLine);
        }
    };
    for (const { lineNumber, text: line, fenced } of lineStates) {
        if (fenced)
            continue;
        const match = HEADING.exec(line);
        if (!match) {
            if (entities.length === 0 && !preamble && line.trim() !== '') {
                preamble = {
                    kind: 'heading',
                    name: '(preamble)',
                    qualifiedName: '(preamble)',
                    startLine: lineNumber,
                    endLine: lineNumber,
                };
                entities.push(preamble);
            }
            continue;
        }
        const level = match[1].length;
        const text = match[2].trim();
        close(level, lineNumber - 1);
        if (preamble && preamble.endLine < lineNumber - 1) {
            preamble.endLine = trimTrailingBlank(lines, lineNumber - 1, preamble.startLine);
            preamble = undefined;
        }
        const parent = open[open.length - 1];
        // A duplicate heading gets its line appended, and that suffix propagates
        // into its children's paths. Without it every "### Fixed" in a changelog
        // collapses onto one entity and its history becomes the union of them all.
        const base = parent ? `${parent.entity.qualifiedName} > ${text}` : text;
        const previous = seenNames.get(base);
        seenNames.set(base, (previous ?? 0) + 1);
        const qualifiedName = previous === undefined ? base : `${base}@L${lineNumber}`;
        const entity = {
            kind: 'heading',
            name: text,
            qualifiedName,
            containerName: parent?.entity.qualifiedName,
            startLine: lineNumber,
            endLine: lines.length,
        };
        entities.push(entity);
        open.push({ level, entity });
    }
    close(0, lines.length);
    if (preamble)
        preamble.endLine = trimTrailingBlank(lines, preamble.endLine, preamble.startLine);
    return entities;
}
//# sourceMappingURL=markdown.js.map