"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.testTypeVersion = exports.testTypeExVer = exports.ExtendedVersion = exports.Version = exports.VersionRange = void 0;
const deep_equality_data_structures_1 = require("deep-equality-data-structures");
const P = __importStar(require("./exver"));
function compareVersionRangePoints(a, b) {
    let up = a.upstream.compareForSort(b.upstream);
    if (up != 0) {
        return up;
    }
    let down = a.upstream.compareForSort(b.upstream);
    if (down != 0) {
        return down;
    }
    if (a.side < b.side) {
        return -1;
    }
    else if (a.side > b.side) {
        return 1;
    }
    else {
        return 0;
    }
}
function adjacentVersionRangePoints(a, b) {
    let up = a.upstream.compareForSort(b.upstream);
    if (up != 0) {
        return false;
    }
    let down = a.upstream.compareForSort(b.upstream);
    if (down != 0) {
        return false;
    }
    return a.side == -1 && b.side == 1;
}
function flavorAnd(a, b) {
    if (a.type == 'Flavor') {
        if (b.type == 'Flavor') {
            if (a.flavor == b.flavor) {
                return a;
            }
            else {
                return null;
            }
        }
        else {
            if (b.flavors.has(a.flavor)) {
                return null;
            }
            else {
                return a;
            }
        }
    }
    else {
        if (b.type == 'Flavor') {
            if (a.flavors.has(b.flavor)) {
                return null;
            }
            else {
                return b;
            }
        }
        else {
            // TODO: use Set.union if targeting esnext or later
            return {
                type: 'FlavorNot',
                flavors: new Set([...a.flavors, ...b.flavors]),
            };
        }
    }
}
/**
 * A truth table for version numbers. This is easiest to picture as a number line, cut up into
 * ranges of versions between version points.
 */
class VersionRangeTable {
    constructor(points, values) {
        this.points = points;
        this.values = values;
    }
    static zip(a, b, func) {
        let c = new VersionRangeTable([], []);
        let i = 0;
        let j = 0;
        while (true) {
            let next = func(a.values[i], b.values[j]);
            if (c.values.length > 0 && c.values[c.values.length - 1] == next) {
                // collapse automatically
                c.points.pop();
            }
            else {
                c.values.push(next);
            }
            // which point do we step over?
            if (i == a.points.length) {
                if (j == b.points.length) {
                    // just added the last segment, no point to jump over
                    return c;
                }
                else {
                    // i has reach the end, step over j
                    c.points.push(b.points[j]);
                    j += 1;
                }
            }
            else {
                if (j == b.points.length) {
                    // j has reached the end, step over i
                    c.points.push(a.points[i]);
                    i += 1;
                }
                else {
                    // depends on which of the next two points is lower
                    switch (compareVersionRangePoints(a.points[i], b.points[j])) {
                        case -1:
                            // i is the lower point
                            c.points.push(a.points[i]);
                            i += 1;
                            break;
                        case 1:
                            // j is the lower point
                            c.points.push(b.points[j]);
                            j += 1;
                            break;
                        default:
                            // step over both
                            c.points.push(a.points[i]);
                            i += 1;
                            j += 1;
                            break;
                    }
                }
            }
        }
    }
    /**
     * Creates a version table which is `true` for the given flavor, and `false` for any other flavor.
     */
    static eqFlavor(flavor) {
        return new deep_equality_data_structures_1.DeepMap([
            [
                { type: 'Flavor', flavor },
                new VersionRangeTable([], [true]),
            ],
            // make sure the truth table is exhaustive, or `not` will not work properly.
            [
                { type: 'FlavorNot', flavors: new Set([flavor]) },
                new VersionRangeTable([], [false]),
            ],
        ]);
    }
    /**
     * Creates a version table with exactly two ranges (to the left and right of the given point) and with `false` for any other flavor.
     * This is easiest to understand by looking at `VersionRange.tables`.
     */
    static cmpPoint(flavor, point, left, right) {
        return new deep_equality_data_structures_1.DeepMap([
            [
                { type: 'Flavor', flavor },
                new VersionRangeTable([point], [left, right]),
            ],
            // make sure the truth table is exhaustive, or `not` will not work properly.
            [
                { type: 'FlavorNot', flavors: new Set([flavor]) },
                new VersionRangeTable([], [false]),
            ],
        ]);
    }
    /**
     * Helper for `cmpPoint`.
     */
    static cmp(version, side, left, right) {
        return VersionRangeTable.cmpPoint(version.flavor, { upstream: version.upstream, downstream: version.downstream, side }, left, right);
    }
    static not(tables) {
        if (tables === true || tables === false) {
            return !tables;
        }
        // because tables are always exhaustive, we can simply invert each range
        for (let [f, t] of tables) {
            for (let i = 0; i < t.values.length; i++) {
                t.values[i] = !t.values[i];
            }
        }
        return tables;
    }
    static and(a_tables, b_tables) {
        if (a_tables === true) {
            return b_tables;
        }
        if (b_tables === true) {
            return a_tables;
        }
        if (a_tables === false || b_tables == false) {
            return false;
        }
        let c_tables = true;
        for (let [f_a, a] of a_tables) {
            for (let [f_b, b] of b_tables) {
                let flavor = flavorAnd(f_a, f_b);
                if (flavor == null) {
                    continue;
                }
                let c = VersionRangeTable.zip(a, b, (a, b) => a && b);
                if (c_tables === true) {
                    c_tables = new deep_equality_data_structures_1.DeepMap();
                }
                let prev_c = c_tables.get(flavor);
                if (prev_c == null) {
                    c_tables.set(flavor, c);
                }
                else {
                    c_tables.set(flavor, VersionRangeTable.zip(c, prev_c, (a, b) => a || b));
                }
            }
        }
        return c_tables;
    }
    static or(...in_tables) {
        let out_tables = false;
        for (let tables of in_tables) {
            if (tables === false) {
                continue;
            }
            if (tables === true) {
                return true;
            }
            if (out_tables === false) {
                out_tables = new deep_equality_data_structures_1.DeepMap();
            }
            for (let [flavor, table] of tables) {
                let prev = out_tables.get(flavor);
                if (prev == null) {
                    out_tables.set(flavor, table);
                }
                else {
                    out_tables.set(flavor, VersionRangeTable.zip(table, prev, (a, b) => a || b));
                }
            }
        }
        return out_tables;
    }
    /**
     * If this is true for all versions or false for all versions, returen that value. Otherwise return null.
     */
    static collapse(tables) {
        if (tables === true || tables === false) {
            return tables;
        }
        else {
            let found = null;
            for (let table of tables.values()) {
                for (let x of table.values) {
                    if (found == null) {
                        found = x;
                    }
                    else if (found != x) {
                        return null;
                    }
                }
            }
            return found;
        }
    }
    /**
     * Expresses this truth table as a series of version range operators.
     * https://en.wikipedia.org/wiki/Canonical_normal_form#Minterms
     */
    static minterms(tables) {
        let collapse = VersionRangeTable.collapse(tables);
        if (tables === true || collapse === true) {
            return VersionRange.any();
        }
        if (tables == false || collapse === false) {
            return VersionRange.none();
        }
        let sum_terms = [];
        for (let [flavor, table] of tables) {
            let cmp_flavor = null;
            if (flavor.type == 'Flavor') {
                cmp_flavor = flavor.flavor;
            }
            for (let i = 0; i < table.values.length; i++) {
                let term = [];
                if (!table.values[i]) {
                    continue;
                }
                if (flavor.type == 'FlavorNot') {
                    for (let not_flavor of flavor.flavors) {
                        term.push(VersionRange.flavor(not_flavor).not());
                    }
                }
                let p = null;
                let q = null;
                if (i > 0) {
                    p = table.points[i - 1];
                }
                if (i < table.points.length) {
                    q = table.points[i];
                }
                if (p != null && q != null && adjacentVersionRangePoints(p, q)) {
                    term.push(VersionRange.anchor('=', new ExtendedVersion(cmp_flavor, p.upstream, p.downstream)));
                }
                else {
                    if (p != null && p.side < 0) {
                        term.push(VersionRange.anchor('>=', new ExtendedVersion(cmp_flavor, p.upstream, p.downstream)));
                    }
                    if (p != null && p.side >= 0) {
                        term.push(VersionRange.anchor('>', new ExtendedVersion(cmp_flavor, p.upstream, p.downstream)));
                    }
                    if (q != null && q.side < 0) {
                        term.push(VersionRange.anchor('<', new ExtendedVersion(cmp_flavor, q.upstream, q.downstream)));
                    }
                    if (q != null && q.side >= 0) {
                        term.push(VersionRange.anchor('<=', new ExtendedVersion(cmp_flavor, q.upstream, q.downstream)));
                    }
                }
                if (term.length == 0) {
                    term.push(VersionRange.flavor(cmp_flavor));
                }
                sum_terms.push(VersionRange.and(...term));
            }
        }
        return VersionRange.or(...sum_terms);
    }
}
/**
 * Represents a parsed version range expression used to match against {@link Version} or {@link ExtendedVersion} values.
 *
 * Version ranges support standard comparison operators (`=`, `>`, `<`, `>=`, `<=`, `!=`),
 * caret (`^`) and tilde (`~`) ranges, boolean logic (`&&`, `||`, `!`), and flavor matching (`#flavor`).
 *
 * @example
 * ```ts
 * const range = VersionRange.parse(">=1.0.0:0 && <2.0.0:0")
 * const version = ExtendedVersion.parse("1.5.0:0")
 * console.log(range.satisfiedBy(version)) // true
 *
 * // Combine ranges with boolean logic
 * const combined = VersionRange.and(
 *   VersionRange.parse(">=1.0:0"),
 *   VersionRange.parse("<3.0:0"),
 * )
 *
 * // Match a specific flavor
 * const flavored = VersionRange.parse("#bitcoin")
 * ```
 */
class VersionRange {
    constructor(atom) {
        this.atom = atom;
    }
    toStringParens(parent) {
        let needs = true;
        switch (this.atom.type) {
            case 'And':
            case 'Or':
                needs = parent != this.atom.type;
                break;
            case 'Anchor':
            case 'Any':
            case 'None':
                needs = parent == 'Not';
                break;
            case 'Not':
            case 'Flavor':
                needs = false;
                break;
        }
        if (needs) {
            return '(' + this.toString() + ')';
        }
        else {
            return this.toString();
        }
    }
    /** Serializes this version range back to its canonical string representation. */
    toString() {
        switch (this.atom.type) {
            case 'Anchor':
                return `${this.atom.operator}${this.atom.version}`;
            case 'And':
                return `${this.atom.left.toStringParens(this.atom.type)} && ${this.atom.right.toStringParens(this.atom.type)}`;
            case 'Or':
                return `${this.atom.left.toStringParens(this.atom.type)} || ${this.atom.right.toStringParens(this.atom.type)}`;
            case 'Not':
                return `!${this.atom.value.toStringParens(this.atom.type)}`;
            case 'Flavor':
                return this.atom.flavor == null ? `#` : `#${this.atom.flavor}`;
            case 'Any':
                return '*';
            case 'None':
                return '!';
        }
    }
    static parseAtom(atom) {
        switch (atom.type) {
            case 'Not':
                return new VersionRange({
                    type: 'Not',
                    value: VersionRange.parseAtom(atom.value),
                });
            case 'Parens':
                return VersionRange.parseRange(atom.expr);
            case 'Anchor':
                return new VersionRange({
                    type: 'Anchor',
                    operator: atom.operator || '^',
                    version: new ExtendedVersion(atom.version.flavor, new Version(atom.version.upstream.number, atom.version.upstream.prerelease), new Version(atom.version.downstream.number, atom.version.downstream.prerelease)),
                });
            case 'Flavor':
                return VersionRange.flavor(atom.flavor);
            default:
                return new VersionRange(atom);
        }
    }
    static parseRange(range) {
        let result = VersionRange.parseAtom(range[0]);
        for (const next of range[1]) {
            switch (next[1]?.[0]) {
                case '||':
                    result = new VersionRange({
                        type: 'Or',
                        left: result,
                        right: VersionRange.parseAtom(next[2]),
                    });
                    break;
                case '&&':
                default:
                    result = new VersionRange({
                        type: 'And',
                        left: result,
                        right: VersionRange.parseAtom(next[2]),
                    });
                    break;
            }
        }
        return result;
    }
    /**
     * Parses a version range string into a `VersionRange`.
     *
     * @param range - A version range expression, e.g. `">=1.0.0:0 && <2.0.0:0"`, `"^1.2:0"`, `"*"`
     * @returns The parsed `VersionRange`
     * @throws If the string is not a valid version range expression
     */
    static parse(range) {
        return VersionRange.parseRange(P.parse(range, { startRule: 'VersionRange' }));
    }
    /**
     * Creates a version range from a comparison operator and an {@link ExtendedVersion}.
     *
     * @param operator - One of `"="`, `">"`, `"<"`, `">="`, `"<="`, `"!="`, `"^"`, `"~"`
     * @param version - The version to compare against
     */
    static anchor(operator, version) {
        return new VersionRange({ type: 'Anchor', operator, version });
    }
    /**
     * Creates a version range that matches only versions with the specified flavor.
     *
     * @param flavor - The flavor string to match, or `null` for the default (unflavored) variant
     */
    static flavor(flavor) {
        return new VersionRange({ type: 'Flavor', flavor });
    }
    /**
     * Parses a legacy "emver" format version range string.
     *
     * @param range - A version range in the legacy emver format
     * @returns The parsed `VersionRange`
     */
    static parseEmver(range) {
        return VersionRange.parseRange(P.parse(range, { startRule: 'EmverVersionRange' }));
    }
    /** Returns the intersection of this range with another (logical AND). */
    and(right) {
        return new VersionRange({ type: 'And', left: this, right });
    }
    /** Returns the union of this range with another (logical OR). */
    or(right) {
        return new VersionRange({ type: 'Or', left: this, right });
    }
    /** Returns the negation of this range (logical NOT). */
    not() {
        return new VersionRange({ type: 'Not', value: this });
    }
    /**
     * Returns the logical AND (intersection) of multiple version ranges.
     * Short-circuits on `none()` and skips `any()`.
     */
    static and(...xs) {
        let y = VersionRange.any();
        for (let x of xs) {
            if (x.atom.type == 'Any') {
                continue;
            }
            if (x.atom.type == 'None') {
                return x;
            }
            if (y.atom.type == 'Any') {
                y = x;
            }
            else {
                y = new VersionRange({ type: 'And', left: y, right: x });
            }
        }
        return y;
    }
    /**
     * Returns the logical OR (union) of multiple version ranges.
     * Short-circuits on `any()` and skips `none()`.
     */
    static or(...xs) {
        let y = VersionRange.none();
        for (let x of xs) {
            if (x.atom.type == 'None') {
                continue;
            }
            if (x.atom.type == 'Any') {
                return x;
            }
            if (y.atom.type == 'None') {
                y = x;
            }
            else {
                y = new VersionRange({ type: 'Or', left: y, right: x });
            }
        }
        return y;
    }
    /** Returns a version range that matches all versions (wildcard `*`). */
    static any() {
        return new VersionRange({ type: 'Any' });
    }
    /** Returns a version range that matches no versions (`!`). */
    static none() {
        return new VersionRange({ type: 'None' });
    }
    /**
     * Returns `true` if the given version satisfies this range.
     *
     * @param version - A {@link Version} or {@link ExtendedVersion} to test
     */
    satisfiedBy(version) {
        return version.satisfies(this);
    }
    tables() {
        switch (this.atom.type) {
            case 'Anchor':
                switch (this.atom.operator) {
                    case '=':
                        // `=1.2.3` is equivalent to `>=1.2.3 && <=1.2.4 && #flavor`
                        return VersionRangeTable.and(VersionRangeTable.cmp(this.atom.version, -1, false, true), VersionRangeTable.cmp(this.atom.version, 1, true, false));
                    case '>':
                        return VersionRangeTable.cmp(this.atom.version, 1, false, true);
                    case '<':
                        return VersionRangeTable.cmp(this.atom.version, -1, true, false);
                    case '>=':
                        return VersionRangeTable.cmp(this.atom.version, -1, false, true);
                    case '<=':
                        return VersionRangeTable.cmp(this.atom.version, 1, true, false);
                    case '!=':
                        // `!=1.2.3` is equivalent to `!(>=1.2.3 && <=1.2.3 && #flavor)`
                        // **not** equivalent to `(<1.2.3 || >1.2.3) && #flavor`
                        return VersionRangeTable.not(VersionRangeTable.and(VersionRangeTable.cmp(this.atom.version, -1, false, true), VersionRangeTable.cmp(this.atom.version, 1, true, false)));
                    case '^':
                        // `^1.2.3` is equivalent to `>=1.2.3 && <2.0.0 && #flavor`
                        return VersionRangeTable.and(VersionRangeTable.cmp(this.atom.version, -1, false, true), VersionRangeTable.cmp(this.atom.version.incrementMajor(), -1, true, false));
                    case '~':
                        // `~1.2.3` is equivalent to `>=1.2.3 && <1.3.0 && #flavor`
                        return VersionRangeTable.and(VersionRangeTable.cmp(this.atom.version, -1, false, true), VersionRangeTable.cmp(this.atom.version.incrementMinor(), -1, true, false));
                }
            case 'Flavor':
                return VersionRangeTable.eqFlavor(this.atom.flavor);
            case 'Not':
                return VersionRangeTable.not(this.atom.value.tables());
            case 'And':
                return VersionRangeTable.and(this.atom.left.tables(), this.atom.right.tables());
            case 'Or':
                return VersionRangeTable.or(this.atom.left.tables(), this.atom.right.tables());
            case 'Any':
                return true;
            case 'None':
                return false;
        }
    }
    /** Returns `true` if any version exists that could satisfy this range. */
    satisfiable() {
        return VersionRangeTable.collapse(this.tables()) !== false;
    }
    /** Returns `true` if this range and `other` share at least one satisfying version. */
    intersects(other) {
        return VersionRange.and(this, other).satisfiable();
    }
    /**
     * Returns a canonical (simplified) form of this range using minterm expansion.
     * Useful for normalizing complex boolean expressions into a minimal representation.
     */
    normalize() {
        return VersionRangeTable.minterms(this.tables());
    }
}
exports.VersionRange = VersionRange;
/**
 * Represents a semantic version number with numeric segments and optional prerelease identifiers.
 *
 * Follows semver precedence rules: numeric segments are compared left-to-right,
 * and a version with prerelease identifiers has lower precedence than the same version without.
 *
 * @example
 * ```ts
 * const v = Version.parse("1.2.3")
 * console.log(v.toString())             // "1.2.3"
 * console.log(v.compare(Version.parse("1.3.0"))) // "less"
 *
 * const pre = Version.parse("2.0.0-beta.1")
 * console.log(pre.compare(Version.parse("2.0.0"))) // "less" (prerelease < release)
 * ```
 */
class Version {
    constructor(
    /** The numeric version segments (e.g. `[1, 2, 3]` for `"1.2.3"`). */
    number, 
    /** Optional prerelease identifiers (e.g. `["beta", 1]` for `"-beta.1"`). */
    prerelease) {
        this.number = number;
        this.prerelease = prerelease;
    }
    /** Serializes this version to its string form (e.g. `"1.2.3"` or `"1.0.0-beta.1"`). */
    toString() {
        return `${this.number.join('.')}${this.prerelease.length > 0 ? `-${this.prerelease.join('.')}` : ''}`;
    }
    /**
     * Compares this version against another using semver precedence rules.
     *
     * @param other - The version to compare against
     * @returns `'greater'`, `'equal'`, or `'less'`
     */
    compare(other) {
        const numLen = Math.max(this.number.length, other.number.length);
        for (let i = 0; i < numLen; i++) {
            if ((this.number[i] || 0) > (other.number[i] || 0)) {
                return 'greater';
            }
            else if ((this.number[i] || 0) < (other.number[i] || 0)) {
                return 'less';
            }
        }
        if (this.prerelease.length === 0 && other.prerelease.length !== 0) {
            return 'greater';
        }
        else if (this.prerelease.length !== 0 && other.prerelease.length === 0) {
            return 'less';
        }
        const prereleaseLen = Math.max(this.prerelease.length, other.prerelease.length);
        for (let i = 0; i < prereleaseLen; i++) {
            if (typeof this.prerelease[i] === typeof other.prerelease[i]) {
                if (this.prerelease[i] > other.prerelease[i]) {
                    return 'greater';
                }
                else if (this.prerelease[i] < other.prerelease[i]) {
                    return 'less';
                }
            }
            else {
                switch (`${typeof this.prerelease[1]}:${typeof other.prerelease[i]}`) {
                    case 'number:string':
                        return 'less';
                    case 'string:number':
                        return 'greater';
                    case 'number:undefined':
                    case 'string:undefined':
                        return 'greater';
                    case 'undefined:number':
                    case 'undefined:string':
                        return 'less';
                }
            }
        }
        return 'equal';
    }
    /**
     * Compares two versions, returning a numeric value suitable for use with `Array.sort()`.
     *
     * @returns `-1` if less, `0` if equal, `1` if greater
     */
    compareForSort(other) {
        switch (this.compare(other)) {
            case 'greater':
                return 1;
            case 'equal':
                return 0;
            case 'less':
                return -1;
        }
    }
    /**
     * Parses a version string into a `Version` instance.
     *
     * @param version - A semver-compatible string, e.g. `"1.2.3"` or `"1.0.0-beta.1"`
     * @throws If the string is not a valid version
     */
    static parse(version) {
        const parsed = P.parse(version, { startRule: 'Version' });
        return new Version(parsed.number, parsed.prerelease);
    }
    /**
     * Returns `true` if this version satisfies the given {@link VersionRange}.
     * Internally treats this as an unflavored {@link ExtendedVersion} with downstream `0`.
     */
    satisfies(versionRange) {
        return new ExtendedVersion(null, this, new Version([0], [])).satisfies(versionRange);
    }
}
exports.Version = Version;
/**
 * Represents an extended version with an optional flavor, an upstream version, and a downstream version.
 *
 * The format is `#flavor:upstream:downstream` (e.g. `#bitcoin:1.2.3:0`) or `upstream:downstream`
 * for unflavored versions. Flavors allow multiple variants of a package to coexist.
 *
 * - **flavor**: An optional string identifier for the variant (e.g. `"bitcoin"`, `"litecoin"`)
 * - **upstream**: The version of the upstream software being packaged
 * - **downstream**: The version of the StartOS packaging itself
 *
 * Versions with different flavors are incomparable (comparison returns `null`).
 *
 * @example
 * ```ts
 * const v = ExtendedVersion.parse("#bitcoin:1.2.3:0")
 * console.log(v.flavor)      // "bitcoin"
 * console.log(v.upstream)    // Version { number: [1, 2, 3] }
 * console.log(v.downstream)  // Version { number: [0] }
 * console.log(v.toString())  // "#bitcoin:1.2.3:0"
 *
 * const range = VersionRange.parse(">=1.0.0:0")
 * console.log(v.satisfies(range)) // true
 * ```
 */
class ExtendedVersion {
    constructor(
    /** The flavor identifier (e.g. `"bitcoin"`), or `null` for unflavored versions. */
    flavor, 
    /** The upstream software version. */
    upstream, 
    /** The downstream packaging version. */
    downstream) {
        this.flavor = flavor;
        this.upstream = upstream;
        this.downstream = downstream;
    }
    /** Serializes this extended version to its string form (e.g. `"#bitcoin:1.2.3:0"` or `"1.0.0:1"`). */
    toString() {
        return `${this.flavor ? `#${this.flavor}:` : ''}${this.upstream.toString()}:${this.downstream.toString()}`;
    }
    /**
     * Compares this extended version against another.
     *
     * @returns `'greater'`, `'equal'`, `'less'`, or `null` if the flavors differ (incomparable)
     */
    compare(other) {
        if (this.flavor !== other.flavor) {
            return null;
        }
        const upstreamCmp = this.upstream.compare(other.upstream);
        if (upstreamCmp !== 'equal') {
            return upstreamCmp;
        }
        return this.downstream.compare(other.downstream);
    }
    /**
     * Lexicographic comparison — compares flavors alphabetically first, then versions.
     * Unlike {@link compare}, this never returns `null`: different flavors are ordered alphabetically.
     */
    compareLexicographic(other) {
        if ((this.flavor || '') > (other.flavor || '')) {
            return 'greater';
        }
        else if ((this.flavor || '') > (other.flavor || '')) {
            return 'less';
        }
        else {
            return this.compare(other);
        }
    }
    /**
     * Returns a numeric comparison result suitable for use with `Array.sort()`.
     * Uses lexicographic ordering (flavors sorted alphabetically, then by version).
     */
    compareForSort(other) {
        switch (this.compareLexicographic(other)) {
            case 'greater':
                return 1;
            case 'equal':
                return 0;
            case 'less':
                return -1;
        }
    }
    /** Returns `true` if this version is strictly greater than `other`. Returns `false` if flavors differ. */
    greaterThan(other) {
        return this.compare(other) === 'greater';
    }
    /** Returns `true` if this version is greater than or equal to `other`. Returns `false` if flavors differ. */
    greaterThanOrEqual(other) {
        return ['greater', 'equal'].includes(this.compare(other));
    }
    /** Returns `true` if this version equals `other` (same flavor, upstream, and downstream). */
    equals(other) {
        return this.compare(other) === 'equal';
    }
    /** Returns `true` if this version is strictly less than `other`. Returns `false` if flavors differ. */
    lessThan(other) {
        return this.compare(other) === 'less';
    }
    /** Returns `true` if this version is less than or equal to `other`. Returns `false` if flavors differ. */
    lessThanOrEqual(other) {
        return ['less', 'equal'].includes(this.compare(other));
    }
    /**
     * Parses an extended version string into an `ExtendedVersion`.
     *
     * @param extendedVersion - A string like `"1.2.3:0"` or `"#bitcoin:1.0.0:0"`
     * @throws If the string is not a valid extended version
     */
    static parse(extendedVersion) {
        const parsed = P.parse(extendedVersion, { startRule: 'ExtendedVersion' });
        return new ExtendedVersion(parsed.flavor || null, new Version(parsed.upstream.number, parsed.upstream.prerelease), new Version(parsed.downstream.number, parsed.downstream.prerelease));
    }
    /**
     * Parses a legacy "emver" format extended version string.
     *
     * @param extendedVersion - A version string in the legacy emver format
     * @throws If the string is not a valid emver version (error message includes the input string)
     */
    static parseEmver(extendedVersion) {
        try {
            const parsed = P.parse(extendedVersion, { startRule: 'Emver' });
            return new ExtendedVersion(parsed.flavor || null, new Version(parsed.upstream.number, parsed.upstream.prerelease), new Version(parsed.downstream.number, parsed.downstream.prerelease));
        }
        catch (e) {
            if (e instanceof Error) {
                e.message += ` (${extendedVersion})`;
            }
            throw e;
        }
    }
    /**
     * Returns an ExtendedVersion with the Upstream major version version incremented by 1
     * and sets subsequent digits to zero.
     * If no non-zero upstream digit can be found the last upstream digit will be incremented.
     */
    incrementMajor() {
        const majorIdx = this.upstream.number.findIndex((num) => num !== 0);
        const majorNumber = this.upstream.number.map((num, idx) => {
            if (idx > majorIdx) {
                return 0;
            }
            else if (idx === majorIdx) {
                return num + 1;
            }
            return num;
        });
        const incrementedUpstream = new Version(majorNumber, []);
        const updatedDownstream = new Version([0], []);
        return new ExtendedVersion(this.flavor, incrementedUpstream, updatedDownstream);
    }
    /**
     * Returns an ExtendedVersion with the Upstream minor version version incremented by 1
     * also sets subsequent digits to zero.
     * If no non-zero upstream digit can be found the last digit will be incremented.
     */
    incrementMinor() {
        const majorIdx = this.upstream.number.findIndex((num) => num !== 0);
        let minorIdx = majorIdx === -1 ? majorIdx : majorIdx + 1;
        const majorNumber = this.upstream.number.map((num, idx) => {
            if (idx > minorIdx) {
                return 0;
            }
            else if (idx === minorIdx) {
                return num + 1;
            }
            return num;
        });
        const incrementedUpstream = new Version(majorNumber, []);
        const updatedDownstream = new Version([0], []);
        return new ExtendedVersion(this.flavor, incrementedUpstream, updatedDownstream);
    }
    /**
     * Returns a boolean indicating whether a given version satisfies the VersionRange
     * !( >= 1:1 <= 2:2) || <=#bitcoin:1.2.0-alpha:0
     */
    satisfies(versionRange) {
        switch (versionRange.atom.type) {
            case 'Anchor':
                const otherVersion = versionRange.atom.version;
                switch (versionRange.atom.operator) {
                    case '=':
                        return this.equals(otherVersion);
                    case '>':
                        return this.greaterThan(otherVersion);
                    case '<':
                        return this.lessThan(otherVersion);
                    case '>=':
                        return this.greaterThanOrEqual(otherVersion);
                    case '<=':
                        return this.lessThanOrEqual(otherVersion);
                    case '!=':
                        return !this.equals(otherVersion);
                    case '^':
                        const nextMajor = versionRange.atom.version.incrementMajor();
                        if (this.greaterThanOrEqual(otherVersion) &&
                            this.lessThan(nextMajor)) {
                            return true;
                        }
                        else {
                            return false;
                        }
                    case '~':
                        const nextMinor = versionRange.atom.version.incrementMinor();
                        if (this.greaterThanOrEqual(otherVersion) &&
                            this.lessThan(nextMinor)) {
                            return true;
                        }
                        else {
                            return false;
                        }
                }
            case 'Flavor':
                return versionRange.atom.flavor == this.flavor;
            case 'And':
                return (this.satisfies(versionRange.atom.left) &&
                    this.satisfies(versionRange.atom.right));
            case 'Or':
                return (this.satisfies(versionRange.atom.left) ||
                    this.satisfies(versionRange.atom.right));
            case 'Not':
                return !this.satisfies(versionRange.atom.value);
            case 'Any':
                return true;
            case 'None':
                return false;
        }
    }
}
exports.ExtendedVersion = ExtendedVersion;
/**
 * Compile-time type-checking helper that validates an extended version string literal.
 * If the string is invalid, TypeScript will report a type error at the call site.
 *
 * @example
 * ```ts
 * testTypeExVer("1.2.3:0")         // compiles
 * testTypeExVer("#bitcoin:1.0:0")  // compiles
 * testTypeExVer("invalid")         // type error
 * ```
 */
const testTypeExVer = (t) => t;
exports.testTypeExVer = testTypeExVer;
/**
 * Compile-time type-checking helper that validates a version string literal.
 * If the string is invalid, TypeScript will report a type error at the call site.
 *
 * @example
 * ```ts
 * testTypeVersion("1.2.3")  // compiles
 * testTypeVersion("-3")     // type error
 * ```
 */
const testTypeVersion = (t) => t;
exports.testTypeVersion = testTypeVersion;
function tests() {
    (0, exports.testTypeVersion)('1.2.3');
    (0, exports.testTypeVersion)('1');
    (0, exports.testTypeVersion)('12.34.56');
    (0, exports.testTypeVersion)('1.2-3');
    (0, exports.testTypeVersion)('1-3');
    (0, exports.testTypeVersion)('1-alpha');
    // @ts-expect-error
    (0, exports.testTypeVersion)('-3');
    // @ts-expect-error
    (0, exports.testTypeVersion)('1.2.3:1');
    // @ts-expect-error
    (0, exports.testTypeVersion)('#cat:1:1');
    (0, exports.testTypeExVer)('1.2.3:1.2.3');
    (0, exports.testTypeExVer)('1.2.3.4.5.6.7.8.9.0:1');
    (0, exports.testTypeExVer)('100:1');
    (0, exports.testTypeExVer)('#cat:1:1');
    (0, exports.testTypeExVer)('1.2.3.4.5.6.7.8.9.11.22.33:1');
    (0, exports.testTypeExVer)('1-0:1');
    (0, exports.testTypeExVer)('1-0:1');
    // @ts-expect-error
    (0, exports.testTypeExVer)('1.2-3');
    // @ts-expect-error
    (0, exports.testTypeExVer)('1-3');
    // @ts-expect-error
    (0, exports.testTypeExVer)('1.2.3.4.5.6.7.8.9.0.10:1');
    // @ts-expect-error
    (0, exports.testTypeExVer)('1.-2:1');
    // @ts-expect-error
    (0, exports.testTypeExVer)('1..2.3:3');
}
//# sourceMappingURL=index.js.map