import { messages } from '../../../core/messages/index.js';
import { createLineLookup, maskHtmlComments, maskTagBlocks } from '../../../shared/helpers/index.js';
import { parseDocument } from 'htmlparser2';
import { criarAnalista, criarOcorrencia } from '../../../types/index.js';
const disableEnv = process.env.TUTANUS_DISABLE_PLUGIN_HTML === '1';
function warn(message, relPath, line) {
    return criarOcorrencia({ relPath, mensagem: message, linha: line, nivel: messages.SeverityNiveis.warning, origem: messages.AnalystOrigens.html, tipo: messages.AnalystTipos.html });
}
function isElement(node) { return node.type === 'tag'; }
function getAttr(el, name) {
    const attrs = el.attribs || {};
    const valor = attrs[name] ?? attrs[name.toLowerCase()] ?? '';
    return typeof valor === 'string' ? valor : '';
}
function walk(node, visit) {
    visit(node);
    const children = node.children;
    if (Array.isArray(children)) {
        for (const elem of children)
            walk(elem, visit);
    }
}
const INLINE_EVENTS = new Set(['onclick', 'onchange', 'onsubmit', 'onload', 'onerror', 'onmouseover', 'onmouseout', 'onkeyup', 'onkeydown', 'onfocus', 'onblur', 'oninput']);
function checkDoctype(node) {
    if (node.type !== 'directive')
        return null;
    const data = String(node.data || '');
    return /^!doctype\s+html\b/i.test(data);
}
function validarButton(node, line, relPath, ocorrencias) {
    const text = node.children?.some(elem => elem.type === 'text' && String(elem.data || '').trim().length > 0);
    if (!text && !getAttr(node, 'aria-label') && !getAttr(node, 'title')) {
        ocorrencias.push(warn(messages.HtmlMensagens.buttonWithoutText, relPath, line));
    }
}
function validarTable(node, line, relPath, ocorrencias) {
    const hasCaption = node.children?.some(elem => elem.type === 'tag' && String(elem.name).toLowerCase() === 'caption');
    if (!hasCaption && !getAttr(node, 'aria-label')) {
        ocorrencias.push(warn(messages.HtmlMensagens.tableWithoutCaption, relPath, line));
    }
}
function validarIframe(node, line, relPath, ocorrencias) {
    if (!getAttr(node, 'title')) {
        ocorrencias.push(warn(messages.HtmlMensagens.iframeWithoutTitle, relPath, line));
    }
}
function validarLink(node, line, relPath, ocorrencias) {
    const target = getAttr(node, 'target');
    if (target === '_blank' && !/(noopener|noreferrer)/i.test(getAttr(node, 'rel'))) {
        ocorrencias.push(warn(messages.HtmlMensagens.linkTargetBlank, relPath, line));
    }
    const attrs = node.attribs || {};
    const hasHref = Object.prototype.hasOwnProperty.call(attrs, 'href');
    const href = getAttr(node, 'href');
    if (hasHref && href !== '' && href !== '#')
        return;
    const hasOnClick = Object.keys(attrs).some(chave => chave.toLowerCase() === 'onclick');
    const isRoleButton = getAttr(node, 'role').toLowerCase() === 'button';
    if (!hasOnClick && !isRoleButton && !getAttr(node, 'tabindex')) {
        ocorrencias.push(warn(messages.HtmlMensagens.linkNoHref, relPath, line));
    }
}
function validarImg(node, line, relPath, ocorrencias, isTemplate) {
    const alt = getAttr(node, 'alt');
    const ariaLabel = !!getAttr(node, 'aria-label');
    const ariaHidden = getAttr(node, 'aria-hidden').toLowerCase() === 'true';
    const role = getAttr(node, 'role').toLowerCase();
    const decorative = ariaHidden || ariaLabel || role === 'presentation' || role === 'none';
    const attrs = node.attribs || {};
    const dataAttr = Object.keys(attrs).some(chave => /^(data-)?(decorative|icon|symbol)$/i.test(chave));
    if (!alt && !decorative && !dataAttr && !/\.svg(\?|#|$)/i.test(getAttr(node, 'src'))) {
        ocorrencias.push(warn(messages.HtmlMensagens.imgWithoutAlt, relPath, line));
    }
    if (!getAttr(node, 'loading') && !isTemplate) {
        ocorrencias.push(warn(messages.HtmlMensagens.imgWithoutLoading, relPath, line));
    }
    if (!getAttr(node, 'width') || !getAttr(node, 'height')) {
        ocorrencias.push(warn(messages.HtmlMensagens.imgWithoutDimensions, relPath, line));
    }
}
function validarForm(node, line, relPath, ocorrencias) {
    if (!getAttr(node, 'method'))
        ocorrencias.push(warn(messages.HtmlMensagens.formWithoutMethod, relPath, line));
    const attrs = node.attribs || {};
    if (!getAttr(node, 'action') && !Object.keys(attrs).some(chave => chave.toLowerCase().startsWith('data-'))) {
        ocorrencias.push(warn(messages.HtmlMensagens.formWithoutAction, relPath, line));
    }
}
function validarInput(node, line, relPath, ocorrencias) {
    const type = getAttr(node, 'type').toLowerCase();
    const isHidden = type === 'hidden';
    const isButton = type === 'button' || type === 'submit' || type === 'reset';
    if (!isHidden && !isButton && !getAttr(node, 'aria-label') && !getAttr(node, 'name')) {
        ocorrencias.push(warn(messages.HtmlMensagens.inputWithoutLabel, relPath, line));
    }
    if (!type)
        ocorrencias.push(warn(messages.HtmlMensagens.inputWithoutType, relPath, line));
    if (type === 'password' && !getAttr(node, 'autocomplete')) {
        ocorrencias.push(warn(messages.HtmlMensagens.passwordWithoutAutocomplete, relPath, line));
    }
}
function validarScript(node, line, relPath, ocorrencias, isTemplate) {
    const srcAttr = getAttr(node, 'src');
    const children = node.children || [];
    const text = children.filter((elem) => elem.type === 'text').map(elem => String(elem.data || '')).join('');
    if (!srcAttr && text.trim().length > 0) {
        ocorrencias.push(warn(messages.HtmlMensagens.inlineScript, relPath, line));
        if (text.length > 1000)
            ocorrencias.push(warn(messages.HtmlMensagens.largeInlineScript, relPath, line));
    }
    if (srcAttr && !isTemplate && !getAttr(node, 'defer') && !getAttr(node, 'async')) {
        ocorrencias.push(warn(messages.HtmlMensagens.scriptWithoutDefer, relPath, line));
    }
}
function validarStyle(node, line, relPath, ocorrencias) {
    const children = node.children || [];
    const text = children.filter((elem) => elem.type === 'text').map(elem => String(elem.data || '')).join('');
    if (text.trim().length > 0) {
        ocorrencias.push(warn(messages.HtmlMensagens.inlineStyle, relPath, line));
    }
}
const validadores = {
    button: validarButton,
    table: validarTable,
    iframe: validarIframe,
    a: validarLink,
    img: validarImg,
    form: validarForm,
    input: validarInput,
    script: validarScript,
    style: validarStyle,
};
function validateNode(node, tag, line, relPath, ocorrencias, isTemplate) {
    const validador = validadores[tag];
    if (validador)
        validador(node, line, relPath, ocorrencias, isTemplate);
    const attrs = node?.attribs || {};
    if (Object.keys(attrs).some(attr => INLINE_EVENTS.has(attr.toLowerCase()))) {
        ocorrencias.push(warn(messages.HtmlMensagens.inlineHandler, relPath, line));
    }
}
function verificarDocumento(ocorrencias, relPath, hasDoctype, htmlLangOk, hasCharset, hasViewport, hasTitle, h1Contagem, isTemplate) {
    if (isTemplate)
        return;
    if (!hasDoctype)
        ocorrencias.push(warn(messages.HtmlMensagens.doctype, relPath, 1));
    if (!htmlLangOk)
        ocorrencias.push(warn(messages.HtmlMensagens.htmlLang, relPath, 1));
    if (!hasCharset)
        ocorrencias.push(warn(messages.HtmlMensagens.metaCharset, relPath, 1));
    if (!hasViewport)
        ocorrencias.push(warn(messages.HtmlMensagens.viewport, relPath, 1));
    if (!hasTitle)
        ocorrencias.push(warn(messages.HtmlMensagens.title, relPath, 1));
    if (h1Contagem > 1)
        ocorrencias.push(warn(messages.HtmlMensagens.multipleH1, relPath, 1));
}
function collectHtmlIssuesAst(src, relPath) {
    const ocorrencias = [];
    const isTemplate = /\.(template\.html|\.component\.html)$/i.test(relPath);
    const lineOf = createLineLookup(src).lineAt;
    const doc = parseDocument(src, {
        xmlMode: false,
        withStartIndices: true,
        withEndIndices: true,
        recognizeSelfClosing: true
    });
    let hasDoctype = false;
    let hasTitle = false;
    let hasViewport = false;
    let hasCharset = false;
    let htmlLangOk = false;
    let lastHeadingNivel = 0;
    let h1Contagem = 0;
    const startLineOf = (node) => lineOf(node.startIndex ?? 0);
    walk(doc, node => {
        const doctype = checkDoctype(node);
        if (doctype !== null) {
            hasDoctype = doctype;
            return;
        }
        if (!isElement(node))
            return;
        const tag = String(node.name || '').toLowerCase();
        const line = startLineOf(node);
        if (tag === 'title' && !isTemplate)
            hasTitle = true;
        if (tag === 'html' && !isTemplate && getAttr(node, 'lang'))
            htmlLangOk = true;
        if (tag === 'meta' && !isTemplate) {
            if (getAttr(node, 'charset') || (getAttr(node, 'http-equiv').toLowerCase() === 'content-type' && /charset\s*=\s*utf-8/i.test(getAttr(node, 'content'))))
                hasCharset = true;
            if (getAttr(node, 'name').toLowerCase() === 'viewport')
                hasViewport = true;
        }
        if (/^h[1-6]$/.test(tag) && !isTemplate) {
            const level = parseInt(tag.charAt(1), 10);
            if (level === 1)
                h1Contagem++;
            if (level > lastHeadingNivel + 1 && lastHeadingNivel > 0) {
                ocorrencias.push(warn(messages.HtmlMensagens.headingSkipped(level, lastHeadingNivel + 1), relPath, line));
            }
            lastHeadingNivel = level;
        }
        validateNode(node, tag, line, relPath, ocorrencias, isTemplate);
    });
    verificarDocumento(ocorrencias, relPath, hasDoctype, htmlLangOk, hasCharset, hasViewport, hasTitle, h1Contagem, isTemplate);
    return ocorrencias;
}
function checkDocTypeRegex(scan, relPath, isTemplate, ocorrencias) {
    if (!/<!DOCTYPE\s+html>/i.test(scan) && !isTemplate) {
        ocorrencias.push(warn(messages.HtmlMensagens.doctype, relPath, 1));
    }
}
function checkHtmlLangRegex(scan, relPath, isTemplate, ocorrencias, lineOfMasked) {
    const htmlTag = scan.match(/<html[^>]*>/i);
    if (htmlTag && !isTemplate) {
        const hasLang = /\slang=['"][^'" >]+['"]/i.test(htmlTag[0]);
        if (!hasLang) {
            ocorrencias.push(warn(messages.HtmlMensagens.htmlLang, relPath, lineOfMasked(htmlTag.index ?? 0)));
        }
    }
}
function checkMetaCharsetRegex(scan, relPath, isTemplate, ocorrencias) {
    const hasCharsetAttr = /<meta\s+[^>]*\bcharset\s*=\s*['"]?[^'">\s]+/i.test(scan);
    const hasCharsetInContentTipo = /<meta\s+[^>]*http-equiv\s*=\s*['"]content-type['"][^>]*>/i.test(scan) && /charset\s*=\s*utf-8/i.test(scan);
    if (!hasCharsetAttr && !hasCharsetInContentTipo && !isTemplate) {
        ocorrencias.push(warn(messages.HtmlMensagens.metaCharset, relPath, 1));
    }
}
function checkViewportRegex(scan, relPath, isTemplate, ocorrencias) {
    if (!/<meta\s+[^>]*name=["']viewport["']/i.test(scan) && !isTemplate) {
        ocorrencias.push(warn(messages.HtmlMensagens.viewport, relPath, 1));
    }
}
function checkTitleRegex(scan, relPath, isTemplate, ocorrencias) {
    if (!/<title>[^<]*<\/title>/i.test(scan) && !isTemplate) {
        ocorrencias.push(warn(messages.HtmlMensagens.title, relPath, 1));
    }
}
function checkLinkTargetBlankRegex(scan, relPath, ocorrencias, lineOfMasked) {
    for (const metrica of scan.matchAll(/<a[^>]*target=['"]?_blank['"]?[^>]*>/gi)) {
        const hasRelSafe = /rel=['"][^'"]*(noopener|noreferrer)[^'"]*['"]/i.test(metrica[0]);
        if (!hasRelSafe) {
            ocorrencias.push(warn(messages.HtmlMensagens.linkTargetBlank, relPath, lineOfMasked(metrica.index)));
        }
    }
}
function checkImgAltRegex(scan, relPath, ocorrencias, lineOfMasked) {
    for (const metrica of scan.matchAll(/<img[^>]*>/gi)) {
        const isSvg = /\.svg/i.test(metrica[0]);
        const hasAlt = /\salt=/.test(metrica[0]);
        const ariaHidden = /\saria-hidden=['"]true['"]/i.test(metrica[0]);
        const ariaLabel = /\saria-label=/i.test(metrica[0]);
        const rolePresentation = /\srole=['"](presentation|none)['"]/i.test(metrica[0]);
        const dataAttr = /\s(?:data-)?(?:decorative|icon|symbol)=/i.test(metrica[0]);
        if (!hasAlt && !ariaHidden && !ariaLabel && !rolePresentation && !dataAttr && !isSvg) {
            ocorrencias.push(warn(messages.HtmlMensagens.imgWithoutAlt, relPath, lineOfMasked(metrica.index)));
        }
    }
}
function checkInlineHandlerRegex(scan, relPath, ocorrencias, lineOfMasked) {
    for (const metrica of scan.matchAll(/\son(?:click|change|submit|load|error|mouseover|mouseout|keyup|keydown|focus|blur|input)=/gi)) {
        ocorrencias.push(warn(messages.HtmlMensagens.inlineHandler, relPath, lineOfMasked(metrica.index)));
    }
}
function checkFormRegex(scan, relPath, ocorrencias, lineOfMasked) {
    for (const metrica of scan.matchAll(/<form[^>]*>/gi)) {
        const hasMethod = /\smethod=/i.test(metrica[0]);
        const hasAction = /\saction=/i.test(metrica[0]);
        if (!hasMethod) {
            ocorrencias.push(warn(messages.HtmlMensagens.formWithoutMethod, relPath, lineOfMasked(metrica.index)));
        }
        if (!hasAction && !/<form[^>]*data-/i.test(metrica[0])) {
            ocorrencias.push(warn(messages.HtmlMensagens.formWithoutAction, relPath, lineOfMasked(metrica.index)));
        }
    }
}
function checkInputRegex(scan, relPath, ocorrencias, lineOfMasked) {
    for (const metrica of scan.matchAll(/<input[^>]*>/gi)) {
        const hasAriaLabel = /\saria-label=/i.test(metrica[0]);
        const hasNome = /\sname=/i.test(metrica[0]);
        const isHidden = /\stype=['"]?hidden['"]?/i.test(metrica[0]);
        const isButton = /\stype=['"]?(button|submit|reset)['"]/i.test(metrica[0]);
        if (!isHidden && !isButton && !hasAriaLabel && !hasNome) {
            ocorrencias.push(warn(messages.HtmlMensagens.inputWithoutLabel, relPath, lineOfMasked(metrica.index)));
        }
        if (/\stype=['"]?password['"]?\s/.test(metrica[0]) && !/\sautocomplete=/i.test(metrica[0])) {
            ocorrencias.push(warn(messages.HtmlMensagens.passwordWithoutAutocomplete, relPath, lineOfMasked(metrica.index)));
        }
    }
}
function checkLinkNoHrefRegex(scan, relPath, ocorrencias, lineOfMasked) {
    for (const metrica of scan.matchAll(/<a[^>]*>/gi)) {
        const hasHref = /\shref=/.test(metrica[0]);
        const href = /\shref=['"]([^'"]*)['"]/i.exec(metrica[0]);
        if (!hasHref || href?.[1] === '' || href?.[1] === '#') {
            const hasOnClick = /\sonclick\s*=/.test(metrica[0]);
            const hasRoleButton = /\srole=['"]button['"]/i.test(metrica[0]);
            const hasTabIndex = /\stabindex\s*=/.test(metrica[0]);
            if (!hasOnClick && !hasRoleButton && !hasTabIndex) {
                ocorrencias.push(warn(messages.HtmlMensagens.linkNoHref, relPath, lineOfMasked(metrica.index)));
            }
        }
    }
}
function checkInlineScriptRegex(src, scanNoScriptStyle, relPath, ocorrencias, lineOfScan) {
    for (const metrica of src.matchAll(/<script[^>]*>[\s\S]*?<\/\s*script\b[^>]*\s*>/gi)) {
        if (typeof metrica.index === 'number') {
            const idx = metrica.index;
            if ((scanNoScriptStyle[idx] ?? ' ') !== '<')
                continue;
        }
        const isExternal = /\ssrc=/.test(metrica[0]);
        const innerScriptContent = metrica[0].replace(/^[\s\S]*?<script\b[^>]*>/i, '').replace(/<\/\s*script\b[^>]*\s*>[\s\S]*$/i, '');
        const isEmpty = innerScriptContent.trim().length === 0;
        if (!isExternal && !isEmpty) {
            ocorrencias.push(warn(messages.HtmlMensagens.inlineScript, relPath, lineOfScan(metrica.index)));
        }
    }
}
function checkInlineStyleRegex(src, scanNoScriptStyle, relPath, ocorrencias, lineOfScan) {
    for (const metrica of src.matchAll(/<style[^>]*>[\s\S]*?<\/\s*style\b[^>]*\s*>/gi)) {
        if (typeof metrica.index === 'number') {
            const idx = metrica.index;
            if ((scanNoScriptStyle[idx] ?? ' ') !== '<')
                continue;
        }
        const isEmpty = /<style\b[^>]*>\s*<\/\s*style[^>]*>/i.test(metrica[0]);
        if (!isEmpty) {
            ocorrencias.push(warn(messages.HtmlMensagens.inlineStyle, relPath, lineOfScan(metrica.index)));
        }
    }
}
function collectHtmlIssuesRegex(src, relPath) {
    const ocorrencias = [];
    const isTemplate = /\.(template\.html|\.component\.html)$/i.test(relPath);
    const lineOfScan = createLineLookup(src).lineAt;
    const scan = maskHtmlComments(maskTagBlocks(maskTagBlocks(src, 'script'), 'style'));
    const lineOfMasked = createLineLookup(scan).lineAt;
    const scanNoScriptStyle = maskHtmlComments(src);
    checkDocTypeRegex(scan, relPath, isTemplate, ocorrencias);
    checkHtmlLangRegex(scan, relPath, isTemplate, ocorrencias, lineOfMasked);
    checkMetaCharsetRegex(scan, relPath, isTemplate, ocorrencias);
    checkViewportRegex(scan, relPath, isTemplate, ocorrencias);
    checkTitleRegex(scan, relPath, isTemplate, ocorrencias);
    checkLinkTargetBlankRegex(scan, relPath, ocorrencias, lineOfMasked);
    checkImgAltRegex(scan, relPath, ocorrencias, lineOfMasked);
    checkInlineHandlerRegex(scan, relPath, ocorrencias, lineOfMasked);
    checkFormRegex(scan, relPath, ocorrencias, lineOfMasked);
    checkInputRegex(scan, relPath, ocorrencias, lineOfMasked);
    checkLinkNoHrefRegex(scan, relPath, ocorrencias, lineOfMasked);
    checkInlineScriptRegex(src, scanNoScriptStyle, relPath, ocorrencias, lineOfScan);
    checkInlineStyleRegex(src, scanNoScriptStyle, relPath, ocorrencias, lineOfScan);
    return ocorrencias;
}
function collectHtmlIssues(src, relPath) {
    try {
        return collectHtmlIssuesAst(src, relPath);
    }
    catch {
        return collectHtmlIssuesRegex(src, relPath);
    }
}
export const analistaHtml = criarAnalista({
    nome: 'analista-html',
    categoria: 'markup',
    descricao: 'Heurísticas leves para HTML com convenções padrão.',
    global: false,
    test: (relPath) => /\.(html|htm)$/i.test(relPath),
    aplicar: async (src, relPath) => {
        if (disableEnv)
            return null;
        const msgs = collectHtmlIssues(src, relPath);
        return msgs.length ? msgs : null;
    }
});
//# sourceMappingURL=analyst-html.js.map