import path from 'node:path';
import { config, traverse } from '../../core/config/index.js';
import { messages } from '../../core/messages/index.js';
import { normalizarPosix, normalizePath, resolverModulo } from '../../shared/helpers/index.js';
export const grafoDependencias = new Map();
export const importsUsadosDinamicamente = new Map();
function ehImportSomenteTipo(node) {
    if (node.importKind === 'type')
        return true;
    const specifiers = node.specifiers || [];
    if (specifiers.length === 0)
        return false;
    return specifiers.every(s => {
        if (s.type === 'ImportSpecifier')
            return s.importKind === 'type';
        return false;
    });
}
function verificarCicloConfirmado(ciclo, grafo, arquivosExistentes) {
    if (ciclo.length < 2)
        return false;
    if (!arquivosExistentes)
        return true;
    for (const node of ciclo) {
        const normalized = normalizarPosix(node);
        if (!arquivosExistentes.has(normalized))
            return false;
    }
    for (let indice = 0; indice < ciclo.length - 1; indice++) {
        const origem = normalizarPosix(ciclo[indice]);
        const destino = normalizarPosix(ciclo[indice + 1]);
        const deps = grafo.get(origem);
        if (!deps)
            return false;
        const depsNorm = new Set(Array.from(deps).map(diretorio => normalizarPosix(diretorio)));
        if (!depsNorm.has(destino))
            return false;
    }
    return true;
}
function detectarCicloComplexo(inicio, grafo, maxProfundidade = 5) {
    const visitados = new Set();
    const pilha = new Set();
    const caminho = [];
    const inicioNormalizado = normalizePath(inicio, true);
    function resolverCaminho(from, to) {
        if (to.startsWith('.')) {
            const fromDir = path.dirname(from);
            const resolved = path.join(fromDir, to);
            return normalizePath(resolved, true);
        }
        return normalizePath(to, true);
    }
    function buscarDependencias(arquivo) {
        const normalizado = normalizePath(arquivo, true);
        if (grafo.has(arquivo))
            return grafo.get(arquivo);
        if (grafo.has(normalizado))
            return grafo.get(normalizado);
        for (const [chave, deps] of grafo.entries()) {
            if (normalizePath(chave, true) === normalizado) {
                return deps;
            }
        }
        return undefined;
    }
    function dfs(atual, profundidade) {
        if (profundidade > maxProfundidade)
            return false;
        const atualNormalizado = normalizePath(atual, true);
        if (pilha.has(atualNormalizado)) {
            const indiceCiclo = caminho.findIndex(caminho => normalizePath(caminho, true) === atualNormalizado);
            if (indiceCiclo >= 0) {
                caminho.push(atualNormalizado);
                return true;
            }
            return false;
        }
        if (visitados.has(atualNormalizado))
            return false;
        visitados.add(atualNormalizado);
        pilha.add(atualNormalizado);
        caminho.push(atualNormalizado);
        const dependencias = buscarDependencias(atual);
        if (dependencias) {
            for (const dep of dependencias) {
                if (!dep.startsWith('.') && !dep.startsWith('/') && !dep.startsWith('src'))
                    continue;
                const depResolvida = resolverCaminho(atualNormalizado, dep);
                if (dfs(depResolvida, profundidade + 1)) {
                    return true;
                }
            }
        }
        pilha.delete(atualNormalizado);
        if (caminho.length > 0) {
            caminho.pop();
        }
        return false;
    }
    if (dfs(inicioNormalizado, 0)) {
        const ultimoNo = caminho[caminho.length - 1];
        const indiceCiclo = caminho.findIndex(caminho => normalizePath(caminho, true) === ultimoNo);
        if (indiceCiclo >= 0) {
            const cicloCompleto = caminho.slice(indiceCiclo);
            const cicloValido = cicloCompleto.every(no => {
                const normalizado = normalizePath(no, true);
                return grafo.has(no) || grafo.has(normalizado) || Array.from(grafo.keys()).some(chave => normalizePath(chave, true) === normalizado);
            });
            return cicloValido ? cicloCompleto : [];
        }
    }
    return [];
}
export function isUsadoEmRegistroDinamico(src, importName) {
    const padroesRegistro = [
        `register\\(\\s*${importName}`, `\\.register\\(\\s*${importName}`, `use\\(\\s*${importName}`, `\\.use\\(\\s*${importName}`, `registerPlugin\\(\\s*${importName}`, `addPlugin\\(\\s*${importName}`, `apply\\(\\s*${importName}`, `\\.apply\\(\\s*${importName}`, `load\\(\\s*${importName}`, `\\.load\\(\\s*${importName}`,
        `\\.on\\(\\s*[^,]+,\\s*${importName}\\s*\\)`, `\\.once\\(\\s*[^,]+,\\s*${importName}\\s*\\)`,
        `\\.(?:get|post|put|delete|patch|options|head)\\(\\s*[^,]+,\\s*${importName}\\s*\\)`,
        `\\[\\s*[^\\]]*${importName}[^\\]]*\\]`, `\\.push\\(\\s*${importName}\\s*\\)`,
        `providers\\s*:\\s*\\[[^\\]]*${importName}[^\\]]*\\]`, `controllers\\s*:\\s*\\[[^\\]]*${importName}[^\\]]*\\]`, `imports\\s*:\\s*\\[[^\\]]*${importName}[^\\]]*\\]`, `exports\\s*:\\s*\\[[^\\]]*${importName}[^\\]]*\\]`,
        `components\\s*:\\s*\\{[^}]*${importName}[^}]*\\}`,
        `combineReducers\\s*\\(\\s*\\{[^}]*${importName}[^}]*\\}`,
        `:\\s*${importName}\\s*[,}]`
    ];
    return padroesRegistro.some(padrao => new RegExp(padrao).test(src));
}
function getMaxProfundidade() {
    const cfg = config;
    return typeof cfg.DEPENDENCIAS_MAX_PROFUNDIDADE === 'number' ? cfg.DEPENDENCIAS_MAX_PROFUNDIDADE : 5;
}
function getVerifyCycles() {
    return Boolean(config.SPECIAL_VERIFY_CYCLES);
}
function processarImportDeclaration(caminho, state) {
    const somenteTipo = ehImportSomenteTipo(caminho.node);
    if (!somenteTipo)
        state.tiposImport.add('import');
    const val = caminho.node.source.value;
    if (somenteTipo) {
        const specifiers = caminho.node.specifiers || [];
        for (const s of specifiers) {
            if (s.type === 'ImportDefaultSpecifier' || s.type === 'ImportSpecifier') {
                state.importsDeclarados.add(s.local.name);
            }
            else if (s.type === 'ImportNamespaceSpecifier') {
                state.importsDeclarados.add(s.local.name);
            }
        }
        return;
    }
    const resolved = resolverModulo(val, state.relPath, state.arquivosExistentes);
    if (!state.depsSet) {
        state.depsSet = new Set();
        grafoDependencias.set(state.relPath, state.depsSet);
    }
    state.depsSet.add(resolved.key);
    if (!val.startsWith('.') && !val.startsWith('/')) {
        state.ocorrencias.push({
            tipo: 'info',
            mensagem: messages.DetectorDependenciasMensagens.importDependenciaExterna(val),
            relPath: state.relPath,
            linha: caminho.node.loc?.start.line,
            coluna: caminho.node.loc?.start.column
        });
    }
    if (val.startsWith('.') && val.split('../').length > 3) {
        state.ocorrencias.push({
            tipo: 'aviso',
            mensagem: messages.DetectorDependenciasMensagens.importRelativoLongo(val),
            relPath: state.relPath,
            linha: caminho.node.loc?.start.line,
            coluna: caminho.node.loc?.start.column
        });
    }
    if (state.relPath.endsWith('.ts') && val.endsWith('.js') && !resolved.key.endsWith('.ts')) {
        state.ocorrencias.push({
            tipo: 'aviso',
            mensagem: messages.DetectorDependenciasMensagens.importJsEmTs(val),
            relPath: state.relPath,
            linha: caminho.node.loc?.start.line,
            coluna: caminho.node.loc?.start.column
        });
    }
    if (val.startsWith('.')) {
        if (state.arquivosExistentes && !resolved.existe) {
            state.ocorrencias.push({
                tipo: 'erro',
                mensagem: messages.DetectorDependenciasMensagens.importArquivoInexistente(val),
                relPath: state.relPath,
                linha: caminho.node.loc?.start.line,
                coluna: caminho.node.loc?.start.column
            });
        }
    }
    const specifiers = caminho.node.specifiers || [];
    for (const s of specifiers) {
        if (s.type === 'ImportDefaultSpecifier' || s.type === 'ImportSpecifier') {
            state.importsDeclarados.add(s.local.name);
        }
        else if (s.type === 'ImportNamespaceSpecifier') {
            state.importsDeclarados.add(s.local.name);
        }
    }
}
function processarCallExpression(caminho, state) {
    const { callee, arguments: args } = caminho.node;
    if (callee.type !== 'Identifier' || callee.name !== 'require' || args[0]?.type !== 'StringLiteral')
        return;
    state.tiposImport.add('require');
    const val = args[0].value;
    const resolved = resolverModulo(val, state.relPath, state.arquivosExistentes);
    if (!state.depsSet) {
        state.depsSet = new Set();
        grafoDependencias.set(state.relPath, state.depsSet);
    }
    state.depsSet.add(resolved.key);
    if (!val.startsWith('.') && !val.startsWith('/')) {
        state.ocorrencias.push({
            tipo: 'info',
            mensagem: messages.DetectorDependenciasMensagens.requireDependenciaExterna(val),
            relPath: state.relPath,
            linha: caminho.node.loc?.start.line,
            coluna: caminho.node.loc?.start.column
        });
    }
    if (val.startsWith('.') && val.split('../').length > 3) {
        state.ocorrencias.push({
            tipo: 'aviso',
            mensagem: messages.DetectorDependenciasMensagens.requireRelativoLongo(val),
            relPath: state.relPath,
            linha: caminho.node.loc?.start.line,
            coluna: caminho.node.loc?.start.column
        });
    }
    if (state.relPath.endsWith('.ts') && val.endsWith('.js') && !resolved.key.endsWith('.ts')) {
        state.ocorrencias.push({
            tipo: 'aviso',
            mensagem: messages.DetectorDependenciasMensagens.requireJsEmTs(val),
            relPath: state.relPath,
            linha: caminho.node.loc?.start.line,
            coluna: caminho.node.loc?.start.column
        });
    }
    if (val.startsWith('.')) {
        if (state.arquivosExistentes && !resolved.existe) {
            state.ocorrencias.push({
                tipo: 'erro',
                mensagem: messages.DetectorDependenciasMensagens.requireArquivoInexistente(val),
                relPath: state.relPath,
                linha: caminho.node.loc?.start.line,
                coluna: caminho.node.loc?.start.column
            });
        }
    }
}
function processarImportsUsadosDinamicamente(src, state) {
    if (state.importsDeclarados.size === 0)
        return;
    let usadosDinamicos = importsUsadosDinamicamente.get(state.relPath);
    for (const nome of Array.from(state.importsDeclarados)) {
        if (!isUsadoEmRegistroDinamico(src, nome))
            continue;
        if (!usadosDinamicos) {
            usadosDinamicos = new Set();
            importsUsadosDinamicamente.set(state.relPath, usadosDinamicos);
        }
        usadosDinamicos.add(nome);
        if (config.VERBOSE) {
            state.ocorrencias.push({
                tipo: 'info',
                mensagem: messages.DetectorDependenciasMensagens.importUsadoRegistroDinamico(nome),
                relPath: state.relPath
            });
        }
    }
}
function processarMisturaRequireImport(state) {
    if (state.tiposImport.size <= 1)
        return;
    state.ocorrencias.push({
        tipo: 'aviso',
        mensagem: messages.DetectorDependenciasMensagens.usoMistoRequireImport,
        relPath: state.relPath
    });
}
function processarCicloDependencia(state, grafoDependencias) {
    if (grafoDependencias.get(state.relPath)?.has(state.relPath)) {
        state.ocorrencias.push({
            tipo: 'alerta',
            mensagem: messages.DetectorDependenciasMensagens.importCircularSelf,
            relPath: state.relPath
        });
    }
    const ciclo = detectarCicloComplexo(state.relPath, grafoDependencias, getMaxProfundidade());
    if (ciclo.length <= 1)
        return;
    if (getVerifyCycles() && !verificarCicloConfirmado(ciclo, grafoDependencias, state.arquivosExistentes)) {
        return;
    }
    const caminhoLimpo = ciclo.map(c => {
        const relativo = path.relative(process.cwd(), c) || c;
        return normalizePath(relativo);
    });
    const caminhoCompleto = caminhoLimpo.join(' → ');
    state.ocorrencias.push({
        tipo: 'alerta',
        mensagem: messages.DetectorDependenciasMensagens.dependenciaCircular(ciclo.length, caminhoCompleto),
        relPath: state.relPath,
        contexto: `Ciclo completo: ${caminhoCompleto}`
    });
}
export const detectorDependencias = {
    nome: 'detector-dependencias',
    test(relPath) {
        return relPath.endsWith('.ts') || relPath.endsWith('.js');
    },
    aplicar(src, relPath, ast, _fullPath, contexto) {
        if (!ast)
            return [];
        const state = {
            ocorrencias: [],
            tiposImport: new Set(),
            arquivosExistentes: contexto ? new Set(contexto.arquivos.map(a => a.relPath)) : undefined,
            depsSet: grafoDependencias.get(relPath),
            importsDeclarados: new Set(),
            relPath
        };
        traverse(ast.node, {
            ImportDeclaration(caminho) {
                processarImportDeclaration(caminho, state);
            },
            CallExpression(caminho) {
                processarCallExpression(caminho, state);
            }
        });
        processarImportsUsadosDinamicamente(src, state);
        processarMisturaRequireImport(state);
        processarCicloDependencia(state, grafoDependencias);
        return state.ocorrencias;
    }
};
//# sourceMappingURL=detector-dependencies.js.map