import { parse as babelParse } from '@babel/parser';
import traverse from '@babel/traverse';
import * as textoTitulo from '@babel/types';
import { messages } from '../../../core/messages/index.js';
import { createLineLookup, maskJsComments } from '../../../shared/helpers/index.js';
import { criarAnalista, criarOcorrencia } from '../../../types/index.js';
const disableEnv = process.env.TUTANUS_DISABLE_PLUGIN_REACT === '1';
function hasJSX(src) {
    const hasTagLike = /(^|[^A-Za-z0-9_$])<([A-Za-z][A-Za-z0-9]*)\b/.test(src);
    const hasFragment = /<>|<\/>|<\/\s*>|<\/\s*Fragment\s*>/i.test(src);
    return hasTagLike || hasFragment || /React\.createElement/.test(src);
}
const traverseFn = traverse;
function warn(message, relPath, line, nivel = messages.SeverityNiveis.warning) {
    return criarOcorrencia({
        relPath,
        mensagem: message,
        linha: line,
        nivel,
        origem: messages.AnalystOrigens.react,
        tipo: messages.AnalystTipos.react
    });
}
function collectReactIssues(src, relPath) {
    const ocorrencias = [];
    const lineOf = createLineLookup(src).lineAt;
    for (const match of src.matchAll(/<a[^>]*target=['"]?_blank['"]?[^>]*>/gi)) {
        const hasRelSafe = /rel=['"][^'"]*(noopener|noreferrer)[^'"]*['"]/i.test(match[0]);
        if (!hasRelSafe) {
            const line = lineOf(match.index);
            ocorrencias.push(warn(messages.ReactMensagens.linkTargetBlank, relPath, line));
        }
    }
    for (const match of src.matchAll(/dangerouslySetInnerHTML/gi)) {
        const line = lineOf(match.index);
        ocorrencias.push(warn(messages.ReactMensagens.dangerouslySetInnerHTML, relPath, line));
    }
    for (const match of src.matchAll(/<img[^>]*>/gi)) {
        const hasAlt = /\salt=/.test(match[0]);
        const ariaHidden = /\saria-hidden=['"]true['"]/i.test(match[0]);
        const rolePresentation = /\srole=['"](presentation|none)['"]/i.test(match[0]);
        if (!hasAlt && !ariaHidden && !rolePresentation) {
            const line = lineOf(match.index);
            ocorrencias.push(warn(messages.ReactMensagens.imgWithoutAlt, relPath, line));
        }
    }
    for (const match of src.matchAll(/\b(fetch|axios\.get|axios\.post|axios\.[a-z]+)\s*\(\s*['"]http:\/\//gi)) {
        const line = lineOf(match.index);
        ocorrencias.push(warn(messages.ReactMensagens.httpFetch, relPath, line));
    }
    for (const match of src.matchAll(/(api[key]?|token|secret|senha|password|passphrase)\s*[:=]\s*['"]([A-Za-z0-9\/_+=.-]{16,})['"]/gi)) {
        const valor = match[2] || '';
        if (valor.length < 24 || /^https?:\/\//i.test(valor))
            continue;
        const line = lineOf(match.index);
        ocorrencias.push(warn(messages.ReactMensagens.hardcodedCredential, relPath, line));
    }
    const usesNextRouter = /useRouter|router\.(push|replace)/.test(src);
    for (const match of src.matchAll(/location\.href\s*=\s*([^;]+)/gi)) {
        const assignedValor = match[1] || '';
        const line = lineOf(match.index);
        const isStaticString = /^['"`][^'"\`]*['"`]$/.test(assignedValor.trim());
        const isInternalNavigation = /^['"`]\//.test(assignedValor.trim());
        if (!isStaticString && !isInternalNavigation && !usesNextRouter) {
            ocorrencias.push(warn(messages.ReactMensagens.locationHrefRedirect, relPath, line));
        }
    }
    return ocorrencias;
}
function normalizeStringValue(valor) {
    return typeof valor === 'string' ? valor : '';
}
function isBabelNode(valor) {
    return typeof valor === 'object' && valor !== null && 'type' in valor;
}
function attrsToFlatList(attrs) {
    if (!Array.isArray(attrs))
        return [];
    return attrs.filter(isBabelNode).filter((arquivo) => textoTitulo.isJSXAttribute(arquivo)).filter(arquivo => textoTitulo.isJSXIdentifier(arquivo.name)).map(arquivo => {
        const name = String(arquivo.name.name);
        let value;
        if (!arquivo.value)
            value = null;
        else if (textoTitulo.isStringLiteral(arquivo.value))
            value = arquivo.value.value;
        else if (textoTitulo.isJSXExpressionContainer(arquivo.value)) {
            const expr = arquivo.value.expression;
            if (textoTitulo.isStringLiteral(expr))
                value = expr.value;
        }
        return {
            name,
            value
        };
    });
}
function findAttr(attrs, attrName) {
    const normalizedAttrName = attrName.toLowerCase();
    return attrs?.find(arquivo => arquivo.name.toLowerCase() === normalizedAttrName) ?? null;
}
function isSensitiveKeyName(key) {
    return /(api[key]?|token|secret|senha|password|passphrase)/i.test(key);
}
function parseReactWithBabel(scan, relPath) {
    const lower = relPath.toLowerCase();
    const isTs = lower.endsWith('.ts') || lower.endsWith('.tsx');
    const isJsxLike = lower.endsWith('.tsx') || lower.endsWith('.jsx');
    const isJsFamily = /\.(js|mjs|cjs|jsx)$/i.test(lower);
    if (lower.endsWith('.ts') && !isJsxLike)
        return null;
    try {
        const plugins = ['decorators-legacy', 'classProperties', 'classPrivateProperties', 'classPrivateMethods', 'optionalChaining', 'nullishCoalescingOperator', 'topLevelAwait', 'importAttributes', 'importAssertions'];
        if (isTs)
            plugins.unshift('typescript');
        if (isJsxLike || isJsFamily)
            plugins.unshift('jsx');
        const ast = babelParse(scan, {
            sourceType: 'unambiguous',
            errorRecovery: true,
            plugins
        });
        const ocorrencias = [];
        const seen = new Set();
        const pushOnce = (metrica) => {
            const chave = `${metrica.mensagem}|${metrica.relPath}|${metrica.linha ?? 0}`;
            if (seen.has(chave))
                return;
            seen.add(chave);
            ocorrencias.push(metrica);
        };
        const visitor = {
            JSXOpeningElement(path) {
                try {
                    const node = path.node;
                    const locLine = node.loc?.start?.line;
                    const nameNode = node.name;
                    const tag = textoTitulo.isJSXIdentifier(nameNode) ? String(nameNode.name) : '';
                    if (!tag)
                        return;
                    const attrs = attrsToFlatList(node.attributes);
                    if (tag.toLowerCase() === 'a') {
                        const target = findAttr(attrs, 'target');
                        const targetVal = normalizeStringValue(target?.value);
                        if (target && targetVal === '_blank') {
                            const rel = findAttr(attrs, 'rel');
                            const relVal = normalizeStringValue(rel?.value);
                            const safe = /(noopener|noreferrer)/i.test(relVal);
                            if (!safe)
                                pushOnce(warn(messages.ReactMensagens.linkTargetBlank, relPath, locLine));
                        }
                    }
                    if (tag.toLowerCase() === 'img') {
                        const alt = findAttr(attrs, 'alt');
                        const ariaHidden = findAttr(attrs, 'aria-hidden');
                        const role = findAttr(attrs, 'role');
                        const ariaHiddenVal = normalizeStringValue(ariaHidden?.value);
                        const roleVal = normalizeStringValue(role?.value);
                        const decorative = ariaHiddenVal === 'true' || /^(presentation|none)$/i.test(roleVal);
                        if (!alt && !decorative) {
                            pushOnce(warn(messages.ReactMensagens.imgWithoutAlt, relPath, locLine));
                        }
                    }
                    if (findAttr(attrs, 'dangerouslySetInnerHTML')) {
                        pushOnce(warn(messages.ReactMensagens.dangerouslySetInnerHTML, relPath, locLine));
                    }
                    const styleAttr = findAttr(attrs, 'style');
                    if (styleAttr && styleAttr.value && styleAttr.value.includes('{')) {
                        const numProps = (styleAttr.value.match(/:/g) || []).length;
                        if (numProps > 2) {
                            pushOnce(warn(messages.ReactMensagens.complexInlineStyles, relPath, locLine, messages.SeverityNiveis.warning));
                        }
                    }
                    for (const arquivo of node.attributes) {
                        try {
                            if (!textoTitulo.isJSXAttribute(arquivo) || !textoTitulo.isJSXIdentifier(arquivo.name))
                                continue;
                            const attrNome = String(arquivo.name.name);
                            if (arquivo.value && textoTitulo.isJSXExpressionContainer(arquivo.value)) {
                                const expr = arquivo.value.expression;
                                if (textoTitulo.isArrowFunctionExpression(expr) || textoTitulo.isFunctionExpression(expr)) {
                                    pushOnce(warn(messages.ReactMensagens.inlineHandlerJsx, relPath, locLine));
                                }
                                if (attrNome === 'key' && textoTitulo.isJSXExpressionContainer(arquivo.value) && textoTitulo.isIdentifier(arquivo.value.expression)) {
                                    const id = arquivo.value.expression.name;
                                    if (['i', 'index', 'idx'].includes(id)) {
                                        pushOnce(warn(messages.ReactMensagens.indexAsKey, relPath, locLine));
                                    }
                                }
                            }
                        }
                        catch {
                        }
                    }
                }
                catch {
                }
            },
            ClassMethod(path) {
                try {
                    const key = path.node.key;
                    if (textoTitulo.isIdentifier(key)) {
                        const name = key.name;
                        const deprecated = ['componentWillMount', 'componentWillReceiveProps', 'componentWillUpdate'];
                        if (deprecated.includes(name)) {
                            pushOnce(warn(messages.ReactMensagens.deprecatedLifecycleMethod(name), relPath, path.node.loc?.start?.line, messages.SeverityNiveis.warning));
                        }
                    }
                }
                catch {
                }
            },
            CallExpression(path) {
                try {
                    const node = path.node;
                    const locLine = node.loc?.start?.line;
                    const args = node.arguments;
                    const first = args[0];
                    const firstStr = textoTitulo.isStringLiteral(first) ? String(first.value) : '';
                    if (textoTitulo.isIdentifier(node.callee) && node.callee.name === 'fetch') {
                        if (/^http:\/\//i.test(firstStr))
                            pushOnce(warn(messages.ReactMensagens.httpFetch, relPath, locLine));
                        return;
                    }
                    if (textoTitulo.isMemberExpression(node.callee)) {
                        const obj = node.callee.object;
                        const prop = node.callee.property;
                        const objNome = textoTitulo.isIdentifier(obj) ? String(obj.name) : '';
                        const propNome = textoTitulo.isIdentifier(prop) ? String(prop.name) : '';
                        if (objNome === 'axios' && propNome) {
                            if (/^http:\/\//i.test(firstStr))
                                pushOnce(warn(messages.ReactMensagens.httpFetch, relPath, locLine));
                        }
                        if (propNome === 'map' && args.length >= 1) {
                            const cb = args[0];
                            const checkCallback = (fn) => {
                                try {
                                    const body = fn.body;
                                    const checkJsxNode = (jsx) => {
                                        if (textoTitulo.isJSXElement(jsx)) {
                                            const hasChave = jsx.openingElement.attributes.some(arquivo => textoTitulo.isJSXAttribute(arquivo) && textoTitulo.isJSXIdentifier(arquivo.name) && String(arquivo.name.name) === 'key');
                                            if (!hasChave) {
                                                pushOnce(warn(messages.ReactMensagens.listItemNoKey, relPath, jsx.loc?.start?.line ?? locLine));
                                            }
                                        }
                                        else {
                                            pushOnce(warn(messages.ReactMensagens.listItemNoKey, relPath, jsx.loc?.start?.line ?? locLine));
                                        }
                                    };
                                    if (textoTitulo.isJSXElement(body) || textoTitulo.isJSXFragment(body)) {
                                        checkJsxNode(body);
                                    }
                                    else if (textoTitulo.isBlockStatement(body)) {
                                        for (const stmt of body.body) {
                                            if (textoTitulo.isReturnStatement(stmt) && stmt.argument) {
                                                if (textoTitulo.isJSXElement(stmt.argument) || textoTitulo.isJSXFragment(stmt.argument)) {
                                                    checkJsxNode(stmt.argument);
                                                }
                                            }
                                        }
                                    }
                                }
                                catch {
                                }
                            };
                            if (textoTitulo.isFunctionExpression(cb))
                                checkCallback(cb);
                            if (textoTitulo.isArrowFunctionExpression(cb))
                                checkCallback(cb);
                        }
                    }
                }
                catch {
                }
            },
            AssignmentExpression(path) {
                try {
                    const node = path.node;
                    const locLine = node.loc?.start?.line;
                    const left = node.left;
                    if (!textoTitulo.isMemberExpression(left))
                        return;
                    const obj = left.object;
                    const prop = left.property;
                    const objNome = textoTitulo.isIdentifier(obj) ? String(obj.name) : '';
                    const propNome = textoTitulo.isIdentifier(prop) ? String(prop.name) : '';
                    if (objNome === 'location' && propNome === 'href') {
                        const right = node.right;
                        let isSafe = false;
                        if (textoTitulo.isStringLiteral(right)) {
                            const val = right.value;
                            isSafe = val.startsWith('/') || val.startsWith('./') || val.startsWith('../');
                        }
                        if (!isSafe) {
                            pushOnce(warn(messages.ReactMensagens.locationHrefRedirect, relPath, locLine));
                        }
                    }
                }
                catch {
                }
            },
            ObjectProperty(path) {
                try {
                    const node = path.node;
                    const locLine = node.loc?.start?.line;
                    const key = node.key;
                    const value = node.value;
                    const keyNome = textoTitulo.isIdentifier(key) ? String(key.name) : textoTitulo.isStringLiteral(key) ? String(key.value) : '';
                    if (!keyNome || !isSensitiveKeyName(keyNome))
                        return;
                    if (!textoTitulo.isStringLiteral(value))
                        return;
                    const string = String(value.value || '');
                    if (string.length < 24)
                        return;
                    if (/^https?:\/\//i.test(string))
                        return;
                    pushOnce(warn(messages.ReactMensagens.hardcodedCredential, relPath, locLine));
                }
                catch {
                }
            }
        };
        traverseFn(ast, visitor);
        return ocorrencias.length ? ocorrencias : null;
    }
    catch {
        return null;
    }
}
export const analistaReact = criarAnalista({
    nome: 'analista-react',
    categoria: 'framework',
    descricao: 'Heurísticas leves de React (sem ESLint).',
    global: false,
    test: (relPath) => /\.(jsx|tsx|js|ts)$/i.test(relPath),
    aplicar: async (src, relPath) => {
        if (disableEnv)
            return null;
        if (relPath.includes('src/analysts/react/analysts/analyst-react.ts'))
            return null;
        const scan = maskJsComments(src);
        if (/\.ts$/i.test(relPath) && !/\.tsx$/i.test(relPath)) {
            const hasStrongJsxEvidence = /<\/[A-Za-z][^>]*>/.test(scan) || /\/>/.test(scan) || /React\.createElement/.test(scan);
            if (!hasStrongJsxEvidence)
                return null;
        }
        if (!hasJSX(scan))
            return null;
        const astMsgs = parseReactWithBabel(scan, relPath) ?? [];
        const heuristicMsgs = collectReactIssues(scan, relPath);
        if (!astMsgs.length && !heuristicMsgs.length)
            return null;
        const seen = new Set();
        const merged = [];
        const pushOnce = (metrica) => {
            const chave = `${metrica.mensagem}|${metrica.relPath}|${metrica.linha ?? 0}`;
            if (seen.has(chave))
                return;
            seen.add(chave);
            merged.push(metrica);
        };
        astMsgs.forEach(pushOnce);
        heuristicMsgs.forEach(pushOnce);
        return merged.length ? merged : null;
    }
});
//# sourceMappingURL=analyst-react.js.map