// Planu — Manifest file readers for each ecosystem (SPEC-054)
// All file I/O via node:fs/promises — no live API calls.
import { readFile } from 'node:fs/promises';
/** Extract major version from a semver-like string (e.g. "^18.2.0" → 18). */
export function extractMajor(versionStr) {
    // Strip leading range operators and whitespace
    const cleaned = versionStr.replace(/^[\^~>=<\s*]+/, '').trim();
    const parts = cleaned.split('.');
    const first = parts[0];
    if (!first) {
        return null;
    }
    const major = parseInt(first, 10);
    return isNaN(major) ? null : major;
}
/** Read package.json and return name→installedMajor for all dependencies. */
export async function readNpmManifest(projectPath) {
    const result = new Map();
    try {
        const raw = await readFile(`${projectPath}/package.json`, 'utf-8');
        const parsed = JSON.parse(raw);
        if (parsed === null || typeof parsed !== 'object') {
            return result;
        }
        const pkg = parsed;
        const depGroups = [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies];
        for (const group of depGroups) {
            if (group === null || typeof group !== 'object') {
                continue;
            }
            for (const [name, version] of Object.entries(group)) {
                if (typeof version !== 'string') {
                    continue;
                }
                const major = extractMajor(version);
                if (major !== null && !result.has(name)) {
                    result.set(name, major);
                }
            }
        }
    }
    catch {
        // File missing or invalid — return empty map
    }
    return result;
}
/** Parse a TOML-like key=value block into a record. Handles [section] markers. */
function parseTomlDependencies(content) {
    const deps = {};
    let inDeps = false;
    for (const line of content.split('\n')) {
        const trimmed = line.trim();
        if (trimmed.startsWith('[')) {
            inDeps = /\[(?:dependencies|dev-dependencies|build-dependencies)\]/i.test(trimmed);
            continue;
        }
        if (!inDeps) {
            continue;
        }
        const eqIdx = trimmed.indexOf('=');
        if (eqIdx === -1) {
            continue;
        }
        const name = trimmed.slice(0, eqIdx).trim();
        const value = trimmed
            .slice(eqIdx + 1)
            .trim()
            .replace(/['"{}]/g, '');
        // Extract "version" key from inline table e.g. { version = "1.0", features = [...] }
        const versionMatch = /version\s*=\s*["']?([^,}"'\s]+)/.exec(value);
        /* v8 ignore next */
        const finalVersion = versionMatch ? (versionMatch[1] ?? '') : value;
        if (name && finalVersion) {
            deps[name] = finalVersion;
        }
    }
    return deps;
}
/** Read Cargo.toml and return name→installedMajor. */
export async function readCargoManifest(projectPath) {
    const result = new Map();
    try {
        const raw = await readFile(`${projectPath}/Cargo.toml`, 'utf-8');
        const deps = parseTomlDependencies(raw);
        for (const [name, version] of Object.entries(deps)) {
            const major = extractMajor(version);
            if (major !== null) {
                result.set(name, major);
            }
        }
    }
    catch {
        // File missing or invalid
    }
    return result;
}
/** Read go.mod and return module→installedMajor (only v2+ explicit majors). */
export async function readGoManifest(projectPath) {
    const result = new Map();
    try {
        const raw = await readFile(`${projectPath}/go.mod`, 'utf-8');
        for (const line of raw.split('\n')) {
            const trimmed = line.trim();
            if (!trimmed.startsWith('require') && !/^\S+\s+v\d/.test(trimmed)) {
                continue;
            }
            // Match lines like: github.com/gin-gonic/gin v1.9.0
            const match = /(\S+)\s+v(\d+)\./.exec(trimmed);
            if (match) {
                /* v8 ignore start */
                const moduleName = match[1] ?? '';
                const majorStr = match[2] ?? '0';
                /* v8 ignore stop */
                const major = parseInt(majorStr, 10);
                /* v8 ignore next 3 */
                if (moduleName && !isNaN(major)) {
                    result.set(moduleName, major);
                }
            }
        }
    }
    catch {
        // File missing or invalid
    }
    return result;
}
/** Parse pyproject.toml dependency lines into name→version entries. */
function parsePyprojectDeps(raw) {
    const deps = {};
    let inDeps = false;
    for (const line of raw.split('\n')) {
        const trimmed = line.trim();
        if (trimmed.startsWith('[')) {
            inDeps =
                /\[(?:project\.dependencies|tool\.poetry\.dependencies|tool\.pdm\.dependencies)\]/i.test(trimmed);
            continue;
        }
        if (!inDeps) {
            continue;
        }
        // Handle "django = '>=4.2,<5'" and "django>=4.2"
        const matchEq = /^([\w-]+)\s*=\s*["']([^"']+)["']/.exec(trimmed);
        const matchInline = /^([\w-]+)\s*([><=!^~]+\s*[\d.]+)/.exec(trimmed);
        /* v8 ignore start */
        const [name, versionExpr] = matchEq
            ? [matchEq[1] ?? '', matchEq[2] ?? '']
            : matchInline
                ? [matchInline[1] ?? '', matchInline[2] ?? '']
                : ['', ''];
        /* v8 ignore stop */
        if (!name) {
            continue;
        }
        deps[name.toLowerCase()] = versionExpr;
    }
    return deps;
}
/** Read pyproject.toml or requirements.txt and return package→installedMajor. */
export async function readPypiManifest(projectPath) {
    const result = new Map();
    // Try pyproject.toml first
    try {
        const raw = await readFile(`${projectPath}/pyproject.toml`, 'utf-8');
        const deps = parsePyprojectDeps(raw);
        for (const [name, versionExpr] of Object.entries(deps)) {
            const major = extractMajor(versionExpr);
            if (major !== null && !result.has(name)) {
                result.set(name, major);
            }
        }
        if (result.size > 0) {
            return result;
        }
    }
    catch {
        // File missing — fall through to requirements.txt
    }
    // Fall back to requirements.txt
    try {
        const raw = await readFile(`${projectPath}/requirements.txt`, 'utf-8');
        for (const line of raw.split('\n')) {
            const trimmed = line.trim();
            if (!trimmed || trimmed.startsWith('#')) {
                continue;
            }
            // Match "django>=4.2.0" or "django==4.2.0"
            const match = /^([\w-]+)\s*[><=!^~]+\s*([\d.]+)/.exec(trimmed);
            if (match) {
                /* v8 ignore start */
                const name = match[1] ?? '';
                const ver = match[2] ?? '';
                /* v8 ignore stop */
                const major = extractMajor(ver);
                if (name && major !== null && !result.has(name.toLowerCase())) {
                    result.set(name.toLowerCase(), major);
                }
            }
        }
    }
    catch {
        // Neither file exists
    }
    return result;
}
/** Read pom.xml / build.gradle and return artifactId→installedMajor. */
export async function readMavenManifest(projectPath) {
    const result = new Map();
    // Try pom.xml
    try {
        const raw = await readFile(`${projectPath}/pom.xml`, 'utf-8');
        // Extract <artifactId> + <version> pairs from <dependency> blocks
        const depBlocks = raw.matchAll(/<dependency>([\s\S]*?)<\/dependency>/g);
        for (const block of depBlocks) {
            /* v8 ignore start */
            const content = block[1] ?? '';
            /* v8 ignore stop */
            const artMatch = /<artifactId>([\w-]+)<\/artifactId>/.exec(content);
            const verMatch = /<version>([\d.]+)/.exec(content);
            if (artMatch && verMatch) {
                /* v8 ignore start */
                const name = artMatch[1] ?? '';
                const ver = verMatch[1] ?? '';
                /* v8 ignore stop */
                const major = extractMajor(ver);
                /* v8 ignore next 3 */
                if (name && major !== null) {
                    result.set(name, major);
                }
            }
        }
        if (result.size > 0) {
            return result;
        }
    }
    catch {
        // Try build.gradle
    }
    // Try build.gradle
    try {
        const raw = await readFile(`${projectPath}/build.gradle`, 'utf-8');
        // Match lines like: implementation 'org.springframework.boot:spring-boot-starter:3.0.0'
        const matches = raw.matchAll(/['"]([\w.-]+):([\w.-]+):([\d.]+)['"]/g);
        for (const match of matches) {
            /* v8 ignore start */
            const name = match[2] ?? '';
            const ver = match[3] ?? '';
            /* v8 ignore stop */
            const major = extractMajor(ver);
            if (name && major !== null && !result.has(name)) {
                result.set(name, major);
            }
        }
    }
    catch {
        // Neither file exists
    }
    return result;
}
/** Read composer.json and return package→installedMajor. */
export async function readComposerManifest(projectPath) {
    const result = new Map();
    try {
        const raw = await readFile(`${projectPath}/composer.json`, 'utf-8');
        const parsed = JSON.parse(raw);
        if (parsed === null || typeof parsed !== 'object') {
            return result;
        }
        const pkg = parsed;
        const depGroups = [pkg.require, pkg['require-dev']];
        for (const group of depGroups) {
            if (group === null || typeof group !== 'object') {
                continue;
            }
            for (const [name, version] of Object.entries(group)) {
                if (typeof version !== 'string' || name === 'php') {
                    continue;
                }
                const major = extractMajor(version);
                if (major !== null && !result.has(name)) {
                    result.set(name, major);
                }
            }
        }
    }
    catch {
        // File missing or invalid
    }
    return result;
}
//# sourceMappingURL=manifest-readers.js.map