import { messages } from '../../../core/messages/index.js';
import { createLineLookup, maskXmlNonCode } from '../../../shared/helpers/index.js';
import { criarAnalista, criarOcorrencia } from '../../../types/index.js';
const disableEnv = process.env.TUTANUS_DISABLE_PLUGIN_XML === '1';
function warn(message, relPath, line, nivel = messages.SeverityNiveis.warning) {
    return criarOcorrencia({
        relPath,
        mensagem: message,
        linha: line,
        nivel,
        origem: messages.AnalystOrigens.xml,
        tipo: messages.AnalystTipos.xml
    });
}
function collectXmlIssues(src, relPath) {
    const ocorrencias = [];
    const scan = maskXmlNonCode(src);
    const lineOf = createLineLookup(scan).lineAt;
    const trimmed = src.trimStart();
    const seemsCompleteDocument = /^<\w/i.test(trimmed) && !trimmed.includes('<?xml');
    if (seemsCompleteDocument && /^</.test(trimmed)) {
        ocorrencias.push(warn(messages.XmlMensagens.xmlPrologAusente, relPath, 1, messages.SeverityNiveis.info));
    }
    const tagRegex = /<\/?([a-zA-Z_][\w.-]*(?::[a-zA-Z_][\w.-]*)?)(?:\s[^>]*)?>/g;
    let match;
    const tagStack = [];
    while ((match = tagRegex.exec(scan)) !== null) {
        const fullTag = match[0];
        const tagNome = match[1];
        const line = lineOf(match.index);
        if (fullTag.startsWith('</')) {
            const expected = tagStack?.pop();
            if (expected === undefined || expected !== tagNome) {
                ocorrencias.push(warn(messages.XmlMensagens.invalidXmlStructure, relPath, line, messages.SeverityNiveis.error));
                break;
            }
        }
        else if (!fullTag.endsWith('/>')) {
            tagStack.push(tagNome);
        }
    }
    if (tagStack.length > 0) {
        ocorrencias.push(warn(messages.XmlMensagens.invalidXmlStructure, relPath, lineOf(scan.length), messages.SeverityNiveis.error));
    }
    const xmlnsRegex = /xmlns(?::([a-zA-Z_][\w.-]*))?\s*=\s*['"][^'"]*['"]/g;
    const declaredNamespaces = new Set();
    while ((match = xmlnsRegex.exec(scan)) !== null) {
        const prefix = match[1];
        if (prefix)
            declaredNamespaces.add(prefix);
    }
    const prefixRegex = /([a-zA-Z_][\w.-]*):[a-zA-Z_][\w.-]*/g;
    while ((match = prefixRegex.exec(scan)) !== null) {
        const prefix = match[1];
        if (!declaredNamespaces.has(prefix) && prefix !== 'xml' && prefix !== 'xmlns') {
            const line = lineOf(match.index);
            ocorrencias.push(warn(messages.XmlMensagens.namespaceUndeclared(prefix), relPath, line, messages.SeverityNiveis.warning));
        }
    }
    for (const metrica of scan.matchAll(/<!DOCTYPE\b[\s\S]*?(?:\]\s*>|>)/gi)) {
        const chunk = metrica[0] ?? '';
        const hasExternalId = /\b(SYSTEM|PUBLIC)\b/i.test(chunk);
        const line = lineOf(metrica.index);
        ocorrencias.push(warn(messages.XmlMensagens.doctypeDetectado, relPath, line));
        if (hasExternalId) {
            ocorrencias.push(warn(messages.XmlMensagens.doctypeExternoDetectado, relPath, line, messages.SeverityNiveis.error));
        }
    }
    for (const metrica of scan.matchAll(/<!ENTITY\b[\s\S]*?>/gi)) {
        const chunk = metrica[0] ?? '';
        const hasExternal = /\b(SYSTEM|PUBLIC)\b/i.test(chunk);
        const isParamEntity = /<!ENTITY\s+%/i.test(chunk);
        const hasDangerousSystemId = /\bSYSTEM\b[\s\S]*?['"]\s*(file:|ftp:|gopher:|jar:|php:|data:)/i.test(chunk);
        const line = lineOf(metrica.index);
        if (isParamEntity) {
            ocorrencias.push(warn(messages.XmlMensagens.entidadeParametroDetectada, relPath, line, messages.SeverityNiveis.warning));
        }
        const entityValor = chunk.match(/<!ENTITY\s+[^'"]*\s+['"]([^'"]*)['"]/i)?.[1];
        if (entityValor && entityValor.includes('&') && entityValor.length > 100) {
            ocorrencias.push(warn(messages.XmlMensagens.largeEntityExpansion, relPath, line, messages.SeverityNiveis.error));
        }
        ocorrencias.push(warn(hasExternal ? messages.XmlMensagens.entidadeExternaDetectada : messages.XmlMensagens.entidadeDetectada, relPath, line, hasExternal || hasDangerousSystemId ? messages.SeverityNiveis.error : messages.SeverityNiveis.warning));
    }
    for (const metrica of scan.matchAll(/<\s*(?:xi|xinclude):include\b[^>]*>/gi)) {
        ocorrencias.push(warn(messages.XmlMensagens.xincludeDetectado, relPath, lineOf(metrica.index)));
    }
    for (const metrica of scan.matchAll(/=\s*['"]\s*<!\[CDATA\[[\s\S]*?\]\]>\s*['"]/gi)) {
        ocorrencias.push(warn(messages.XmlMensagens.cdataInAttribute, relPath, lineOf(metrica.index), messages.SeverityNiveis.error));
    }
    return ocorrencias;
}
export const analistaXml = criarAnalista({
    nome: 'analista-xml',
    categoria: 'markup',
    descricao: 'Heurísticas leves para XML (foco em segurança/XXE e compatibilidade).',
    global: false,
    test: (relPath) => /\.xml$/i.test(relPath),
    aplicar: async (src, relPath) => {
        if (disableEnv)
            return null;
        const msgs = collectXmlIssues(src, relPath);
        return msgs.length ? msgs : null;
    }
});
//# sourceMappingURL=analyst-xml.js.map