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 { criarAnalista, criarOcorrencia } from '../../../types/index.js';
const disableEnv = process.env.TUTANUS_DISABLE_PLUGIN_REACT_HOOKS === '1';
function hasHooksUsage(src) {
    return /use(State|Effect|Memo|Callback|Reducer|Ref|LayoutEffect|ImperativeHandle|Transition)/.test(src);
}
function warn(message, relPath, line, nivel = messages.SeverityNiveis.warning) {
    return criarOcorrencia({
        relPath,
        mensagem: message,
        linha: line,
        nivel,
        origem: messages.AnalystOrigens.reactHooks,
        tipo: messages.AnalystTipos.reactHooks
    });
}
function extractHookCallContent(src, startIndex) {
    let depth = 0;
    let started = false;
    let content = '';
    for (let indice = startIndex; indice < src.length; indice++) {
        const char = src[indice];
        if (char === '(') {
            depth++;
            started = true;
        }
        else if (char === ')') {
            depth--;
        }
        if (started) {
            content += char;
        }
        if (started && depth === 0) {
            break;
        }
    }
    return content;
}
function collectHookIssues(src, relPath) {
    const ocorrencias = [];
    const effectMatches = [...src.matchAll(/use(Layout)?Effect\s*\(/g)];
    effectMatches.forEach(metrica => {
        const hookInicio = metrica.index ?? 0;
        const fullCall = extractHookCallContent(src, hookInicio + metrica[0].length - 1);
        const hasDepsArg = /,\s*(\[[\s\S]*?\]|\w+)\s*\)$/.test(fullCall);
        const skip = /no-deps-ok|eslint-disable-next-line\s+react-hooks\/exhaustive-deps/i.test(fullCall);
        if (!hasDepsArg && !skip) {
            const line = src.slice(0, hookInicio).split(/\n/).length;
            ocorrencias.push(warn(messages.ReactHooksMensagens.useEffectNoDeps, relPath, line));
        }
    });
    const memoMatches = [...src.matchAll(/use(Memo|Callback)\s*\(/g)];
    memoMatches.forEach(metrica => {
        const hookInicio = metrica.index ?? 0;
        const fullCall = extractHookCallContent(src, hookInicio + metrica[0].length - 1);
        const hasDepsArg = /,\s*(\[[\s\S]*?\]|\w+)\s*\)$/.test(fullCall);
        const skip = /no-deps-ok|eslint-disable-next-line\s+react-hooks\/exhaustive-deps/i.test(fullCall);
        if (!hasDepsArg && !skip) {
            const line = src.slice(0, hookInicio).split(/\n/).length;
            ocorrencias.push(warn(messages.ReactHooksMensagens.memoCallbackNoDeps, relPath, line));
        }
    });
    const conditionalHooks = [...src.matchAll(/(if|for|while)\s*\([^)]*\)\s*\{[\s\S]{0,160}?use[A-Z][A-Za-z0-9_]*/g)];
    conditionalHooks.forEach(metrica => {
        const line = src.slice(0, metrica.index || 0).split(/\n/).length;
        ocorrencias.push(warn(messages.ReactHooksMensagens.hookInConditional, relPath, line));
    });
    return ocorrencias;
}
const traverseFn = traverse;
function parseHooksWithBabel(src, relPath) {
    const lower = relPath.toLowerCase();
    const isTs = lower.endsWith('.ts') || lower.endsWith('.tsx');
    const isJsxLike = lower.endsWith('.tsx') || lower.endsWith('.jsx');
    try {
        const plugins = ['decorators-legacy', 'classProperties', 'classPrivateProperties', 'classPrivateMethods', 'optionalChaining', 'nullishCoalescingOperator', 'topLevelAwait', 'importAttributes', 'importAssertions'];
        if (isTs)
            plugins.unshift('typescript');
        if (isJsxLike)
            plugins.unshift('jsx');
        const ast = babelParse(src, {
            sourceType: 'unambiguous',
            errorRecovery: true,
            plugins
        });
        const out = [];
        const seen = new Set();
        const pushOnce = (metrica) => {
            const chave = `${metrica.mensagem}|${metrica.relPath}|${metrica.linha ?? 0}`;
            if (seen.has(chave))
                return;
            seen.add(chave);
            out.push(metrica);
        };
        const isHookCallee = (callee) => {
            if (textoTitulo.isIdentifier(callee))
                return {
                    name: String(callee.name)
                };
            if (textoTitulo.isMemberExpression(callee)) {
                const obj = callee.object;
                const prop = callee.property;
                const objNome = textoTitulo.isIdentifier(obj) ? String(obj.name) : '';
                const propNome = textoTitulo.isIdentifier(prop) ? String(prop.name) : '';
                if (objNome === 'React' && propNome)
                    return {
                        name: propNome
                    };
            }
            return null;
        };
        const isEffectLike = (name) => name === 'useEffect' || name === 'useLayoutEffect';
        const isMemoLike = (name) => name === 'useMemo' || name === 'useCallback';
        const isAnyHook = (name) => /^use[A-Z0-9_]/.test(name);
        const inConditionalOrLoop = (path) => {
            return Boolean(path.findParent(caminho => caminho.isIfStatement() || caminho.isForStatement() || caminho.isWhileStatement() || caminho.isDoWhileStatement() || caminho.isSwitchStatement()));
        };
        const visitor = {
            CallExpression(path) {
                try {
                    const calleeInfo = isHookCallee(path.node.callee);
                    if (!calleeInfo)
                        return;
                    const name = calleeInfo.name;
                    const locLine = path.node.loc?.start?.line;
                    if (isEffectLike(name) && path.node.arguments.length < 2) {
                        pushOnce(warn(messages.ReactHooksMensagens.useEffectNoDeps, relPath, locLine));
                    }
                    if (isMemoLike(name) && path.node.arguments.length < 2) {
                        pushOnce(warn(messages.ReactHooksMensagens.memoCallbackNoDeps, relPath, locLine));
                    }
                    if (isAnyHook(name) && inConditionalOrLoop(path)) {
                        pushOnce(warn(messages.ReactHooksMensagens.hookInConditional, relPath, locLine));
                    }
                }
                catch {
                }
            }
        };
        traverseFn(ast, visitor);
        return out;
    }
    catch {
        return null;
    }
}
export const analistaReactHooks = criarAnalista({
    nome: 'analista-react-hooks',
    categoria: 'framework',
    descricao: 'Heurísticas leves de Hooks (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-hooks.ts'))
            return null;
        if (!hasHooksUsage(src))
            return null;
        const astMsgs = parseHooksWithBabel(src, relPath);
        if (astMsgs !== null)
            return astMsgs.length ? astMsgs : null;
        const msgs = collectHookIssues(src, relPath);
        return msgs.length ? msgs : null;
    }
});
//# sourceMappingURL=analyst-react-hooks.js.map