import * as textoTitulo from '@babel/types';
import { traverse } from '../../../core/config/index.js';
import { messages } from '../../../core/messages/index.js';
import { detectarContextoProjeto, isRelevanteParaAnalise } from '../../../shared/index.js';
import { isBabelNode } from '../../../types/index.js';
export function extractHandlerInfo(nodeParam) {
    if (!nodeParam || !isBabelNode(nodeParam))
        return null;
    const node = nodeParam;
    const build = (fn, bodyBlock, isAnonymous) => {
        const rawParams = Array.isArray(fn.params) ? fn.params : [];
        const params = rawParams.filter((p) => textoTitulo.isIdentifier(p));
        return { func: fn, bodyBlock, isAnonymous, params, totalParams: (fn.params ?? []).length };
    };
    if (textoTitulo.isFunctionDeclaration(node) && node.body && textoTitulo.isBlockStatement(node.body)) {
        return build(node, node.body, !node.id);
    }
    if ((textoTitulo.isFunctionExpression(node) || textoTitulo.isArrowFunctionExpression(node)) && node.body && textoTitulo.isBlockStatement(node.body)) {
        return build(node, node.body, textoTitulo.isFunctionExpression(node) ? !node.id : true);
    }
    return null;
}
function getNumberProp(obj, prop) {
    if (typeof obj !== 'object' || obj === null)
        return undefined;
    const dict = obj;
    const val = dict[prop];
    return typeof val === 'number' ? val : undefined;
}
function extractLineFromNode(obj) {
    if (typeof obj !== 'object' || obj === null)
        return undefined;
    const dict = obj;
    const loc = dict.loc;
    if (typeof loc !== 'object' || loc === null)
        return undefined;
    const start = loc.start;
    if (typeof start !== 'object' || start === null)
        return undefined;
    const line = start.line;
    return typeof line === 'number' ? line : undefined;
}
function detectarNewCommand(node, state) {
    if (!textoTitulo.isNewExpression(node))
        return;
    const newExpr = node;
    if (!textoTitulo.isIdentifier(newExpr.callee) || newExpr.callee.name !== 'Command')
        return;
    state.hasFrameworkSignal = true;
    let nome = '';
    const arg0 = newExpr.arguments && newExpr.arguments[0];
    if (arg0 && textoTitulo.isStringLiteral(arg0))
        nome = arg0.value;
    if (state.comandosInvocados === 0) {
        state.comandosInvocados++;
        if (nome)
            state.comandoNomes.push(nome);
    }
}
function detectarDiscordJs(node, state) {
    if (!textoTitulo.isCallExpression(node) && !textoTitulo.isMemberExpression(node))
        return;
    if (textoTitulo.isCallExpression(node)) {
        const call = node;
        if (!textoTitulo.isMemberExpression(call.callee))
            return;
        const prop = call.callee.property;
        if (!textoTitulo.isIdentifier(prop))
            return;
        const chamaMetodos = ['addSlashCommand', 'setName', 'setDescription', 'addStringOption', 'addUserOption', 'addChannelOption'];
        if (!chamaMetodos.includes(prop.name))
            return;
        state.hasFrameworkSignal = true;
        if (state.comandosInvocados === 0) {
            state.comandosInvocados++;
            const arg0 = call.arguments && call.arguments[0];
            const nome = prop.name === 'setName' && arg0 && textoTitulo.isStringLiteral(arg0) ? arg0.value : 'slash-command';
            state.comandoNomes.push(nome);
        }
        return;
    }
    if (textoTitulo.isMemberExpression(node)) {
        const member = node;
        const prop = member.property;
        if (!textoTitulo.isIdentifier(prop))
            return;
        const membros = ['commandName', 'options', 'isCommand', 'reply', 'editReply', 'deferReply', 'isChatInputCommand', 'isAutocomplete'];
        if (!membros.includes(prop.name))
            return;
        state.hasFrameworkSignal = true;
        if (state.comandosInvocados === 0)
            state.comandosInvocados++;
    }
}
const METODOS_CADEIA_COMMANDER = new Set(['option', 'description', 'alias', 'argument', 'usage', 'help']);
function detectarCommanderChainMethods(node, state) {
    if (!textoTitulo.isCallExpression(node))
        return;
    const callee = node.callee;
    if (!textoTitulo.isMemberExpression(callee))
        return;
    const prop = callee.property;
    if (!textoTitulo.isIdentifier(prop))
        return;
    if (METODOS_CADEIA_COMMANDER.has(prop.name)) {
        state.hasFrameworkSignal = true;
    }
}
function detectarGenericCommand(node, state, registrar) {
    if (!textoTitulo.isCallExpression(node))
        return;
    const call = node;
    const callee = call.callee;
    if (!textoTitulo.isIdentifier(callee))
        return;
    if (!['onCommand', 'registerCommand'].includes(callee.name))
        return;
    let comandoNome = '';
    const primeiroArg = call.arguments && call.arguments[0];
    if (primeiroArg && textoTitulo.isStringLiteral(primeiroArg))
        comandoNome = primeiroArg.value;
    const handler = call.arguments && call.arguments.length > 1 ? call.arguments[1] : undefined;
    registrar(call, comandoNome, handler, 'generico');
}
function detectarCommanderAction(node, state, registrar) {
    if (!textoTitulo.isCallExpression(node))
        return;
    const call = node;
    const callee = call.callee;
    if (!textoTitulo.isMemberExpression(callee))
        return;
    const prop = callee.property;
    if (!textoTitulo.isIdentifier(prop) || prop.name !== 'action')
        return;
    const handler = call.arguments && call.arguments[0];
    const nome = extrairNomeDeCadeia(call);
    registrar(call, nome, handler, 'commander');
}
function detectarCommanderCommand(node, state, registrar) {
    if (!textoTitulo.isCallExpression(node))
        return;
    const call = node;
    const callee = call.callee;
    if (!textoTitulo.isMemberExpression(callee))
        return;
    const prop = callee.property;
    if (!textoTitulo.isIdentifier(prop) || prop.name !== 'command')
        return;
    const args = call.arguments || [];
    const nameArg = args[0];
    let nome = '';
    if (nameArg && textoTitulo.isStringLiteral(nameArg))
        nome = nameArg.value;
    let handler;
    for (let i = args.length - 1; i >= 0; i--) {
        const arg = args[i];
        if (textoTitulo.isFunctionExpression(arg) || textoTitulo.isArrowFunctionExpression(arg)) {
            handler = arg;
            break;
        }
    }
    registrar(call, nome, handler, 'multi');
}
function detectarYargs(node, state, registrar) {
    if (!textoTitulo.isCallExpression(node))
        return;
    const call = node;
    const callee = call.callee;
    if (!textoTitulo.isMemberExpression(callee))
        return;
    const obj = callee.object;
    const prop = callee.property;
    if (!textoTitulo.isIdentifier(prop) || prop.name !== 'command')
        return;
    if (!textoTitulo.isIdentifier(obj) || !/yargs?/i.test(obj.name))
        return;
    const args = call.arguments || [];
    let nome = '';
    if (args[0] && textoTitulo.isStringLiteral(args[0]))
        nome = args[0].value;
    const handlerCandidates = args.filter((arg) => textoTitulo.isFunctionExpression(arg) || textoTitulo.isArrowFunctionExpression(arg));
    const handler = handlerCandidates.at(-1);
    registrar(call, nome, handler, 'yargs');
}
function detectarOclif(node, state, registrar) {
    if (!textoTitulo.isClassDeclaration(node))
        return;
    const cls = node;
    if (!cls.superClass || !textoTitulo.isIdentifier(cls.superClass) || cls.superClass.name !== 'Command')
        return;
    const classNome = cls.id?.name || '';
    const bodyArray = cls.body.body;
    const isRunMethod = (method) => {
        if (!textoTitulo.isClassMethod(method))
            return false;
        const k = method.key;
        return textoTitulo.isIdentifier(k) && k.name === 'run';
    };
    const bodyElem = Array.isArray(bodyArray) ? bodyArray?.find(isRunMethod) : undefined;
    if (bodyElem && bodyElem.body && textoTitulo.isBlockStatement(bodyElem.body)) {
        registrar(textoTitulo.callExpression(textoTitulo.identifier('oclifRun'), []), classNome, bodyElem, 'oclif');
    }
}
function detectarImportCommander(node, state) {
    if (!textoTitulo.isImportDeclaration(node))
        return;
    const imp = node;
    if (typeof imp.source.value !== 'string' || !/commander/.test(imp.source.value))
        return;
    for (const spec of imp.specifiers) {
        if (textoTitulo.isImportSpecifier(spec) && textoTitulo.isIdentifier(spec.imported) && spec.imported.name === 'Command') {
            if (textoTitulo.isIdentifier(spec.local))
                state.commandClassNomes.add(spec.local.name);
        }
    }
}
function detectarVarUsage(node, state) {
    if (!textoTitulo.isFunctionDeclaration(node) && !textoTitulo.isFunctionExpression(node) && !textoTitulo.isArrowFunctionExpression(node))
        return;
    const fnBody = node.body;
    if (!fnBody || !textoTitulo.isBlockStatement(fnBody))
        return;
    const createdVars = new Set();
    for (const stmt of fnBody.body || []) {
        if (textoTitulo.isVariableDeclaration(stmt)) {
            for (const decl of stmt.declarations) {
                if (decl.id && textoTitulo.isIdentifier(decl.id) && decl.init && textoTitulo.isNewExpression(decl.init) && textoTitulo.isIdentifier(decl.init.callee) && state.commandClassNomes.has(decl.init.callee.name)) {
                    createdVars.add(decl.id.name);
                }
            }
        }
        else if (textoTitulo.isExpressionStatement(stmt) && textoTitulo.isCallExpression(stmt.expression) && textoTitulo.isMemberExpression(stmt.expression.callee)) {
            const obj = stmt.expression.callee.object;
            if (textoTitulo.isIdentifier(obj) && createdVars.has(obj.name)) {
                state.hasFrameworkSignal = true;
            }
        }
        else if (textoTitulo.isReturnStatement(stmt) && stmt.argument) {
            if (textoTitulo.isIdentifier(stmt.argument) && createdVars.has(stmt.argument.name)) {
                state.hasFrameworkSignal = true;
                if (state.comandosInvocados === 0)
                    state.comandosInvocados++;
            }
        }
    }
}
function criarRegistrar(state) {
    return (node, comandoNome, handler, origemFramework) => {
        state.comandosInvocados++;
        if (comandoNome)
            state.comandoNomes.push(comandoNome);
        const userInfo = extractHandlerInfo(handler);
        const registro = {
            comandoNome,
            id: undefined,
            timestamp: new Date().toISOString(),
            origemFramework
        };
        registro.handler = userInfo ? {
            ...userInfo
        } : undefined;
        registro.node = node;
        state.comandos.push(registro);
    };
}
function extrairNomeDeCadeia(call) {
    let current = call;
    const visitados = new Set();
    while (current && !visitados.has(current)) {
        visitados.add(current);
        if (textoTitulo.isCallExpression(current) && textoTitulo.isMemberExpression(current.callee)) {
            const callee = current.callee;
            const prop = callee.property;
            if (textoTitulo.isIdentifier(prop) && prop.name === 'command') {
                const arg0 = current.arguments && current.arguments[0];
                if (arg0 && textoTitulo.isStringLiteral(arg0))
                    return arg0.value;
            }
            const owner = callee.object;
            if (textoTitulo.isCallExpression(owner)) {
                current = owner;
                continue;
            }
            else if (textoTitulo.isMemberExpression(owner)) {
                current = owner.object;
                continue;
            }
            else {
                break;
            }
        }
        else if (textoTitulo.isMemberExpression(current)) {
            current = current.object;
            continue;
        }
        break;
    }
    return '';
}
function verificarComandosDuplicados(comandoNomes, relPath) {
    const duplicados = comandoNomes.filter((item, idx) => item && comandoNomes.indexOf(item) !== idx);
    if (duplicados.length === 0)
        return null;
    return {
        tipo: 'padrao-problematico',
        nivel: 'aviso',
        mensagem: messages.ComandosCliMensagens.comandosDuplicados(duplicados),
        relPath,
        origem: 'analista-comandos-cli'
    };
}
function extrairLinha(handlerInfo, node) {
    const fromHandler = extractLineFromNode(handlerInfo);
    if (typeof fromHandler === 'number')
        return fromHandler;
    const fromNode = extractLineFromNode(node);
    return typeof fromNode === 'number' ? fromNode : 1;
}
function verificarHandlerAnonimo(handlerInfo, comandoNome, linha, relPath) {
    if (!handlerInfo.isAnonymous || !comandoNome)
        return null;
    return { tipo: 'padrao-problematico', nivel: 'aviso', mensagem: messages.ComandosCliMensagens.handlerAnonimo(comandoNome), relPath, linha, origem: 'analista-comandos-cli' };
}
function verificarMuitosParams(handlerInfo, comandoNome, linha, relPath) {
    const contagem = handlerInfo.totalParams ?? (Array.isArray(handlerInfo.params) ? handlerInfo.params.length : 0);
    if (contagem <= 3 || !comandoNome)
        return null;
    return { tipo: 'padrao-problematico', nivel: 'aviso', mensagem: messages.ComandosCliMensagens.handlerMuitosParametros(comandoNome, contagem), relPath, linha, origem: 'analista-comandos-cli' };
}
function verificarHandlerLongo(statements, comandoNome, linha, relPath) {
    if (statements.length <= 30 || !comandoNome)
        return null;
    return { tipo: 'padrao-problematico', nivel: 'aviso', mensagem: messages.ComandosCliMensagens.handlerMuitoLongo(comandoNome, statements.length), relPath, linha, origem: 'analista-comandos-cli' };
}
function verificarTryCatch(statements, comandoNome, linha, relPath) {
    if (statements.length === 0 || !comandoNome)
        return null;
    if (statements.some((stmt) => textoTitulo.isTryStatement(stmt)))
        return null;
    return { tipo: 'boa-pratica-ausente', nivel: 'aviso', mensagem: messages.ComandosCliMensagens.handlerSemTryCatch(comandoNome), relPath, linha, origem: 'analista-comandos-cli' };
}
function verificarFeedback(handlerInfo, statements, comandoNome, linha, relPath, conteudo) {
    if (statements.length === 0 || !comandoNome)
        return null;
    const bodyInicio = getNumberProp(handlerInfo.bodyBlock, 'start');
    const bodyFim = getNumberProp(handlerInfo.bodyBlock, 'end');
    const bodySlice = typeof bodyInicio === 'number' && typeof bodyFim === 'number' ? conteudo.substring(bodyInicio, bodyFim) : '';
    const bodySrc = (bodySlice && bodySlice.length > 5 ? bodySlice : conteudo);
    const regexLog = /console\.(log|warn|error)|logger\.|ctx\.(reply|send|res|response)/;
    if (regexLog.test(bodySrc) || regexLog.test(conteudo))
        return null;
    return { tipo: 'boa-pratica-ausente', nivel: 'aviso', mensagem: messages.ComandosCliMensagens.handlerSemFeedback(comandoNome), relPath, linha, origem: 'analista-comandos-cli' };
}
function processarComandosRegistrados(comandos, comandoNomes, comandosInvocados, hasFrameworkSignal, relPath, conteudo, contextoProjeto) {
    const ocorrencias = [];
    if (comandosInvocados === 0 && !hasFrameworkSignal && (contextoProjeto.isBot || contextoProjeto.isCLI) && !contextoProjeto.isInfrastructure) {
        ocorrencias.push({ tipo: 'padrao-ausente', nivel: 'info', mensagem: messages.ComandosCliMensagens.padraoAusente, relPath, origem: 'analista-comandos-cli' });
    }
    const duplicata = verificarComandosDuplicados(comandoNomes, relPath);
    if (duplicata)
        ocorrencias.push(duplicata);
    for (const cmd of comandos) {
        const handlerInfo = cmd.handler;
        if (!handlerInfo || !handlerInfo.bodyBlock)
            continue;
        const linha = extrairLinha(handlerInfo, cmd.node);
        const statements = Array.isArray(handlerInfo.bodyBlock.body) ? handlerInfo.bodyBlock.body : [];
        for (const ocorrencia of [
            verificarHandlerAnonimo(handlerInfo, cmd.comandoNome, linha, relPath),
            verificarMuitosParams(handlerInfo, cmd.comandoNome, linha, relPath),
            verificarHandlerLongo(statements, cmd.comandoNome, linha, relPath),
            verificarTryCatch(statements, cmd.comandoNome, linha, relPath),
            verificarFeedback(handlerInfo, statements, cmd.comandoNome, linha, relPath, conteudo),
        ]) {
            if (ocorrencia)
                ocorrencias.push(ocorrencia);
        }
    }
    if (comandos.length > 1) {
        ocorrencias.push({ tipo: 'padrao-estrutural', nivel: 'info', mensagem: messages.ComandosCliMensagens.multiplosComandos(comandos.length), relPath, origem: 'analista-comandos-cli' });
    }
    return ocorrencias;
}
export const analistaComandosCli = {
    nome: 'analista-comandos-cli',
    test: (relPath) => {
        const caminho = relPath.replace(/\\/g, '/').toLowerCase();
        if (/(^|\/)tests?(\/)/i.test(caminho) || /\.(test|spec)\.(ts|js|tsx|jsx)$/i.test(caminho))
            return false;
        return /(\.ts|\.tsx|\.js|\.jsx)$/.test(caminho);
    },
    aplicar(conteudo, arquivo, ast, _fullPath, _contexto) {
        const contextoProjeto = detectarContextoProjeto({
            arquivo,
            conteudo,
            relPath: arquivo
        });
        if (!isRelevanteParaAnalise(contextoProjeto, 'comando'))
            return [];
        if (!ast)
            return [];
        const state = {
            comandos: [],
            comandoNomes: [],
            comandosInvocados: 0,
            hasFrameworkSignal: false,
            commandClassNomes: new Set(['Command']),
        };
        const registrar = criarRegistrar(state);
        const detectores = [
            (n) => detectarNewCommand(n, state),
            (n) => detectarDiscordJs(n, state),
            (n) => detectarCommanderChainMethods(n, state),
            (n) => detectarGenericCommand(n, state, registrar),
            (n) => detectarCommanderAction(n, state, registrar),
            (n) => detectarCommanderCommand(n, state, registrar),
            (n) => detectarYargs(n, state, registrar),
            (n) => detectarOclif(n, state, registrar),
            (n) => detectarImportCommander(n, state),
            (n) => detectarVarUsage(n, state),
        ];
        traverse(ast.node, {
            enter(path) {
                const nodeAtual = path.node;
                for (const detector of detectores)
                    detector(nodeAtual);
            }
        });
        return processarComandosRegistrados(state.comandos, state.comandoNomes, state.comandosInvocados, state.hasFrameworkSignal, arquivo, conteudo, contextoProjeto);
    }
};
//# sourceMappingURL=analyst-commands-cli.js.map