// manifest-reader.ts — Reads project manifest files (SPEC-041)
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { glob } from 'glob';
// ---------------------------------------------------------------------------
// Ecosystem detection
// ---------------------------------------------------------------------------
/** Detect the primary ecosystem for a project path. */
export async function detectEcosystem(projectPath) {
    const checks = [
        ['package.json', 'nodejs'],
        ['requirements.txt', 'python'],
        ['pyproject.toml', 'python'],
        ['go.mod', 'go'],
        ['Cargo.toml', 'rust'],
        ['pom.xml', 'java'],
        ['build.gradle', 'java'],
        ['Directory.Build.props', 'dotnet'],
    ];
    for (const [file, ecosystem] of checks) {
        try {
            await readFile(join(projectPath, file), 'utf-8');
            return ecosystem;
        }
        catch {
            // continue checking
        }
    }
    // Check for .csproj files (glob-based, file name is variable)
    const csprojFiles = await glob(`${projectPath}/*.csproj`);
    if (csprojFiles.length > 0) {
        return 'dotnet';
    }
    return 'unknown';
}
// ---------------------------------------------------------------------------
// Dependency parsing
// ---------------------------------------------------------------------------
/** Parse package.json content and return deps + devDeps merged. */
export function parseNodeDependencies(packageJsonContent) {
    let parsed;
    try {
        parsed = JSON.parse(packageJsonContent);
    }
    catch {
        return {};
    }
    if (typeof parsed !== 'object' || parsed === null) {
        return {};
    }
    const obj = parsed;
    const deps = (obj.dependencies ?? {});
    const devDeps = (obj.devDependencies ?? {});
    return { ...deps, ...devDeps };
}
// ---------------------------------------------------------------------------
// Lock file parsing
// ---------------------------------------------------------------------------
/** Parse pnpm-lock.yaml for resolved versions (best-effort). */
function parsePnpmLock(content) {
    const result = new Map();
    // Pattern: "  /package@version:\n" or "  package@version:\n"
    const re = /^\s{2}(?:\/?)([\w@/-]+)@([^:\s]+):/gm;
    let match;
    while ((match = re.exec(content)) !== null) {
        const name = match[1];
        const version = match[2];
        if (name && version && !result.has(name)) {
            result.set(name, version);
        }
    }
    return result;
}
/** Parse package-lock.json v2/v3 for resolved versions. */
function parsePackageLock(content) {
    const result = new Map();
    let parsed;
    try {
        parsed = JSON.parse(content);
    }
    catch {
        return result;
    }
    if (typeof parsed !== 'object' || parsed === null) {
        return result;
    }
    const obj = parsed;
    const packages = obj.packages;
    if (packages) {
        for (const [path, pkg] of Object.entries(packages)) {
            if (!path || path === '') {
                continue;
            }
            const name = path.replace(/^node_modules\//, '');
            if (name && pkg.version) {
                result.set(name, pkg.version);
            }
        }
    }
    return result;
}
/** Read the first found lockfile and return resolved name→version map. */
export async function readLockfileVersions(projectPath) {
    const candidates = [
        ['pnpm-lock.yaml', parsePnpmLock],
        ['package-lock.json', parsePackageLock],
    ];
    for (const [file, parser] of candidates) {
        try {
            const content = await readFile(join(projectPath, file), 'utf-8');
            return parser(content);
        }
        catch {
            // try next
        }
    }
    return new Map();
}
// ---------------------------------------------------------------------------
// Runtime version detection
// ---------------------------------------------------------------------------
/** Detect runtime versions from .nvmrc, Dockerfile, go.mod, etc. */
export async function detectRuntimeVersions(projectPath) {
    const results = [];
    // .nvmrc
    try {
        const nvmrc = (await readFile(join(projectPath, '.nvmrc'), 'utf-8')).trim();
        if (nvmrc) {
            results.push({
                runtime: 'node',
                currentVersion: nvmrc,
                latestStable: 'unknown',
                statusCode: 'up_to_date',
                source: '.nvmrc',
            });
        }
    }
    catch {
        // optional file
    }
    // Dockerfile FROM node:X
    try {
        const dockerfile = await readFile(join(projectPath, 'Dockerfile'), 'utf-8');
        const fromMatch = /FROM\s+node:(\d+[\d.]*)/i.exec(dockerfile);
        if (fromMatch?.[1]) {
            results.push({
                runtime: 'node',
                currentVersion: fromMatch[1],
                latestStable: 'unknown',
                statusCode: 'up_to_date',
                source: 'Dockerfile',
            });
        }
    }
    catch {
        // optional
    }
    // go.mod
    try {
        const goMod = await readFile(join(projectPath, 'go.mod'), 'utf-8');
        const goMatch = /^go\s+(\d+\.\d+)/m.exec(goMod);
        if (goMatch?.[1]) {
            results.push({
                runtime: 'go',
                currentVersion: goMatch[1],
                latestStable: 'unknown',
                statusCode: 'up_to_date',
                source: 'go.mod',
            });
        }
    }
    catch {
        // optional
    }
    return results;
}
// ---------------------------------------------------------------------------
// Main entry point
// ---------------------------------------------------------------------------
/** Read all manifest data for a project path. */
export async function readManifest(projectPath) {
    const ecosystem = await detectEcosystem(projectPath);
    let directDependencies = {};
    if (ecosystem === 'nodejs') {
        try {
            const content = await readFile(join(projectPath, 'package.json'), 'utf-8');
            directDependencies = parseNodeDependencies(content);
        }
        catch {
            directDependencies = {};
        }
    }
    else if (ecosystem === 'python') {
        try {
            const content = await readFile(join(projectPath, 'requirements.txt'), 'utf-8');
            for (const line of content.split('\n')) {
                const trimmed = line.trim();
                if (!trimmed || trimmed.startsWith('#')) {
                    continue;
                }
                const eqMatch = /^([A-Za-z0-9_.-]+)==(.+)$/.exec(trimmed);
                const approxMatch = /^([A-Za-z0-9_.-]+)~?>=?(.+)$/.exec(trimmed);
                const m = eqMatch ?? approxMatch;
                if (m?.[1] && m[2]) {
                    directDependencies[m[1]] = m[2];
                }
            }
        }
        catch {
            directDependencies = {};
        }
    }
    const lockfileVersions = await readLockfileVersions(projectPath);
    const runtimeVersions = await detectRuntimeVersions(projectPath);
    return { ecosystem, directDependencies, lockfileVersions, runtimeVersions };
}
// Re-export glob usage for tests
export { glob };
//# sourceMappingURL=manifest-reader.js.map