// engine/workers/handlers/dependency-health.ts — Analyzes dependency manifests (SPEC-078 AC-10)
import { readFile, stat } from 'node:fs/promises';
import { join, relative } from 'node:path';
// ---------------------------------------------------------------------------
// Analysis helpers
// ---------------------------------------------------------------------------
function analyzePackageJson(content, relPath) {
    const findings = [];
    let pkg;
    try {
        pkg = JSON.parse(content);
    }
    catch {
        findings.push({
            severity: 'medium',
            file: relPath,
            message: 'Invalid JSON in package.json',
            category: 'dependency-health',
        });
        return findings;
    }
    const checkDeps = (deps, section) => {
        if (!deps || typeof deps !== 'object') {
            return;
        }
        const depMap = deps;
        for (const [name, version] of Object.entries(depMap)) {
            if (version === '*' || version === 'latest') {
                findings.push({
                    severity: 'high',
                    file: relPath,
                    message: `${section}.${name} uses unpinned version "${version}"`,
                    suggestion: `Pin to a specific version or range (e.g., ^1.0.0)`,
                    category: 'unpinned-dependency',
                });
            }
            else if (version.startsWith('>=') && !version.includes('<')) {
                findings.push({
                    severity: 'medium',
                    file: relPath,
                    message: `${section}.${name} uses open-ended range "${version}"`,
                    suggestion: 'Use a caret range (^) or tilde range (~) instead',
                    category: 'wide-range',
                });
            }
        }
    };
    checkDeps(pkg.dependencies, 'dependencies');
    checkDeps(pkg.devDependencies, 'devDependencies');
    return findings;
}
function analyzePyproject(content, relPath) {
    const findings = [];
    const lines = content.split('\n');
    let inDeps = false;
    for (const line of lines) {
        if (line.includes('[project.dependencies]') || line.includes('[tool.poetry.dependencies]')) {
            inDeps = true;
            continue;
        }
        if (line.startsWith('[') && inDeps) {
            inDeps = false;
            continue;
        }
        if (inDeps) {
            const trimmed = line.trim();
            if (!trimmed || trimmed.startsWith('#')) {
                continue;
            }
            // Check for missing version specifier
            if (trimmed.includes('=') || trimmed.includes('"')) {
                const nameMatch = /^["']?(\S+?)["']?\s*$/.exec(trimmed);
                if (nameMatch?.[1] &&
                    !trimmed.includes('>=') &&
                    !trimmed.includes('==') &&
                    !trimmed.includes('^') &&
                    !trimmed.includes('~')) {
                    // Simple name without version
                    if (!trimmed.includes('=')) {
                        findings.push({
                            severity: 'medium',
                            file: relPath,
                            message: `Dependency "${nameMatch[1]}" has no version constraint`,
                            suggestion: 'Pin to a specific version (e.g., >=1.0.0,<2.0.0)',
                            category: 'unpinned-dependency',
                        });
                    }
                }
            }
        }
    }
    return findings;
}
function analyzeRequirementsTxt(content, relPath) {
    const findings = [];
    const lines = content.split('\n');
    for (const line of lines) {
        const trimmed = line.trim();
        if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('-')) {
            continue;
        }
        if (!trimmed.includes('==') && !trimmed.includes('>=') && !trimmed.includes('~=')) {
            const name = trimmed.split(/[<>=!]/)[0]?.trim();
            if (name) {
                findings.push({
                    severity: 'medium',
                    file: relPath,
                    message: `"${name}" has no version pinning`,
                    suggestion: 'Pin with == for reproducible builds',
                    category: 'unpinned-dependency',
                });
            }
        }
    }
    return findings;
}
function analyzeGoMod(content, relPath) {
    const findings = [];
    const lines = content.split('\n');
    for (const line of lines) {
        const trimmed = line.trim();
        if (trimmed.startsWith('require') && !trimmed.includes('(')) {
            const parts = trimmed.split(/\s+/);
            if (parts.length >= 3 && parts[2]?.includes('+incompatible')) {
                findings.push({
                    severity: 'low',
                    file: relPath,
                    message: `Module ${parts[1] ?? 'unknown'} uses +incompatible`,
                    suggestion: 'Consider upgrading to a module-aware version',
                    category: 'dependency-health',
                });
            }
        }
    }
    return findings;
}
function analyzeCargoToml(content, relPath) {
    const findings = [];
    const lines = content.split('\n');
    let inDeps = false;
    for (const line of lines) {
        if (line.includes('[dependencies]') || line.includes('[dev-dependencies]')) {
            inDeps = true;
            continue;
        }
        if (line.startsWith('[') && inDeps) {
            inDeps = false;
            continue;
        }
        if (inDeps) {
            const trimmed = line.trim();
            if (!trimmed || trimmed.startsWith('#')) {
                continue;
            }
            if (trimmed.includes('"*"')) {
                const name = trimmed.split('=')[0]?.trim();
                findings.push({
                    severity: 'high',
                    file: relPath,
                    message: `${name ?? 'unknown'} uses wildcard version "*"`,
                    suggestion: 'Pin to a specific version',
                    category: 'unpinned-dependency',
                });
            }
        }
    }
    return findings;
}
// ---------------------------------------------------------------------------
// Manifest configs
// ---------------------------------------------------------------------------
const MANIFESTS = [
    {
        file: 'package.json',
        lockfiles: ['package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lockb'],
        analyze: analyzePackageJson,
    },
    {
        file: 'pyproject.toml',
        lockfiles: ['poetry.lock', 'pdm.lock', 'uv.lock'],
        analyze: analyzePyproject,
    },
    {
        file: 'requirements.txt',
        lockfiles: [],
        analyze: analyzeRequirementsTxt,
    },
    {
        file: 'go.mod',
        lockfiles: ['go.sum'],
        analyze: analyzeGoMod,
    },
    {
        file: 'Cargo.toml',
        lockfiles: ['Cargo.lock'],
        analyze: analyzeCargoToml,
    },
];
// ---------------------------------------------------------------------------
// Public handler
// ---------------------------------------------------------------------------
export async function runDependencyHealth(projectDir, _changedFiles) {
    const startedAt = new Date().toISOString();
    const start = Date.now();
    const findings = [];
    let analyzedFiles = 0;
    for (const manifest of MANIFESTS) {
        const manifestPath = join(projectDir, manifest.file);
        let content;
        try {
            content = await readFile(manifestPath, 'utf-8');
        }
        catch {
            continue;
        }
        analyzedFiles++;
        const relPath = relative(projectDir, manifestPath);
        const manifestFindings = manifest.analyze(content, relPath);
        findings.push(...manifestFindings);
        // Check lockfile existence
        if (manifest.lockfiles.length > 0) {
            let hasLockfile = false;
            for (const lockfile of manifest.lockfiles) {
                try {
                    await stat(join(projectDir, lockfile));
                    hasLockfile = true;
                    break;
                }
                catch {
                    // lockfile not found
                }
            }
            if (!hasLockfile) {
                findings.push({
                    severity: 'medium',
                    file: relPath,
                    message: `No lockfile found for ${manifest.file}`,
                    suggestion: `Generate lockfile (e.g., ${manifest.lockfiles[0] ?? 'lockfile'})`,
                    category: 'missing-lockfile',
                });
            }
        }
    }
    return {
        workerName: 'dependency-health',
        startedAt,
        completedAt: new Date().toISOString(),
        durationMs: Date.now() - start,
        status: 'success',
        findings,
        analyzedFiles,
    };
}
//# sourceMappingURL=dependency-health.js.map