/* eslint-disable max-lines */
/* eslint-disable func-style */
/* eslint-disable function-paren-newline */
/* eslint-disable max-statements */
/* eslint-disable no-param-reassign */
/* eslint-disable complexity */
/* eslint-disable max-params */
/* eslint-disable max-depth */
/* eslint-disable prefer-destructuring */
/* eslint-disable prefer-arrow-callback */
/* eslint-disable arrow-body-style */
/* eslint-disable init-declarations */

// This is a heavily stripped down/reformatted version of https://github.com/npm/node-semver/blob/v5.5.1/semver.js
// Originally licensed under ISC (https://github.com/npm/node-semver/blob/v5.5.1/LICENSE)
// Copyright (c) Isaac Z. Schlueter and Contributors

const MAX_LENGTH = 256;
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;

// Max safe segment length for coercion.
const MAX_SAFE_COMPONENT_LENGTH = 16;

// The actual regexps go on re
/**
 * @type {RegExp[]}
 */
const re = [];

/**
 * @type {string[]}
 */
const src = [];
const NUMERICIDENTIFIER = 0;
const NUMERICIDENTIFIERLOOSE = 1;
const NONNUMERICIDENTIFIER = 2;
const MAINVERSION = 3;
const MAINVERSIONLOOSE = 4;
const PRERELEASEIDENTIFIER = 5;
const PRERELEASEIDENTIFIERLOOSE = 6;
const PRERELEASE = 7;
const PRERELEASELOOSE = 8;
const BUILDIDENTIFIER = 9;
const BUILD = 10;
const FULL = 11;
const LOOSE = 12;
const GTLT = 13;
const XRANGEIDENTIFIERLOOSE = 14;
const XRANGEIDENTIFIER = 15;
const XRANGEPLAIN = 16;
const XRANGEPLAINLOOSE = 17;
const XRANGE = 18;
const XRANGELOOSE = 19;
const COERCE = 20;
const LONETILDE = 21;
const TILDETRIM = 22;
const TILDE = 23;
const TILDELOOSE = 24;
const LONECARET = 25;
const CARET = 26;
const CARETLOOSE = 27;
const CARETTRIM = 28;
const COMPARATORLOOSE = 29;
const COMPARATOR = 30;
const COMPARATORTRIM = 31;
const HYPHENRANGE = 32;
const HYPHENRANGELOOSE = 33;
const STAR = 34;

// The following Regular Expressions can be used for tokenizing, validating, and parsing SemVer version strings.

// ## Numeric Identifier
// A single `0`, or a non-zero digit followed by zero or more digits.
/* eslint-disable operator-linebreak */
src[NUMERICIDENTIFIER] = "0|[1-9]\\d*";
src[NUMERICIDENTIFIERLOOSE] = "[0-9]+";

// ## Non-numeric Identifier
// Zero or more digits, followed by a letter or hyphen, and then zero or more letters, digits, or hyphens.
src[NONNUMERICIDENTIFIER] = "\\d*[a-zA-Z-][a-zA-Z0-9-]*";

// ## Main Version
// Three dot-separated numeric identifiers.
src[MAINVERSION] = `(${src[NUMERICIDENTIFIER]})\\.(${src[NUMERICIDENTIFIER]})\\.(${src[NUMERICIDENTIFIER]})`;
src[
    MAINVERSIONLOOSE
] = `(${src[NUMERICIDENTIFIERLOOSE]})\\.(${src[NUMERICIDENTIFIERLOOSE]})\\.(${src[NUMERICIDENTIFIERLOOSE]})`;

// ## Pre-release Version Identifier
// A numeric identifier, or a non-numeric identifier.
src[PRERELEASEIDENTIFIER] = `(?:${src[NUMERICIDENTIFIER]}|${src[NONNUMERICIDENTIFIER]})`;
src[PRERELEASEIDENTIFIERLOOSE] = `(?:${src[NUMERICIDENTIFIERLOOSE]}|${src[NONNUMERICIDENTIFIER]})`;

// ## Pre-release Version
// Hyphen, followed by one or more dot-separated pre-release version identifiers.
src[PRERELEASE] = `(?:-(${src[PRERELEASEIDENTIFIER]}(?:\\.${src[PRERELEASEIDENTIFIER]})*))`;
src[PRERELEASELOOSE] = `(?:-?(${src[PRERELEASEIDENTIFIERLOOSE]}(?:\\.${src[PRERELEASEIDENTIFIERLOOSE]})*))`;

// ## Build Metadata Identifier
// Any combination of digits, letters, or hyphens.
src[BUILDIDENTIFIER] = "[0-9A-Za-z-]+";

// ## Build Metadata
// Plus sign, followed by one or more period-separated build metadata identifiers.
src[BUILD] = `(?:\\+(${src[BUILDIDENTIFIER]}(?:\\.${src[BUILDIDENTIFIER]})*))`;

// ## Full Version String
// A main version, followed optionally by a pre-release version and build metadata.

// Note that the only major, minor, patch, and pre-release sections of the version string are capturing groups.
// The build metadata is not a capturing group, because it should not ever be used in version comparison.
const FULLPLAIN = `v?${src[MAINVERSION]}${src[PRERELEASE]}?${src[BUILD]}?`;
src[FULL] = `^${FULLPLAIN}$`;

// Like full, but allows v1.2.3 and =1.2.3, which people do sometimes.
// Also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty common in the npm registry.
const LOOSEPLAIN = `[v=\\s]*${src[MAINVERSIONLOOSE]}${src[PRERELEASELOOSE]}?${src[BUILD]}?`;
src[LOOSE] = `^${LOOSEPLAIN}$`;
src[GTLT] = "((?:<|>)?=?)";

// Something like "2.*" or "1.2.x".
// Note that "x.x" is a valid xRange identifer, meaning "any version"
// Only the first item is strictly required.
src[XRANGEIDENTIFIERLOOSE] = `${src[NUMERICIDENTIFIERLOOSE]}|x|X|\\*`;
src[XRANGEIDENTIFIER] = `${src[NUMERICIDENTIFIER]}|x|X|\\*`;

/* eslint-disable-next-line max-len */
src[
    XRANGEPLAIN
] = `[v=\\s]*(${src[XRANGEIDENTIFIER]})(?:\\.(${src[XRANGEIDENTIFIER]})(?:\\.(${src[XRANGEIDENTIFIER]})(?:${src[PRERELEASE]})?${src[BUILD]}?)?)?`;

/* eslint-disable-next-line max-len */
src[XRANGEPLAINLOOSE] =
  `[v=\\s]*(${src[XRANGEIDENTIFIERLOOSE]})(?:\\.(${src[XRANGEIDENTIFIERLOOSE]})` +
  `(?:\\.(${src[XRANGEIDENTIFIERLOOSE]})(?:${src[PRERELEASELOOSE]})?${src[BUILD]}?)?)?`;

src[XRANGE] = `^${src[GTLT]}\\s*${src[XRANGEPLAIN]}$`;
src[XRANGELOOSE] = `^${src[GTLT]}\\s*${src[XRANGEPLAINLOOSE]}$`;

// Coercion.
// Extract anything that could conceivably be a part of a valid semver
/* eslint-disable-next-line max-len */
src[
    COERCE
] = `(?:^|[^\\d])(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}})(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?(?:$|[^\\d])`;

// Tilde ranges.
// Meaning is "reasonably at or greater than"
src[LONETILDE] = "(?:~>?)";

src[TILDETRIM] = `(\\s*)${src[LONETILDE]}\\s+`;
re[TILDETRIM] = new RegExp(src[TILDETRIM], "g");
const tildeTrimReplace = "$1~";

src[TILDE] = `^${src[LONETILDE]}${src[XRANGEPLAIN]}$`;
src[TILDELOOSE] = `^${src[LONETILDE]}${src[XRANGEPLAINLOOSE]}$`;

// Caret ranges.
// Meaning is "at least and backwards compatible with"
src[LONECARET] = "(?:\\^)";
src[CARETTRIM] = `(\\s*)${src[LONECARET]}\\s+`;
re[CARETTRIM] = new RegExp(src[CARETTRIM], "g");
const caretTrimReplace = "$1^";
src[CARET] = `^${src[LONECARET]}${src[XRANGEPLAIN]}$`;
src[CARETLOOSE] = `^${src[LONECARET]}${src[XRANGEPLAINLOOSE]}$`;

// A simple gt/lt/eq thing, or just "" to indicate "any version"
src[COMPARATORLOOSE] = `^${src[GTLT]}\\s*(${LOOSEPLAIN})$|^$`;

src[COMPARATOR] = `^${src[GTLT]}\\s*(${FULLPLAIN})$|^$`;

// An expression to strip any whitespace between the gtlt and the thing it modifies, so that `> 1.2.3` ==> `>1.2.3`
src[COMPARATORTRIM] = `(\\s*)${src[GTLT]}\\s*(${LOOSEPLAIN}|${src[XRANGEPLAIN]})`;

// This one has to use the /g flag
re[COMPARATORTRIM] = new RegExp(src[COMPARATORTRIM], "g");
const comparatorTrimReplace = "$1$2$3";

// Something like `1.2.3 - 1.2.4`
// Note that these all use the loose form, because they'll be checked against either the strict or loose comparator form later.
src[HYPHENRANGE] = `^\\s*(${src[XRANGEPLAIN]})\\s+-\\s+(${src[XRANGEPLAIN]})\\s*$`;

src[HYPHENRANGELOOSE] = `^\\s*(${src[XRANGEPLAINLOOSE]})\\s+-\\s+(${src[XRANGEPLAINLOOSE]})\\s*$`;

// Star ranges basically just allow anything at all.
src[STAR] = "(<|>)?=?\\s*\\*";
/* eslint-enable operator-linebreak */

// Compile to actual regexp objects.
// All are flag-free, unless they were created above with a flag.
for (let idx = 0; idx <= STAR; idx++) {
    if (!re[idx]) {
        re[idx] = new RegExp(src[idx]);
    }
}

const ANY = {};
const isX = (id) => !id || id.toLowerCase() === "x" || id === "*";

function compareIdentifiers(left, right) {
    const numeric = /^[0-9]+$/;
    const leftIsNumeric = numeric.test(left);
    const rightIsNumeric = numeric.test(right);
    if (leftIsNumeric && !rightIsNumeric) {
        return -1;
    }

    if (rightIsNumeric && !leftIsNumeric) {
        return 1;
    }

    if (leftIsNumeric && rightIsNumeric) {
        left = Number(left);
        right = Number(right);
    }

    if (left < right) {
        return -1;
    }

    if (left > right) {
        return 1;
    }

    return 0;
}

// This function is passed to string.replace(re[HYPHENRANGE])
// M, m, patch, prerelease, build
// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5
// 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do
// 1.2 - 3.4 => >=1.2.0 <3.5.0
function hyphenReplace($0, from, fM, fm, fp, fpr, fb, to, tM, tm, tp, tpr) {
    if (isX(fM)) {
        from = "";
    } else if (isX(fm)) {
        from = `>=${fM}.0.0`;
    } else if (isX(fp)) {
        from = `>=${fM}.${fm}.0`;
    } else {
        from = `>=${from}`;
    }

    if (isX(tM)) {
        to = "";
    } else if (isX(tm)) {
        to = `<${Number(tM) + 1}.0.0`;
    } else if (isX(tp)) {
        to = `<${tM}.${Number(tm) + 1}.0`;
    } else if (tpr) {
        to = `<=${tM}.${tm}.${tp}-${tpr}`;
    } else {
        to = `<=${to}`;
    }

    return `${from} ${to}`.trim();
}

function replaceTilde(comp, loose) {
    const regex = loose ? re[TILDELOOSE] : re[TILDE];

    return comp.replace(regex, function (match, major, minor, patch, prerelease) {
        let ret;

        if (isX(major)) {
            ret = "";
        } else if (isX(minor)) {
            ret = `>=${major}.0.0 <${Number(major) + 1}.0.0`;
        } else if (isX(patch)) {
            // ~1.2 == >=1.2.0 <1.3.0
            ret = `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0`;
        } else if (prerelease) {
            if (prerelease.charAt(0) !== "-") {
                prerelease = `-${prerelease}`;
            }
            ret = `>=${major}.${minor}.${patch}${prerelease} <${major}.${Number(minor) + 1}.0`;
        } else {
            // ~1.2.3 == >=1.2.3 <1.3.0
            ret = `>=${major}.${minor}.${patch} <${major}.${Number(minor) + 1}.0`;
        }

        return ret;
    });
}

// ~, ~> --> * (any, kinda silly)
// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0
// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0
// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0
// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0
function replaceTildes(comp, loose) {
    return comp
        .trim()
        .split(/\s+/)
        .map((comp1) => replaceTilde(comp1, loose))
        .join(" ");
}

function replaceCaret(comp, loose) {
    const regex = loose ? re[CARETLOOSE] : re[CARET];

    return comp.replace(regex, function (match, major, minor, patch, prerelease) {
        let ret;

        if (isX(major)) {
            ret = "";
        } else if (isX(minor)) {
            ret = `>=${major}.0.0 <${Number(major) + 1}.0.0`;
        } else if (isX(patch)) {
            if (major === "0") {
                ret = `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0`;
            } else {
                ret = `>=${major}.${minor}.0 <${Number(major) + 1}.0.0`;
            }
        } else if (prerelease) {
            if (prerelease.charAt(0) !== "-") {
                prerelease = `-${prerelease}`;
            }
            if (major === "0") {
                if (minor === "0") {
                    ret = `>=${major}.${minor}.${patch}${prerelease} <${major}.${minor}.${Number(patch) + 1}`;
                } else {
                    ret = `>=${major}.${minor}.${patch}${prerelease} <${major}.${Number(minor) + 1}.0`;
                }
            } else {
                ret = `>=${major}.${minor}.${patch}${prerelease} <${Number(major) + 1}.0.0`;
            }
        } else if (major === "0") {
            if (minor === "0") {
                ret = `>=${major}.${minor}.${patch} <${major}.${minor}.${Number(patch) + 1}`;
            } else {
                ret = `>=${major}.${minor}.${patch} <${major}.${Number(minor) + 1}.0`;
            }
        } else {
            ret = `>=${major}.${minor}.${patch} <${Number(major) + 1}.0.0`;
        }

        return ret;
    });
}

// ^ --> * (any, kinda silly)
// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0
// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0
// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0
// ^1.2.3 --> >=1.2.3 <2.0.0
// ^1.2.0 --> >=1.2.0 <2.0.0
function replaceCarets(comp, loose) {
    return comp
        .trim()
        .split(/\s+/)
        .map((comp1) => replaceCaret(comp1, loose))
        .join(" ");
}

function replaceXRange(comp, loose) {
    comp = comp.trim();
    const regex = loose ? re[XRANGELOOSE] : re[XRANGE];

    return comp.replace(regex, function (ret, operator, major, minor, patch) {
        const xM = isX(major);
        const xm = xM || isX(minor);
        const xp = xm || isX(patch);
        const anyX = xp;

        if (operator === "=" && anyX) {
            operator = "";
        }

        if (xM) {
            if (operator === ">" || operator === "<") {
                // Nothing is allowed
                ret = "<0.0.0";
            } else {
                // Nothing is forbidden
                ret = "*";
            }
        } else if (operator && anyX) {
            // Replace X with 0
            if (xm) {
                minor = 0;
            }
            if (xp) {
                patch = 0;
            }

            if (operator === ">") {
                // >1 => >=2.0.0
                // >1.2 => >=1.3.0
                // >1.2.3 => >= 1.2.4
                operator = ">=";
                if (xm) {
                    major = Number(major) + 1;
                    minor = 0;
                    patch = 0;
                } else if (xp) {
                    minor = Number(minor) + 1;
                    patch = 0;
                }
            } else if (operator === "<=") {
                // <=0.7.x is actually <0.8.0, since any 0.7.x should pass. Similarly, <=7.x is actually <8.0.0, etc.
                operator = "<";
                if (xm) {
                    major = Number(major) + 1;
                } else {
                    minor = Number(minor) + 1;
                }
            }

            ret = `${operator}${major}.${minor}.${patch}`;
        } else if (xm) {
            ret = `>=${major}.0.0 <${Number(major) + 1}.0.0`;
        } else if (xp) {
            ret = `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0`;
        }

        return ret;
    });
}

function replaceXRanges(comp, loose) {
    return comp
        .split(/\s+/)
        .map((comp1) => replaceXRange(comp1, loose))
        .join(" ");
}

// Because * is AND-ed with everything else in the comparator, and '' means "any version", just remove the *s entirely.
function replaceStars(comp) {
    // Looseness is ignored here.  star is always as loose as it gets!
    return comp.trim().replace(re[STAR], "");
}

// Comprised of xranges, tildes, stars, and gtlt's at this point.
// Already replaced the hyphen ranges turn into a set of JUST comparators.
function parseComparator(comp, loose) {
    comp = replaceCarets(comp, loose);
    comp = replaceTildes(comp, loose);
    comp = replaceXRanges(comp, loose);
    comp = replaceStars(comp, loose);

    return comp;
}

class SemVer {

    /**
   * A semantic version.
   * @param {string} version The version.
   * @param {boolean} loose If this is a loose representation of a version.
   * @returns {SemVer} a new instance.
   */
    constructor(version, loose) {
        if (version instanceof SemVer) {
            if (version.loose === loose) {
                return version;
            }
            version = version.version;
        } else if (typeof version !== "string") {
            throw new TypeError(`Invalid Version: ${version}`);
        }
        if (version.length > MAX_LENGTH) {
            throw new TypeError(`version is longer than ${MAX_LENGTH} characters`);
        }
        if (!(this instanceof SemVer)) {
            return new SemVer(version, loose);
        }
        this.loose = loose;
        const matches = version.trim().match(loose ? re[LOOSE] : re[FULL]);
        if (!matches) {
            throw new TypeError(`Invalid Version: ${version}`);
        }
        this.raw = version;
        // These are actually numbers
        this.major = Number(matches[1]);
        this.minor = Number(matches[2]);
        this.patch = Number(matches[3]);
        if (this.major > MAX_SAFE_INTEGER || this.major < 0) {
            throw new TypeError("Invalid major version");
        }
        if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) {
            throw new TypeError("Invalid minor version");
        }
        if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) {
            throw new TypeError("Invalid patch version");
        }
        // Numberify any prerelease numeric ids
        if (matches[4]) {
            this.prerelease = matches[4].split(".").map((id) => {
                if ((/^[0-9]+$/).test(id)) {
                    const num = Number(id);
                    if (num >= 0 && num < MAX_SAFE_INTEGER) {
                        return num;
                    }
                }

                return id;
            });
        } else {
            this.prerelease = [];
        }
        this.build = matches[5] ? matches[5].split(".") : [];
        this.format();
    }

    format() {
        this.version = `${this.major}.${this.minor}.${this.patch}`;
        if (this.prerelease.length) {
            this.version += `-${this.prerelease.join(".")}`;
        }

        return this.version;
    }

    toString() {
        return this.version;
    }

    /**
   * Comares the current instance against another instance.
   * @param {SemVer} other The SemVer to comare to.
   * @returns {0|1|-1} A comparable value for sorting.
   */
    compare(other) {
        return this.compareMain(other) || this.comparePre(other);
    }

    compareMain(other) {
        if (!(other instanceof SemVer)) {
            other = new SemVer(other, this.loose);
        }

        return (
            compareIdentifiers(this.major, other.major) || compareIdentifiers(this.minor, other.minor) || compareIdentifiers(this.patch, other.patch)
        );
    }

    comparePre(other) {
        if (!(other instanceof SemVer)) {
            other = new SemVer(other, this.loose);
        }
        // NOT having a prerelease is > having one
        if (this.prerelease.length && !other.prerelease.length) {
            return -1;
        } else if (!this.prerelease.length && other.prerelease.length) {
            return 1;
        } else if (!this.prerelease.length && !other.prerelease.length) {
            return 0;
        }
        let idx = 0;
        do {
            const thisPrelease = this.prerelease[idx];
            const otherPrelease = other.prerelease[idx];
            const thisPreleaseIsUndefined = typeof thisPrelease === "undefined";
            const otherPreleaseIsUndefined = typeof otherPrelease === "undefined";
            if (thisPreleaseIsUndefined && otherPreleaseIsUndefined) {
                return 0;
            } else if (otherPreleaseIsUndefined) {
                return 1;
            } else if (thisPreleaseIsUndefined) {
                return -1;
            } else if (thisPrelease === otherPrelease) {
                continue;
            } else {
                return compareIdentifiers(thisPrelease, otherPrelease);
            }
        } while ((idx += 1) > 0);

        // Should not hit this point, but assume equal ranking.
        return 0;
    }
}

const compare = (leftVersion, rightVersion, loose) => new SemVer(leftVersion, loose).compare(new SemVer(rightVersion, loose));
const gt = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) > 0;
const lt = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) < 0;
const eq = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) === 0;
const neq = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) !== 0;
const gte = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) >= 0;
const lte = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) <= 0;

function cmp(left, op, right, loose) {
    let ret;
    switch (op) {
        case "===":
            if (typeof left === "object") {
                left = left.version;
            }
            if (typeof right === "object") {
                right = right.version;
            }
            ret = left === right;
            break;
        case "!==":
            if (typeof left === "object") {
                left = left.version;
            }
            if (typeof right === "object") {
                right = right.version;
            }
            ret = left !== right;
            break;
        case "":
        case "=":
        case "==":
            ret = eq(left, right, loose);
            break;
        case "!=":
            ret = neq(left, right, loose);
            break;
        case ">":
            ret = gt(left, right, loose);
            break;
        case ">=":
            ret = gte(left, right, loose);
            break;
        case "<":
            ret = lt(left, right, loose);
            break;
        case "<=":
            ret = lte(left, right, loose);
            break;
        default:
            throw new TypeError(`Invalid operator: ${op}`);
    }

    return ret;
}

function testSet(set, version) {
    for (let idx = 0; idx < set.length; idx++) {
        if (!set[idx].test(version)) {
            return false;
        }
    }

    if (version.prerelease.length) {
    // Find the set of versions that are allowed to have prereleases
    // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0
    // That should allow `1.2.3-pr.2` to pass.
    // However, `1.2.4-alpha.notready` should NOT be allowed, even though it's within the range set by the comparators.
        for (let idx = 0; idx < set.length; idx++) {
            if (set[idx].semver !== ANY) {
                if (set[idx].semver.prerelease.length > 0) {
                    const allowed = set[idx].semver;
                    if (allowed.major === version.major && allowed.minor === version.minor && allowed.patch === version.patch) {
                        return true;
                    }
                }
            }
        }

        // Version has a -pre, but it's not one of the ones we like.
        return false;
    }

    return true;
}
class Comparator {
    constructor(comp, loose) {
        if (comp instanceof Comparator) {
            if (comp.loose === loose) {
                return comp;
            }
            comp = comp.value;
        }
        if (!(this instanceof Comparator)) {
            return new Comparator(comp, loose);
        }
        this.loose = loose;
        this.parse(comp);
        if (this.semver === ANY) {
            this.value = "";
        } else {
            this.value = this.operator + this.semver.version;
        }
    }

    parse(comp) {
        const regex = this.loose ? re[COMPARATORLOOSE] : re[COMPARATOR];
        const matches = comp.match(regex);
        if (!matches) {
            throw new TypeError(`Invalid comparator: ${comp}`);
        }
        this.operator = matches[1];
        if (this.operator === "=") {
            this.operator = "";
        }
        // If it literally is just '>' or '' then allow anything.
        if (matches[2]) {
            this.semver = new SemVer(matches[2], this.loose);
        } else {
            this.semver = ANY;
        }
    }

    toString() {
        return this.value;
    }

    test(version) {
        if (this.semver === ANY) {
            return true;
        }
        if (typeof version === "string") {
            version = new SemVer(version, this.loose);
        }

        return cmp(version, this.operator, this.semver, this.loose);
    }
}

/**
 * A range object.
 * @param {Range|Comparator|string} range the value to parse to a Range.
 * @param {any} loose whether the range is explicit or a loose value.
 * @returns {Range} the Range instace.
 */
class Range {
    constructor(range, loose) {
        if (range instanceof Range) {
            if (range.loose === loose) {
                return range;
            }

            return new Range(range.raw, loose);
        }
        if (range instanceof Comparator) {
            return new Range(range.value, loose);
        }
        if (!(this instanceof Range)) {
            return new Range(range, loose);
        }
        this.loose = loose;
        // First, split based on boolean or ||
        /**
     * @type {string}
     */
        this.raw = range;
        // Throw out any that are not relevant for whatever reason
        const hasLength = (item) => item.length;
        this.set = this.raw
            .split(/\s*\|\|\s*/)
            .map(function (range1) {
                return this.parseRange(range1.trim());
            }, this)
            .filter(hasLength);
        if (!this.set.length) {
            throw new TypeError(`Invalid SemVer Range: ${range}`);
        }
        this.format();
    }

    format() {
        this.range = this.set
            .map((comps) => comps.join(" ").trim())
            .join("||")
            .trim();

        return this.range;
    }

    toString() {
        return this.range;
    }

    parseRange(range) {
        const loose = this.loose;
        range = range.trim();
        // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4`
        const hr = loose ? re[HYPHENRANGELOOSE] : re[HYPHENRANGE];
        range = range.replace(hr, hyphenReplace);
        // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5`
        range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace);
        // `~ 1.2.3` => `~1.2.3`
        range = range.replace(re[TILDETRIM], tildeTrimReplace);
        // `^ 1.2.3` => `^1.2.3`
        range = range.replace(re[CARETTRIM], caretTrimReplace);
        // Normalize spaces
        range = range.split(/\s+/).join(" ");
        // At this point, the range is completely trimmed and ready to be split into comparators.
        const compRe = loose ? re[COMPARATORLOOSE] : re[COMPARATOR];
        let set = range
            .split(" ")
            .map((comp) => parseComparator(comp, loose))
            .join(" ")
            .split(/\s+/);
        if (loose) {
            // In loose mode, throw out any that are not valid comparators
            set = set.filter((comp) => Boolean(comp.match(compRe)));
        }
        set = set.map((comp) => new Comparator(comp, loose));

        return set;
    }

    // If ANY of the sets match ALL of its comparators, then pass
    test(version) {
        if (!version) {
            return false;
        }
        if (typeof version === "string") {
            version = new SemVer(version, this.loose);
        }
        for (let idx = 0; idx < this.set.length; idx++) {
            if (testSet(this.set[idx], version)) {
                return true;
            }
        }

        return false;
    }
}

/**
 * Checks if the provided version can satisfy the provided range.
 * @param {string} version The specific version.
 * @param {string} range The range expression.
 * @param {any} loose If the range is a loose expression.
 * @returns {boolean} Whether the versions successfully satisfies the range.
 */
function satisfies(version, range, loose) {
    try {
        const rangeObj = new Range(range, loose);

        return rangeObj.test(version);
    } catch (er) {
        return false;
    }
}

module.exports.satisfies = satisfies;