import { messages } from '../../../core/messages/index.js';
import { parseDocument } from 'yaml';
import { detectorDependenciasVulneraveis } from '../detectors/detector-dependencies-vulnerable.js';
import { detectarProblemasAcessibilidade } from '../detectors/detector-workflow-accessibility.js';
import { detectarProblemasCompliance } from '../detectors/detector-workflow-compliance.js';
import { detectorWorkflowPerformance } from '../detectors/detector-workflow-performance.js';
import { detectorWorkflowSecurity } from '../detectors/detector-workflow-security.js';
import { detectarJobsPassivos, detectorWorkflowEstrutura } from '../detectors/detector-workflow-structure.js';
import { detectorWorkflowTriggerInseguro } from '../detectors/detector-workflow-trigger-unsafe.js';
import { isOrgVerificada } from '../detectors/org-verified.js';
let _workflowNeedsFullHistory = false;
const detectoresRegistrados = [];
export function registrarDetectorGithubActions(detector) {
    detectoresRegistrados.push(detector);
}
export function obterDetectoresGithubActions() {
    return [...detectoresRegistrados];
}
export const analistaGithubActions = {
    nome: 'github-actions',
    categoria: 'workflows',
    descricao: 'Analista avançado de workflows do GitHub Actions com suporte a plugins',
    test: (relPath) => relPath.startsWith('.github/workflows/'),
    async aplicar(conteudo, relPath, _ast = null, _fc, contexto) {
        const ocorrencias = [];
        try {
            const doc = parseDocument(conteudo);
            const workflow = doc.toJS();
            const problemas = await executarDetectoresNativos(workflow, conteudo);
            const caminhos = contexto?.arquivos.map(arquivo => arquivo.relPath) || [];
            const plugResults = await Promise.all(detectoresRegistrados.map(diretorio => Promise.resolve(diretorio.testar(workflow, {
                conteudo,
                caminhos
            })).catch(() => [])));
            const securityResults = await Promise.resolve(detectorWorkflowSecurity.aplicar(conteudo, relPath, _ast, _fc, contexto));
            const performanceResults = await Promise.resolve(detectorWorkflowPerformance.aplicar(conteudo, relPath, _ast, _fc, contexto));
            const estruturaResults = await Promise.resolve(detectorWorkflowEstrutura.aplicar(conteudo, relPath, _ast, _fc, contexto));
            const depsResults = await Promise.resolve(detectorDependenciasVulneraveis.aplicar(conteudo, relPath, _ast, _fc, contexto));
            const triggerResults = await Promise.resolve(detectorWorkflowTriggerInseguro.aplicar(conteudo, relPath, _ast, _fc, contexto));
            const todos = [...problemas, ...plugResults.flat()];
            ocorrencias.push(...securityResults, ...performanceResults, ...estruturaResults, ...depsResults, ...triggerResults);
            for (const caminho of todos) {
                ocorrencias.push({
                    tipo: `GITHUB_ACTIONS_${caminho.tipo.toUpperCase().replace(/-/g, '_')}`,
                    mensagem: caminho.descricao,
                    relPath,
                    linha: caminho.linha || 1,
                    coluna: 1,
                    sugestao: caminho.sugestao || '',
                    nivel: caminho.severidade === 'critica' || caminho.severidade === 'alta' ? 'erro' : caminho.severidade === 'media' ? 'aviso' : 'info'
                });
            }
        }
        catch {
        }
        return ocorrencias;
    }
};
export const analistaGithubActionsGlobal = {
    nome: 'github-actions-global',
    categoria: 'workflows',
    descricao: 'Governança Global do GitHub',
    test: () => false,
    async aplicar(_c, _p, _a, _f, ctx) {
        if (!ctx)
            return [];
        const ores = [];
        const paths = ctx.arquivos.map(arquivo => arquivo.relPath);
        if (!paths.some(caminho => /CODEOWNERS/i.test(caminho))) {
            ores.push({
                tipo: 'GITHUB_ACTIONS_CODEOWNERS_MISSING',
                mensagem: messages.AnalistaGithubActionsMensagens.missingCodeowners,
                relPath: '.github/',
                linha: 0,
                coluna: 0,
                nivel: 'info'
            });
        }
        const readme = ctx.arquivos?.find(arquivo => /README\.md/i.test(arquivo.relPath ?? ''));
        const content = readme ? readme.content || '' : '';
        if (readme && !/Code of Conduct|Código de Conduta/i.test(content)) {
            ores.push({
                tipo: 'GITHUB_ACTIONS_DOC_GOVERNANCE_MISSING',
                mensagem: messages.AnalistaGithubActionsMensagens.missingCodeOfConduct,
                relPath: readme.relPath,
                linha: 1,
                coluna: 0,
                nivel: 'info'
            });
        }
        if (!paths.some(caminho => /stale/i.test(caminho))) {
            ores.push({
                tipo: 'GITHUB_ACTIONS_STALE_BOT_MISSING',
                mensagem: messages.AnalistaGithubActionsMensagens.missingStaleBot,
                relPath: '.github/workflows/',
                linha: 0,
                coluna: 0,
                nivel: 'info'
            });
        }
        if (!paths.some(caminho => /release|deploy/i.test(caminho))) {
            ores.push({
                tipo: 'GITHUB_ACTIONS_RELEASE_AUTOMATION_MISSING',
                mensagem: messages.AnalistaGithubActionsMensagens.missingReleaseAutomation,
                relPath: '.github/workflows/',
                linha: 0,
                coluna: 0,
                nivel: 'info'
            });
        }
        if (!paths.some(caminho => /\.github\/ISSUE_TEMPLATE/i.test(caminho))) {
            ores.push({
                tipo: 'GITHUB_ACTIONS_ISSUE_TEMPLATES_MISSING',
                mensagem: messages.AnalistaGithubActionsMensagens.missingIssueTemplates,
                relPath: '.github/ISSUE_TEMPLATE/',
                linha: 0,
                coluna: 0,
                nivel: 'info'
            });
        }
        if (!paths.some(caminho => /pull_request_template/i.test(caminho))) {
            ores.push({
                tipo: 'GITHUB_ACTIONS_PR_TEMPLATE_MISSING',
                mensagem: messages.AnalistaGithubActionsMensagens.missingPrTemplate,
                relPath: '.github/',
                linha: 0,
                coluna: 0,
                nivel: 'info'
            });
        }
        const hasSemanticRelease = paths.some(caminho => /semantic-release|semrel/i.test(caminho));
        const hasChangelog = paths.some(caminho => /changelog/i.test(caminho));
        if (!hasSemanticRelease) {
            ores.push({
                tipo: 'GITHUB_ACTIONS_SEMANTIC_VERSIONING_MISSING',
                mensagem: messages.AnalistaGithubActionsMensagens.missingSemanticVersioning,
                relPath: '.github/workflows/',
                linha: 0,
                coluna: 0,
                nivel: 'info'
            });
        }
        if (!hasChangelog) {
            ores.push({
                tipo: 'GITHUB_ACTIONS_CHANGELOG_AUTOMATION_MISSING',
                mensagem: messages.AnalistaGithubActionsMensagens.missingChangelogAutomation,
                relPath: '.github/workflows/',
                linha: 0,
                coluna: 0,
                nivel: 'info'
            });
        }
        const sourceFiles = ctx.arquivos.filter(arquivo => /\.(ts|js)$/.test(arquivo.relPath));
        const missingLicense = sourceFiles.filter(arquivo => !/SPDX-License-Identifier/i.test(arquivo.content || ''));
        if (missingLicense.length > sourceFiles.length / 2) {
            ores.push({
                tipo: 'GITHUB_ACTIONS_LICENSE_HEADERS_MISSING',
                mensagem: messages.AnalistaGithubActionsMensagens.missingLicenseHeaders,
                relPath: './',
                linha: 0,
                coluna: 0,
                nivel: 'info'
            });
        }
        return ores;
    }
};
function workflowRequerHistoricoCompleto(wf, raw) {
    const gitCommandsQuePrecisamDeHistorico = /\b(git\s+(rev-parse|tag|describe|log|shortlog|diff|bisect|blame)|git\s+checkout\s+-b|git\s+fetch\s+--unshallow|git\s+clone\s+--depth)/gi;
    if (gitCommandsQuePrecisamDeHistorico.test(raw)) {
        return true;
    }
    const jobs = wf?.jobs ? Object.values(wf.jobs) : [];
    const allSteps = jobs.flatMap(job => job?.steps || []);
    for (const step of allSteps) {
        if (step?.run && gitCommandsQuePrecisamDeHistorico.test(step.run)) {
            return true;
        }
    }
    return false;
}
async function executarDetectoresNativos(wf, raw) {
    const probs = [];
    if (!wf)
        return probs;
    _workflowNeedsFullHistory = workflowRequerHistoricoCompleto(wf, raw);
    const candidates = Array.isArray(wf) ? wf : [wf];
    for (const item of candidates) {
        if (typeof item === 'object' && item !== null) {
            analisarEstrutura(item, probs);
        }
    }
    probs.push(...detectarProblemasCompliance(wf, raw));
    probs.push(...detectarProblemasAcessibilidade(wf));
    if (typeof wf === 'object' && 'jobs' in wf) {
        const wfJobs = wf.jobs;
        if (wfJobs) {
            for (const [, job] of Object.entries(wfJobs)) {
                if (detectarJobsPassivos(job)) {
                    probs.push({
                        tipo: 'job-passivo-workflow',
                        descricao: 'Job passivo detectado',
                        severidade: 'baixa'
                    });
                }
            }
        }
    }
    if (/sudo\s+/.test(raw)) {
        probs.push({
            tipo: 'uso-sudo',
            descricao: 'Uso de sudo detectado',
            severidade: 'alta'
        });
    }
    if (raw.includes('pull_request_target:')) {
        probs.push({
            tipo: 'estrutura-workflow',
            descricao: 'Uso de pull_request_target',
            severidade: 'alta'
        });
    }
    return probs;
}
function analisarEstrutura(wf, probs) {
    if (!wf.name) {
        probs.push({
            tipo: 'workflow-sem-nome',
            descricao: 'Workflow sem nome definido',
            severidade: 'baixa'
        });
    }
    if (wf.container && typeof wf.container === 'string') {
        probs.push({
            tipo: 'container-sem-user',
            descricao: 'Container sem user',
            severidade: 'media'
        });
    }
    if (wf.strategy?.matrix && wf.strategy['fail-fast'] === undefined) {
        probs.push({
            tipo: 'matrix-sem-fail-fast',
            descricao: 'Matrix sem fail-fast',
            severidade: 'baixa'
        });
    }
    for (const [chave, valor] of Object.entries(wf)) {
        if (/(KEY|TOKEN|SECRET|PASSWORD)/i.test(chave) && valor && typeof valor !== 'object' && !String(valor).includes('${{')) {
            probs.push({
                tipo: 'env-sensivel',
                descricao: `Secret hardcoded em ${chave}`,
                severidade: 'critica'
            });
        }
    }
    if (wf.jobs) {
        const jobEntries = Object.entries(wf.jobs);
        for (const [id, job] of jobEntries) {
            if (job?.strategy?.matrix && job.strategy['fail-fast'] === undefined) {
                probs.push({
                    tipo: 'matrix-sem-fail-fast',
                    descricao: `Job ${id} sem fail-fast`,
                    severidade: 'baixa'
                });
            }
            if (job?.container && typeof job.container === 'string') {
                probs.push({
                    tipo: 'container-sem-user',
                    descricao: 'Container rodando como root',
                    severidade: 'media'
                });
            }
            if (/^(job\d+|step\d+|build\d*|test\d*|j\d+)$/i.test(id) && !job?.name) {
                probs.push({
                    tipo: 'job-sem-nome',
                    descricao: `Job '${id}' usa ID genérico sem campo 'name'`,
                    severidade: 'baixa',
                    sugestao: 'Usar nomes semânticos como build-backend, test-integration'
                });
            }
            if (job?.steps) {
                for (const string of job.steps)
                    analisarStep(string, probs);
                analisarStepsSemNome(job.steps, probs);
                if (job.steps.length > 15) {
                    probs.push({
                        tipo: 'job-longo',
                        descricao: `Job '${id}' possui ${job.steps.length} steps, o que dificulta a manutenção`,
                        severidade: 'baixa',
                        sugestao: 'Considere quebrar em jobs menores ou usar composite actions'
                    });
                }
            }
        }
        analisarParalelismo(jobEntries, probs);
        analisarDownloadArtifacts(jobEntries, probs);
        analisarMultiplasInstalacoesDeps(jobEntries, probs);
    }
    if (wf.steps && Array.isArray(wf.steps)) {
        for (const string of wf.steps)
            analisarStep(string, probs);
        analisarStepsSemNome(wf.steps, probs);
    }
    if (wf.uses || wf.run)
        analisarStep(wf, probs);
}
function analisarStep(string, probs) {
    if (!string)
        return;
    if (string.uses && /@v\d+/.test(string.uses)) {
        const actionRef = string.uses.split('@')[0];
        if (!isOrgVerificada(actionRef)) {
            probs.push({
                tipo: 'falta-sha-pinning',
                descricao: 'Pinning por SHA faltando',
                severidade: 'media'
            });
        }
    }
    if (string.uses?.includes('upload-artifact') && string.with?.path?.includes('.env')) {
        probs.push({
            tipo: 'upload-sensivel',
            descricao: 'Upload de .env',
            severidade: 'critica'
        });
    }
    if (string.env) {
        for (const [chave, valor] of Object.entries(string.env)) {
            if (/(KEY|TOKEN|SECRET|PASSWORD)/i.test(chave) && valor && typeof valor !== 'object' && !String(valor).includes('${{')) {
                probs.push({
                    tipo: 'env-sensivel',
                    descricao: `Env ${chave} hardcoded`,
                    severidade: 'critica'
                });
            }
        }
    }
    if (string.run && /\$\{\{\s*github\.event\./.test(string.run)) {
        probs.push({
            tipo: 'script-injection',
            descricao: 'Script injection',
            severidade: 'alta'
        });
    }
    if (string.uses && /docker\/build-push-action/.test(string.uses)) {
        const hasCacheFrom = string.with?.['cache-from'] !== undefined;
        const hasCacheTo = string.with?.['cache-to'] !== undefined;
        if (!hasCacheFrom && !hasCacheTo) {
            probs.push({
                tipo: 'docker-sem-layer-cache',
                descricao: 'Docker build sem cache de layers configurado',
                severidade: 'media',
                sugestao: 'Adicionar cache-from e cache-to para otimizar builds Docker'
            });
        }
    }
    if (string.uses && /actions\/checkout/.test(string.uses) && string.with) {
        const fd = string.with['fetch-depth'];
        if (fd === 0 || fd === '0') {
            if (!_workflowNeedsFullHistory) {
                probs.push({
                    tipo: 'fetch-depth-desnecessario',
                    descricao: 'checkout com fetch-depth: 0 baixa todo o histórico do repositório',
                    severidade: 'baixa',
                    sugestao: 'Usar fetch-depth: 1 (default) a menos que precise do histórico completo para tags/changelog'
                });
            }
        }
    }
}
const ACTIONS_AUTO_EXPLICATIVAS = [
    'actions/checkout',
    'actions/setup-node',
    'actions/setup-python',
    'actions/setup-java',
    'actions/setup-go',
    'actions/setup-dotnet',
    'actions/setup-ruby',
    'actions/upload-artifact',
    'actions/download-artifact',
    'actions/cache',
    'actions/stale',
    'actions/labeler',
    'actions/dependency-review',
    'actions/attest-build-provenance',
    'docker/login-action',
    'docker/build-push-action',
    'docker/metadata-action',
    'docker/setup-buildx-action',
    'docker/setup-qemu-action',
    'azure/login',
    'azure/webapps-deploy',
    'aws-actions/configure-aws-credentials',
    'google-github-actions/auth',
    'github/codeql-action/init',
    'github/codeql-action/analyze',
    'github/codeql-action/autobuild',
    'softprops/action-gh-release',
    'cypress-io/github-action',
];
function analisarStepsSemNome(steps, probs) {
    for (const string of steps) {
        if (!string || string.name)
            continue;
        if (string.uses || string.run) {
            if (string.uses && ACTIONS_AUTO_EXPLICATIVAS.some(action => string.uses?.startsWith(action))) {
                continue;
            }
            const ident = string.uses ? `uses: ${string.uses}` : 'run command';
            probs.push({
                tipo: 'step-sem-nome',
                descricao: `Step sem campo 'name' (${ident})`,
                severidade: 'baixa',
                sugestao: 'Adicionar campo name: para melhor legibilidade nos logs do Actions'
            });
        }
    }
}
function analisarParalelismo(jobEntries, probs) {
    if (jobEntries.length < 3)
        return;
    const workflowName = jobEntries[0]?.[1]?.name?.toLowerCase() || '';
    const jobNames = jobEntries.map(([, j]) => j?.name?.toLowerCase() || '').join(' ');
    const isReleaseWorkflow = /release|deploy|publish|ship/i.test(workflowName) || /release|deploy|publish|ship/i.test(jobNames);
    if (isReleaseWorkflow)
        return;
    let totalWithNeeds = 0;
    for (const [, job] of jobEntries) {
        if (job?.needs)
            totalWithNeeds++;
    }
    if (totalWithNeeds === jobEntries.length - 1) {
        probs.push({
            tipo: 'build-sem-parallelismo',
            descricao: 'Todos os jobs estão em cadeia linear - considere paralelizar jobs independentes',
            severidade: 'media',
            sugestao: 'Remover dependências (needs) desnecessárias para permitir execução paralela'
        });
    }
}
function analisarDownloadArtifacts(jobEntries, probs) {
    let downloadCount = 0;
    for (const [, job] of jobEntries) {
        if (!job?.steps)
            continue;
        for (const string of job.steps) {
            if (string.uses && /download-artifact/.test(string.uses))
                downloadCount++;
        }
    }
    if (downloadCount >= 3) {
        probs.push({
            tipo: 'download-artifacts-desnecessario',
            descricao: `${downloadCount} download-artifact encontrados - considere consolidar artifacts`,
            severidade: 'baixa',
            sugestao: 'Consolidar artifacts ou usar cache compartilhado'
        });
    }
}
function analisarMultiplasInstalacoesDeps(jobEntries, probs) {
    let installCount = 0;
    let hasCacheStep = false;
    let hasSetupNodeWithCache = false;
    for (const [, job] of jobEntries) {
        if (!job?.steps)
            continue;
        let jobHasInstall = false;
        for (const string of job.steps) {
            if (string.run && /\b(npm\s+(ci|install)|yarn\s+install|pnpm\s+install)\b/.test(string.run)) {
                jobHasInstall = true;
            }
            if (string.uses && /actions\/cache/.test(string.uses)) {
                hasCacheStep = true;
            }
            if (string.uses && /actions\/setup-node/.test(string.uses) && string.with?.cache) {
                hasSetupNodeWithCache = true;
            }
        }
        if (jobHasInstall)
            installCount++;
    }
    if (installCount >= 2 && !hasCacheStep && !hasSetupNodeWithCache) {
        probs.push({
            tipo: 'multiplas-instalacoes-deps',
            descricao: `Instalação de dependências em ${installCount} jobs sem cache compartilhado`,
            severidade: 'media',
            sugestao: 'Usar actions/cache ou artifact de node_modules para evitar instalações duplicadas'
        });
    }
}
//# sourceMappingURL=analyst-github-actions.js.map