import { corrigirPontuacaoECodigo, normalizarFimDeLinha, normalizarNewlinesFinais, removerEspacosFinaisPorLinha } from './commons.js';
export function matchMarkdownFence(line) {
    const trimmedLeft = line.replace(/^\s+/, '');
    const metrica = trimmedLeft.match(/^([`~])\1{2,}/);
    if (!metrica)
        return null;
    const ch = (metrica[1] ?? '`') === '~' ? '~' : '`';
    const len = (metrica[0] ?? '').length;
    const rest = trimmedLeft.slice(len);
    return {
        ch,
        len,
        rest
    };
}
export function isMarkdownFenceCloser(match, fenceChar, fenceLen) {
    return fenceChar !== null && match.ch === fenceChar && match.len >= fenceLen && match.rest.trim() === '';
}
function stripDiacritics(input) {
    return input.normalize('NFD').replace(/\p{M}+/gu, '').normalize('NFC');
}
function normalizarCercasMarkdown(code, protectedLines) {
    const lines = normalizarFimDeLinha(code).split('\n');
    let changed = false;
    const out = lines.map((line, idx) => {
        const lineNo = idx + 1;
        if (protectedLines && protectedLines.has(lineNo))
            return line;
        const typo = line.match(/^(\s*)``è(.*)$/);
        if (typo) {
            const prefix = typo[1] ?? '';
            const after = typo[2] ?? '';
            const normalized = `${prefix}\`\`\`${after}`;
            if (normalized !== line)
                changed = true;
            return normalized;
        }
        const metrica = line.match(/^(\s*)(`{3,}|~{3,})(.*)$/);
        if (!metrica)
            return line;
        const prefix = metrica[1] ?? '';
        const fence = metrica[2] ?? '';
        const afterFence = metrica[3] ?? '';
        const info = afterFence.match(/^(\s*)(\S+)(.*)$/);
        if (!info)
            return line;
        const lead = info[1] ?? '';
        const token = info[2] ?? '';
        const tail = info[3] ?? '';
        const shouldNormalizeToken = /[^\x00-\x7F]/.test(token);
        if (!shouldNormalizeToken)
            return line;
        const normalizedToken = stripDiacritics(token);
        const normalized = `${prefix}${fence}${lead}${normalizedToken}${tail}`;
        if (normalized !== line)
            changed = true;
        return normalized;
    });
    return {
        code: out.join('\n'),
        changed
    };
}
function corrigirHtmlInlineEmMarkdown(text, protectedLines) {
    let changed = false;
    const fixed = text.replace(/<[^>\n]+>/g, tag => {
        if (tag.startsWith('<!--') || tag.startsWith('<!'))
            return tag;
        let textoTitulo = tag;
        textoTitulo = textoTitulo.replace(/^<\s+/, '<').replace(/^<\/\s+/, '</');
        textoTitulo = textoTitulo.replace(/\s+\/>$/, '/>').replace(/\s+>$/, '>');
        textoTitulo = textoTitulo.replace(/^<\/\s+/, '</');
        if (textoTitulo !== tag)
            changed = true;
        return textoTitulo;
    });
    return {
        text: fixed,
        changed
    };
}
function corrigirEnfaseMarkdown(text) {
    let changed = false;
    let out = text;
    if (/^\s*[-*+]\s/.test(out))
        return {
            text: out,
            changed: false
        };
    out = out.replace(/\*\*\*([^\s*][^*\n]*?)\*\*\*(?!\*)/g, '**$1**');
    out = out.replace(/(^|[^*])\*([^\s*][^*\n]*?)\*\*(?!\*)/g, '$1*$2*');
    out = out.replace(/(^|[^*])\*([^\s*][^*\n]*?)\*\*\*(?!\*)/g, '$1*$2*');
    if (out !== text)
        changed = true;
    text = out;
    const encontrarFimEnfase = (texto) => {
        const virgula = texto.match(/,\s/);
        if (virgula && virgula.index !== undefined) {
            return virgula.index;
        }
        const pontuacaoForte = texto.match(/[.!?]+$/);
        if (pontuacaoForte && pontuacaoForte.index !== undefined) {
            const antes = texto.slice(0, pontuacaoForte.index);
            const ultimaPalavra = antes.match(/(\S+)$/);
            if (ultimaPalavra) {
                const palavra = ultimaPalavra[1]?.toLowerCase();
                if (palavra) {
                    const abreviacoes = ['sr', 'sra', 'dr', 'dra', 'prof', 'etc', 'ex', 'vs', 'aprox', 'obs', 'v', 'p', 'n', 'e'];
                    if (!abreviacoes.includes(palavra.replace(/[.!?]+$/, ''))) {
                        return pontuacaoForte.index;
                    }
                }
            }
        }
        const temEspacos = texto.includes(' ');
        const terminaPontuacao = /[.!?]$/.test(texto.trimEnd());
        if (temEspacos && terminaPontuacao) {
            return texto.trimEnd().length;
        }
        const palavraUnica = texto.match(/^(\S+)\s+\S/);
        if (palavraUnica && palavraUnica[1]) {
            return palavraUnica[1].length;
        }
        return texto.trimEnd().length;
    };
    const fixarEnfasePorMarcador = (str, marcador) => {
        const len = marcador.length;
        const posicoes = [];
        let idx = 0;
        while ((idx = str.indexOf(marcador, idx)) !== -1) {
            const antes = idx > 0 ? str[idx - 1] : '';
            const depois = idx + len < str.length ? str[idx + len] : '';
            const mesmoChar = marcador[0];
            if (antes === mesmoChar || depois === mesmoChar) {
                idx += len;
                continue;
            }
            posicoes.push(idx);
            idx += len;
        }
        if (posicoes.length === 0 || posicoes.length % 2 === 0)
            return str;
        const ultimoIdx = posicoes[posicoes.length - 1];
        if (ultimoIdx === undefined)
            return str;
        if (ultimoIdx === 0 || ultimoIdx + len >= str.trimEnd().length)
            return str;
        const antesTexto = ultimoIdx > 0 ? str[ultimoIdx - 1] : '';
        const depoisTexto = ultimoIdx + len < str.length ? str[ultimoIdx + len] : '';
        const espacoRegex = /\s/;
        const pareceAbertura = (!antesTexto || espacoRegex.test(antesTexto)) && depoisTexto && !espacoRegex.test(depoisTexto);
        const pareceFechamento = antesTexto && !espacoRegex.test(antesTexto) && (!depoisTexto || espacoRegex.test(depoisTexto));
        if (pareceAbertura) {
            const conteudoApos = str.slice(ultimoIdx + len);
            const fimEnfase = encontrarFimEnfase(conteudoApos);
            const conteudoEnfase = conteudoApos.slice(0, fimEnfase).trimEnd();
            const resto = conteudoApos.slice(fimEnfase);
            return str.slice(0, ultimoIdx + len) + conteudoEnfase + marcador + resto;
        }
        else if (pareceFechamento) {
            const conteudoAntes = str.slice(0, ultimoIdx);
            const conteudoTrimmed = conteudoAntes.trimStart();
            const espacoInicial = conteudoAntes.slice(0, conteudoAntes.length - conteudoTrimmed.length);
            return espacoInicial + marcador + conteudoTrimmed + str.slice(ultimoIdx);
        }
        return str;
    };
    const fixarEnfaseSimples = (str, marcador) => {
        const len = marcador.length;
        const posicoes = [];
        let idx = 0;
        while (idx < str.length) {
            if (str.slice(idx, idx + len) === marcador) {
                const antes = idx > 0 ? str[idx - 1] : '';
                const depois = idx + len < str.length ? str[idx + len] : '';
                const mesmoChar = marcador[0];
                if (antes !== mesmoChar && depois !== mesmoChar) {
                    posicoes.push(idx);
                }
                idx += len;
            }
            else {
                idx++;
            }
        }
        if (posicoes.length === 0 || posicoes.length % 2 === 0)
            return str;
        const ultimoIdx = posicoes[posicoes.length - 1];
        if (ultimoIdx === undefined)
            return str;
        if (ultimoIdx === 0 || ultimoIdx + len >= str.trimEnd().length)
            return str;
        const antesTexto = ultimoIdx > 0 ? str[ultimoIdx - 1] : '';
        const depoisTexto = ultimoIdx + len < str.length ? str[ultimoIdx + len] : '';
        const espacoRegex = /\s/;
        const pareceAbertura = (!antesTexto || espacoRegex.test(antesTexto)) && depoisTexto && !espacoRegex.test(depoisTexto);
        const pareceFechamento = antesTexto && !espacoRegex.test(antesTexto) && (!depoisTexto || espacoRegex.test(depoisTexto));
        if (pareceAbertura) {
            const conteudoApos = str.slice(ultimoIdx + len);
            const fimEnfase = encontrarFimEnfase(conteudoApos);
            const conteudoEnfase = conteudoApos.slice(0, fimEnfase).trimEnd();
            const resto = conteudoApos.slice(fimEnfase);
            return str.slice(0, ultimoIdx + len) + conteudoEnfase + marcador + resto;
        }
        else if (pareceFechamento) {
            const conteudoAntes = str.slice(0, ultimoIdx);
            const conteudoTrimmed = conteudoAntes.trimStart();
            const espacoInicial = conteudoAntes.slice(0, conteudoAntes.length - conteudoTrimmed.length);
            return espacoInicial + marcador + conteudoTrimmed + str.slice(ultimoIdx);
        }
        return str;
    };
    const linhas = out.split('\n');
    const corrigidas = linhas.map(linha => {
        let raiz = linha;
        raiz = fixarEnfasePorMarcador(raiz, '**');
        raiz = fixarEnfasePorMarcador(raiz, '__');
        raiz = fixarEnfaseSimples(raiz, '*');
        raiz = fixarEnfaseSimples(raiz, '_');
        return raiz;
    });
    out = corrigidas.join('\n');
    if (out !== text)
        changed = true;
    return {
        text: out,
        changed
    };
}
function corrigirMarkdownInlineForaDeCodigo(code) {
    const lines = normalizarFimDeLinha(code).split('\n');
    const out = [];
    let changed = false;
    let inFence = false;
    let fenceChar = null;
    let fenceLen = 0;
    const processOutsideCodigoSpans = (text) => {
        let indice = 0;
        let acc = '';
        while (indice < text.length) {
            const tickInicio = text.indexOf('`', indice);
            if (tickInicio === -1) {
                const chunk = text.slice(indice);
                let next = chunk;
                const erro = corrigirEnfaseMarkdown(next);
                next = erro.text;
                const hash = corrigirHtmlInlineEmMarkdown(next);
                next = hash.text;
                if (next !== chunk)
                    changed = true;
                acc += next;
                break;
            }
            const chunk = text.slice(indice, tickInicio);
            let next = chunk;
            const erro = corrigirEnfaseMarkdown(next);
            next = erro.text;
            const hash = corrigirHtmlInlineEmMarkdown(next);
            next = hash.text;
            if (next !== chunk)
                changed = true;
            acc += next;
            let runFim = tickInicio;
            while (runFim < text.length && text[runFim] === '`')
                runFim++;
            const run = text.slice(tickInicio, runFim);
            const closeIndex = text.indexOf(run, runFim);
            if (closeIndex === -1) {
                acc += text.slice(tickInicio);
                break;
            }
            acc += text.slice(tickInicio, closeIndex + run.length);
            indice = closeIndex + run.length;
        }
        return acc;
    };
    for (const line of lines) {
        const fence = matchMarkdownFence(line);
        if (!inFence && fence) {
            inFence = true;
            fenceChar = fence.ch;
            fenceLen = fence.len;
            out.push(line);
            continue;
        }
        if (inFence && fence && isMarkdownFenceCloser(fence, fenceChar, fenceLen)) {
            inFence = false;
            fenceChar = null;
            fenceLen = 0;
            out.push(line);
            continue;
        }
        if (inFence) {
            out.push(line);
            continue;
        }
        out.push(processOutsideCodigoSpans(line));
    }
    return {
        code: out.join('\n'),
        changed
    };
}
function assegurarLinhaVaziaAposTitulosMarkdown(code) {
    const lines = normalizarFimDeLinha(code).split('\n');
    const out = [];
    let changed = false;
    let inFence = false;
    let fenceChar = null;
    let fenceLen = 0;
    for (let indice = 0; indice < lines.length; indice++) {
        const line = lines[indice] ?? '';
        const fence = matchMarkdownFence(line);
        if (!inFence && fence) {
            inFence = true;
            fenceChar = fence.ch;
            fenceLen = fence.len;
            out.push(line);
            continue;
        }
        if (inFence && fence && isMarkdownFenceCloser(fence, fenceChar, fenceLen)) {
            inFence = false;
            fenceChar = null;
            fenceLen = 0;
            out.push(line);
            continue;
        }
        if (inFence) {
            out.push(line);
            continue;
        }
        out.push(line);
        const ehHeading = /^#{1,6}\s+\S/.test(line.trim());
        const proxima = lines[indice + 1];
        const proximaTambemHeading = proxima !== undefined && /^#{1,6}\s+\S/.test(proxima.trim());
        const precisaEspaco = ehHeading && proxima !== undefined && proxima.trim() !== '' && !proximaTambemHeading;
        if (precisaEspaco) {
            out.push('');
            changed = true;
        }
    }
    return {
        code: out.join('\n'),
        changed
    };
}
function formatarListasMarkdown(code) {
    const lines = normalizarFimDeLinha(code).split('\n');
    const out = [];
    let changed = false;
    let inFence = false;
    let fenceChar = null;
    let fenceLen = 0;
    for (let indice = 0; indice < lines.length; indice++) {
        const line = lines[indice] ?? '';
        const fence = matchMarkdownFence(line);
        if (!inFence && fence) {
            inFence = true;
            fenceChar = fence.ch;
            fenceLen = fence.len;
            out.push(line);
            continue;
        }
        if (inFence && fence && isMarkdownFenceCloser(fence, fenceChar, fenceLen)) {
            inFence = false;
            fenceChar = null;
            fenceLen = 0;
            out.push(line);
            continue;
        }
        if (inFence) {
            out.push(line);
            continue;
        }
        const listMatch = line.match(/^(\s*)([*+\-])\s+(.*)$/);
        if (listMatch) {
            const indent = listMatch[1] ?? '';
            const content = listMatch[3] ?? '';
            const normalized = `${indent}- ${content}`;
            if (normalized !== line) {
                changed = true;
            }
            out.push(normalized);
            continue;
        }
        out.push(line);
    }
    return {
        code: out.join('\n'),
        changed
    };
}
function formatarTabelasMarkdown(code) {
    const lines = normalizarFimDeLinha(code).split('\n');
    const out = [];
    let changed = false;
    let inFence = false;
    let fenceChar = null;
    let fenceLen = 0;
    let inTable = false;
    let tableRows = [];
    const processTable = () => {
        if (tableRows.length === 0)
            return;
        const hasSeparatorRow = tableRows.length >= 2 && /^\s*[-:|]+\s*$/.test(tableRows[1] ?? '');
        const colCounts = tableRows.map(row => {
            const cells = row.split('|').filter((_, posAtual, arr) => posAtual > 0 && posAtual < arr.length - 1);
            return cells.length;
        });
        const consistentCols = colCounts.every(elem => elem === colCounts[0]);
        if (hasSeparatorRow && consistentCols) {
            for (const row of tableRows) {
                out.push(row);
            }
            tableRows = [];
            inTable = false;
            return;
        }
        const parsedRows = tableRows.map(row => {
            const content = row.replace(/^\s*\|/, '').replace(/\|\s*$/, '');
            return content.split('|').map(cell => cell.trim());
        });
        const colCount = Math.max(...parsedRows.map(r => r.length));
        const colWidths = [];
        for (let elem = 0; elem < colCount; elem++) {
            const maxWidth = Math.max(...parsedRows.map(row => {
                const cell = row[elem] ?? '';
                return cell.length;
            }));
            colWidths.push(maxWidth);
        }
        for (let raiz = 0; raiz < parsedRows.length; raiz++) {
            const row = parsedRows[raiz] ?? [];
            const cells = row.map((cell, elem) => {
                const width = colWidths[elem] ?? 0;
                return ` ${cell.padEnd(width)} `;
            });
            let line = `|${cells.join('|')}|`;
            if (raiz === 1) {
                const separators = colWidths.map(dimensaoLargura => ` ${'-'.repeat(Math.max(dimensaoLargura, 3))} `);
                line = `|${separators.join('|')}|`;
            }
            out.push(line);
        }
        changed = true;
        tableRows = [];
        inTable = false;
    };
    for (let indice = 0; indice < lines.length; indice++) {
        const line = lines[indice] ?? '';
        const fence = matchMarkdownFence(line);
        if (!inFence && fence) {
            if (inTable) {
                processTable();
            }
            inFence = true;
            fenceChar = fence.ch;
            fenceLen = fence.len;
            out.push(line);
            continue;
        }
        if (inFence && fence && isMarkdownFenceCloser(fence, fenceChar, fenceLen)) {
            if (inTable) {
                processTable();
            }
            inFence = false;
            fenceChar = null;
            fenceLen = 0;
            out.push(line);
            continue;
        }
        if (inFence) {
            out.push(line);
            continue;
        }
        const tableMatch = line.match(/^\s*\|(.+)\|\s*$/);
        if (tableMatch || inTable && line.trim().startsWith('|')) {
            if (!inTable) {
                inTable = true;
                tableRows = [];
            }
            tableRows.push(line);
            continue;
        }
        else if (inTable) {
            processTable();
        }
        out.push(line);
    }
    if (inTable) {
        processTable();
    }
    return {
        code: out.join('\n'),
        changed
    };
}
function limitarLinhasEmBrancoMarkdown(code, maxConsecutivas = 2) {
    const lines = normalizarFimDeLinha(code).split('\n');
    const out = [];
    let consecutive = 0;
    let changed = false;
    let inFence = false;
    let fenceChar = null;
    let fenceLen = 0;
    for (const line of lines) {
        const fence = matchMarkdownFence(line);
        if (!inFence && fence) {
            inFence = true;
            fenceChar = fence.ch;
            fenceLen = fence.len;
            consecutive = 0;
            out.push(line);
            continue;
        }
        if (inFence && fence && isMarkdownFenceCloser(fence, fenceChar, fenceLen)) {
            inFence = false;
            fenceChar = null;
            fenceLen = 0;
            consecutive = 0;
            out.push(line);
            continue;
        }
        if (inFence) {
            out.push(line);
            continue;
        }
        const isBlank = line.trim() === '';
        if (isBlank) {
            consecutive += 1;
            if (consecutive > maxConsecutivas) {
                changed = true;
                continue;
            }
        }
        else {
            consecutive = 0;
        }
        out.push(line);
    }
    return {
        code: out.join('\n'),
        changed
    };
}
export function formatarMarkdownMinimo(code, protectedLines) {
    const normalized = normalizarFimDeLinha(code);
    const trimmed = removerEspacosFinaisPorLinha(normalized);
    const { code: cercasNormalizadas, changed: changedFences } = normalizarCercasMarkdown(trimmed, protectedLines);
    const { code: markdownCorrigido, changed: changedInline } = corrigirMarkdownInlineForaDeCodigo(cercasNormalizadas);
    const { code: comEspacoTitulos, changed: changedHeadings } = assegurarLinhaVaziaAposTitulosMarkdown(markdownCorrigido);
    const { code: comListasFormatadas, changed: changedListas } = formatarListasMarkdown(comEspacoTitulos);
    const { code: comTabelasFormatadas, changed: changedTabelas } = formatarTabelasMarkdown(comListasFormatadas);
    const { code: comPontuacaoCorrigida, changed: changedPunctuation, stats } = corrigirPontuacaoECodigoPreservandoCodeFences(comTabelasFormatadas, protectedLines);
    const { code: semBlanks, changed: changedBlanks } = limitarLinhasEmBrancoMarkdown(comPontuacaoCorrigida, 2);
    const formatted = normalizarNewlinesFinais(semBlanks);
    const anyChanged = changedFences || changedInline || changedHeadings || changedListas || changedTabelas || changedBlanks || changedPunctuation;
    return {
        ok: true,
        parser: 'markdown',
        formatted,
        changed: formatted !== normalizarNewlinesFinais(normalized),
        reason: anyChanged ? `estilo-tutanus-markdown(punct:${stats.pontuacaoRepetida},space:${stats.espacamentoIncorreto},ops:${stats.operadoresRelacionais})` : 'normalizacao-basica'
    };
}
function corrigirPontuacaoECodigoPreservandoCodeFences(code, protectedLines) {
    const lines = normalizarFimDeLinha(code).split('\n');
    const out = [];
    let inFence = false;
    let fenceChar = null;
    let fenceLen = 0;
    const totalStats = {
        pontuacaoRepetida: 0,
        espacamentoIncorreto: 0,
        operadoresRelacionais: 0
    };
    for (let indice = 0; indice < lines.length; indice++) {
        const line = lines[indice] ?? '';
        const lineNo = indice + 1;
        const fence = matchMarkdownFence(line);
        if (!inFence && fence) {
            inFence = true;
            fenceChar = fence.ch;
            fenceLen = fence.len;
            out.push(line);
            continue;
        }
        if (inFence && fence && isMarkdownFenceCloser(fence, fenceChar, fenceLen)) {
            inFence = false;
            fenceChar = null;
            fenceLen = 0;
            out.push(line);
            continue;
        }
        if (inFence) {
            out.push(line);
            continue;
        }
        if (protectedLines && protectedLines.has(lineNo)) {
            out.push(line);
            continue;
        }
        const { text: corrigida, stats } = corrigirPontuacaoECodigo(line);
        totalStats.pontuacaoRepetida += stats.pontuacaoRepetida;
        totalStats.espacamentoIncorreto += stats.espacamentoIncorreto;
        totalStats.operadoresRelacionais += stats.operadoresRelacionais;
        out.push(corrigida);
    }
    const changed = totalStats.pontuacaoRepetida > 0 || totalStats.espacamentoIncorreto > 0 || totalStats.operadoresRelacionais > 0;
    return {
        code: out.join('\n'),
        changed,
        stats: totalStats
    };
}
//# sourceMappingURL=markdown.js.map