/**
 * Parse a version string like "1.2.3", "1.2.3-rc.1", "^1.2.3" into parts.
 * Strips leading range operators (^, ~, >=, <=, >, <) before parsing.
 */
function parseSemver(raw) {
    const stripped = raw.trim().replace(/^[^0-9]*/, '');
    const match = /^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9._-]+))?/.exec(stripped);
    if (!match) {
        return null;
    }
    return {
        major: parseInt(match[1] ?? '0', 10),
        minor: parseInt(match[2] ?? '0', 10),
        patch: parseInt(match[3] ?? '0', 10),
        preRelease: match[4] ?? '',
    };
}
/** Compare two parsed versions. Returns negative if a < b, 0 if equal, positive if a > b. */
function compareTuples(a, b) {
    if (a.major !== b.major) {
        return a.major - b.major;
    }
    if (a.minor !== b.minor) {
        return a.minor - b.minor;
    }
    if (a.patch !== b.patch) {
        return a.patch - b.patch;
    }
    // Pre-release handling: a version without pre-release is greater than one with
    if (a.preRelease === '' && b.preRelease !== '') {
        return 1;
    }
    if (a.preRelease !== '' && b.preRelease === '') {
        return -1;
    }
    // Both have pre-release: compare lexicographically
    return a.preRelease < b.preRelease ? -1 : a.preRelease > b.preRelease ? 1 : 0;
}
/** Evaluate a single comparator like ">=1.0.0", "<2.0.0", "1.2.3" against installed. */
function evaluateComparator(installed, comparator) {
    const c = comparator.trim();
    if (c === '' || c === '*') {
        return true;
    }
    if (c.startsWith('>=')) {
        const bound = parseSemver(c.slice(2));
        return bound !== null && compareTuples(installed, bound) >= 0;
    }
    if (c.startsWith('<=')) {
        const bound = parseSemver(c.slice(2));
        return bound !== null && compareTuples(installed, bound) <= 0;
    }
    if (c.startsWith('>')) {
        const bound = parseSemver(c.slice(1));
        return bound !== null && compareTuples(installed, bound) > 0;
    }
    if (c.startsWith('<')) {
        const bound = parseSemver(c.slice(1));
        return bound !== null && compareTuples(installed, bound) < 0;
    }
    if (c.startsWith('~')) {
        // ~1.2.3 := >=1.2.3 <1.3.0
        const base = parseSemver(c.slice(1));
        if (!base) {
            return false;
        }
        const upper = {
            major: base.major,
            minor: base.minor + 1,
            patch: 0,
            preRelease: '',
        };
        return compareTuples(installed, base) >= 0 && compareTuples(installed, upper) < 0;
    }
    if (c.startsWith('^')) {
        // ^1.2.3 := >=1.2.3 <2.0.0, ^0.2.3 := >=0.2.3 <0.3.0, ^0.0.3 := >=0.0.3 <0.0.4
        const base = parseSemver(c.slice(1));
        if (!base) {
            return false;
        }
        let upper;
        if (base.major > 0) {
            upper = { major: base.major + 1, minor: 0, patch: 0, preRelease: '' };
        }
        else if (base.minor > 0) {
            upper = { major: 0, minor: base.minor + 1, patch: 0, preRelease: '' };
        }
        else {
            upper = { major: 0, minor: 0, patch: base.patch + 1, preRelease: '' };
        }
        return compareTuples(installed, base) >= 0 && compareTuples(installed, upper) < 0;
    }
    // Exact version match
    const exact = parseSemver(c);
    if (!exact) {
        return false;
    }
    return compareTuples(installed, exact) === 0;
}
/**
 * Returns true if installedVersion falls within vulnerableRange.
 * vulnerableRange supports:
 *   - exact: "1.2.3"
 *   - caret: "^1.2.3"
 *   - tilde: "~1.2.3"
 *   - comparators: ">=1.0.0", "<=2.0.0", ">1.0.0", "<2.0.0"
 *   - AND ranges (space-separated): ">=1.0.0 <2.0.0"
 *   - OR ranges (|| separated): ">=1.0.0 <1.5.0 || >=2.0.0 <2.5.0"
 *   - pre-release suffixes: "1.2.3-rc.1", ">=1.0.0-alpha"
 */
export function isVersionVulnerable(installedVersion, vulnerableRange) {
    const installed = parseSemver(installedVersion);
    if (!installed) {
        return false;
    }
    // Split by || for OR groups
    const orGroups = vulnerableRange.split('||').map((g) => g.trim());
    for (const group of orGroups) {
        // Each group is an AND of space-separated comparators
        const comparators = group.split(/\s+/).filter((c) => c !== '');
        const allMatch = comparators.every((c) => evaluateComparator(installed, c));
        if (allMatch) {
            return true;
        }
    }
    return false;
}
/**
 * Returns true if installedVersion is strictly below threshold.
 * Equivalent to isVersionVulnerable(installed, '<threshold').
 * Used as backward-compatible replacement for the old isVersionBelow().
 */
export function isVersionBelow(installedVersion, threshold) {
    const installed = parseSemver(installedVersion);
    const bound = parseSemver(threshold);
    if (!installed || !bound) {
        return false;
    }
    return compareTuples(installed, bound) < 0;
}
//# sourceMappingURL=semver-utils.js.map