"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.api = exports.supportedRangeStrategies = exports.supportsRanges = exports.urls = exports.displayName = exports.id = void 0;
exports.isValid = isValid;
exports.isVersion = isVersion;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const semver_1 = tslib_1.__importDefault(require("semver"));
const semver_utils_1 = require("semver-utils");
const logger_1 = require("../../../logger");
const regex_1 = require("../../../util/regex");
const npm_1 = require("../npm");
exports.id = 'composer';
exports.displayName = 'Composer';
exports.urls = [
    'https://getcomposer.org/doc/articles/versions.md',
    'https://packagist.org/packages/composer/semver',
    'https://madewithlove.be/tilde-and-caret-constraints/',
    'https://semver.mwl.be',
];
exports.supportsRanges = true;
exports.supportedRangeStrategies = [
    'bump',
    'widen',
    'pin',
    'replace',
    'update-lockfile',
];
function getVersionParts(input) {
    const versionParts = input.split('-');
    if (versionParts.length === 1) {
        return [input, ''];
    }
    return [versionParts[0], '-' + versionParts[1]];
}
function padZeroes(input) {
    const [output, stability] = getVersionParts(input);
    const sections = output.split('.');
    while (sections.length < 3) {
        sections.push('0');
    }
    return sections.join('.') + stability;
}
function convertStabilityModifier(input) {
    // Handle stability modifiers.
    const versionParts = input.split('@');
    if (versionParts.length === 1) {
        return input;
    }
    // 1.0@beta2 to 1.0-beta.2
    const stability = versionParts[1].replace((0, regex_1.regEx)(/(?:^|\s)(beta|alpha|rc)([1-9][0-9]*)(?: |$)/gi), '$1.$2');
    // If there is a stability part, npm semver expects the version
    // to be full
    return padZeroes(versionParts[0]) + '-' + stability;
}
function normalizeVersion(input) {
    let output = input;
    output = output.replace((0, regex_1.regEx)(/(^|>|>=|\^|~)v/i), '$1');
    return convertStabilityModifier(output);
}
/**
 * @param versions Version list in any format, it recognizes the specific patch format x.x.x-pXX
 * @param range Range in composer format
 * @param minMode If true, it will calculate minSatisfyingVersion, if false, it calculates the maxSatisfyingVersion
 * @returns min or max satisfyingVersion from the input
 */
function calculateSatisfyingVersionIntenal(versions, range, minMode) {
    // Because composer -p versions are considered stable, we have to remove the suffix for the npm.XXX functions.
    const versionsMapped = versions.map((x) => {
        return {
            origianl: x,
            cleaned: removeComposerSpecificPatchPart(x),
            npmVariant: composer2npm(removeComposerSpecificPatchPart(x)[0]),
        };
    });
    const npmVersions = versionsMapped.map((x) => x.npmVariant);
    const npmVersion = minMode
        ? npm_1.api.minSatisfyingVersion(npmVersions, composer2npm(range))
        : npm_1.api.getSatisfyingVersion(npmVersions, composer2npm(range));
    if (!npmVersion) {
        return null;
    }
    // After we find the npm versions, we select from them back in the mapping the possible patches.
    const candidates = versionsMapped
        .filter((x) => x.npmVariant === npmVersion)
        .sort((a, b) => (minMode ? 1 : -1) * sortVersions(a.origianl, b.origianl));
    return candidates[0].origianl;
}
/**
 * @param intput Version in any format, it recognizes the specific patch format x.x.x-pXX
 * @returns If input contains the specific patch, it returns the input with removed the patch and true, otherwise it retunrs the same string and false.
 */
function removeComposerSpecificPatchPart(input) {
    // the regex is based on the original from composer implementation https://github.com/composer/semver/blob/fa1ec24f0ab1efe642671ec15c51a3ab879f59bf/src/VersionParser.php#L137
    const pattern = /^v?\d+(\.\d+(\.\d+(\.\d+)?)?)?(?<suffix>-p[1-9]\d*)$/gi;
    const match = pattern.exec(input);
    return match
        ? [input.replace(match.groups.suffix, ''), true]
        : [input, false];
}
function composer2npm(input) {
    return input
        .split((0, regex_1.regEx)(/\s*\|\|?\s*/g))
        .map((part) => {
        const cleanInput = normalizeVersion(part);
        if (npm_1.api.isVersion(cleanInput)) {
            return cleanInput;
        }
        if (npm_1.api.isVersion(padZeroes(cleanInput))) {
            return padZeroes(cleanInput);
        }
        const [versionId, stability] = getVersionParts(cleanInput);
        let output = versionId;
        // ~4 to ^4 and ~4.1 to ^4.1
        output = output.replace((0, regex_1.regEx)(/(?:^|\s)~([1-9][0-9]*(?:\.[0-9]*)?)(?: |$)/g), '^$1');
        // ~0.4 to >=0.4 <1
        output = output.replace((0, regex_1.regEx)(/(?:^|\s)~(0\.[1-9][0-9]*)(?: |$)/g), '>=$1 <1');
        // add extra digits to <8-DEV and <8.0-DEV
        output = output
            .replace((0, regex_1.regEx)(/^(<\d+(\.\d+)?)$/g), '$1.0')
            .replace((0, regex_1.regEx)(/^(<\d+(\.\d+)?)$/g), '$1.0');
        return output + stability;
    })
        .map((part) => part.replace(/([a-z])([0-9])/gi, '$1.$2'))
        .join(' || ');
}
function equals(a, b) {
    return npm_1.api.equals(composer2npm(a), composer2npm(b));
}
function getMajor(version) {
    const semverVersion = semver_1.default.coerce(composer2npm(version));
    return semverVersion ? npm_1.api.getMajor(semverVersion) : null;
}
function getMinor(version) {
    const semverVersion = semver_1.default.coerce(composer2npm(version));
    return semverVersion ? npm_1.api.getMinor(semverVersion) : null;
}
function getPatch(version) {
    const semverVersion = semver_1.default.coerce(composer2npm(version));
    // This returns only the numbers without the optional `-pXX` patch version supported by composer. Fixing that would require a bigger
    // refactoring, because the API supports only numbers.
    return semverVersion ? npm_1.api.getPatch(semverVersion) : null;
}
function isGreaterThan(a, b) {
    return sortVersions(a, b) === 1;
}
function isLessThanRange(version, range) {
    return !!npm_1.api.isLessThanRange?.(composer2npm(version), composer2npm(range));
}
function isSingleVersion(input) {
    return !!input && npm_1.api.isSingleVersion(composer2npm(input));
}
function isStable(version) {
    if (version) {
        // Composer considers patches `-pXX` as stable: https://github.com/composer/semver/blob/fa1ec24f0ab1efe642671ec15c51a3ab879f59bf/src/VersionParser.php#L568 but npm not.
        // In order to be able to use the standard npm.isStable function, we remove the potential patch version for the check.
        const [withoutPatch] = removeComposerSpecificPatchPart(version);
        return npm_1.api.isStable(composer2npm(withoutPatch));
    }
    return false;
}
function isValid(input) {
    return !!input && npm_1.api.isValid(composer2npm(input));
}
function isVersion(input) {
    return !!input && npm_1.api.isVersion(composer2npm(input));
}
function matches(version, range) {
    return npm_1.api.matches(composer2npm(version), composer2npm(range));
}
function getSatisfyingVersion(versions, range) {
    return calculateSatisfyingVersionIntenal(versions, range, false);
}
function minSatisfyingVersion(versions, range) {
    return calculateSatisfyingVersionIntenal(versions, range, true);
}
function subset(subRange, superRange) {
    try {
        return npm_1.api.subset(composer2npm(subRange), composer2npm(superRange));
    }
    catch (err) {
        logger_1.logger.trace({ err }, 'composer.subset error');
        return false;
    }
}
function getNewValue({ currentValue, rangeStrategy, currentVersion, newVersion, }) {
    if (rangeStrategy === 'pin') {
        return newVersion;
    }
    if (rangeStrategy === 'update-lockfile') {
        if (matches(newVersion, currentValue)) {
            return currentValue;
        }
        return getNewValue({
            currentValue,
            rangeStrategy: 'replace',
            currentVersion,
            newVersion,
        });
    }
    const currentMajor = currentVersion ? getMajor(currentVersion) : null;
    const toMajor = getMajor(newVersion);
    const toMinor = getMinor(newVersion);
    let newValue = null;
    if (isVersion(currentValue)) {
        newValue = newVersion;
    }
    else if ((0, regex_1.regEx)(/^[~^](0\.[1-9][0-9]*)$/).test(currentValue)) {
        const operator = currentValue.substring(0, 1);
        // handle ~0.4 case first
        if (toMajor === 0) {
            // TODO: types (#22198)
            newValue = `${operator}0.${toMinor}`;
        }
        else {
            // TODO: types (#22198)
            newValue = `${operator}${toMajor}.0`;
        }
    }
    else if ((0, regex_1.regEx)(/^[~^]([0-9]*)$/).test(currentValue)) {
        // handle ~4 case
        const operator = currentValue.substring(0, 1);
        // TODO: types (#22198)
        newValue = `${operator}${toMajor}`;
    }
    else if (toMajor &&
        (0, regex_1.regEx)(/^[~^]([0-9]*(?:\.[0-9]*)?)$/).test(currentValue)) {
        const operator = currentValue.substring(0, 1);
        if (rangeStrategy === 'bump') {
            newValue = `${operator}${newVersion}`;
        }
        else if ((is_1.default.number(currentMajor) && toMajor > currentMajor) ||
            !toMinor) {
            // handle ~4.1 case
            newValue = `${operator}${toMajor}.0`;
        }
        else {
            newValue = `${operator}${toMajor}.${toMinor}`;
        }
    }
    else if (currentVersion &&
        npm_1.api.isVersion(padZeroes(normalizeVersion(newVersion))) &&
        npm_1.api.isValid(normalizeVersion(currentValue)) &&
        composer2npm(currentValue) === normalizeVersion(currentValue)) {
        newValue = npm_1.api.getNewValue({
            currentValue: normalizeVersion(currentValue),
            rangeStrategy,
            currentVersion: padZeroes(normalizeVersion(currentVersion)),
            newVersion: padZeroes(normalizeVersion(newVersion)),
        });
    }
    if (rangeStrategy === 'widen' && matches(newVersion, currentValue)) {
        newValue = currentValue;
    }
    else {
        const hasOr = currentValue.includes(' || ');
        if (hasOr || rangeStrategy === 'widen') {
            const splitValues = currentValue.split('||');
            const lastValue = splitValues[splitValues.length - 1];
            const replacementValue = getNewValue({
                currentValue: lastValue.trim(),
                rangeStrategy: 'replace',
                currentVersion,
                newVersion,
            });
            if (rangeStrategy === 'replace') {
                newValue = replacementValue;
            }
            else if (replacementValue) {
                const parsedRange = (0, semver_utils_1.parseRange)(replacementValue);
                const element = parsedRange[parsedRange.length - 1];
                if (element.operator?.startsWith('<')) {
                    const splitCurrent = currentValue.split(element.operator);
                    splitCurrent.pop();
                    newValue = splitCurrent.join(element.operator) + replacementValue;
                }
                else {
                    newValue = currentValue + ' || ' + replacementValue;
                }
            }
        }
    }
    if (!newValue) {
        logger_1.logger.warn({ currentValue, rangeStrategy, currentVersion, newVersion }, 'Unsupported composer value');
        newValue = newVersion;
    }
    if (currentValue.split('.')[0].includes('v')) {
        newValue = newValue.replace((0, regex_1.regEx)(/([0-9])/), 'v$1');
    }
    // Preserve original min-stability specifier
    if (currentValue.includes('@')) {
        newValue += '@' + currentValue.split('@')[1];
    }
    return newValue;
}
function sortVersions(a, b) {
    const [aWithoutPatch, aContainsPatch] = removeComposerSpecificPatchPart(a);
    const [bWithoutPatch, bContainsPatch] = removeComposerSpecificPatchPart(b);
    if (aContainsPatch === bContainsPatch) {
        // If both [a and b] contain patch version or both [a and b] do not contain patch version, then npm comparison deliveres correct results
        return npm_1.api.sortVersions(composer2npm(a), composer2npm(b));
    }
    else if (npm_1.api.equals(composer2npm(aWithoutPatch), composer2npm(bWithoutPatch))) {
        // If only one [a or b] contains patch version and the parts without patch versions are equal, then the version with patch is greater (this is the case where npm comparison fails)
        return aContainsPatch ? 1 : -1;
    }
    else {
        // All other cases can be compared correctly by npm
        return npm_1.api.sortVersions(composer2npm(a), composer2npm(b));
    }
}
function isCompatible(version) {
    return isVersion(version);
}
exports.api = {
    equals,
    getMajor,
    getMinor,
    getPatch,
    isCompatible,
    isGreaterThan,
    isLessThanRange,
    isSingleVersion,
    isStable,
    isValid,
    isVersion,
    matches,
    getSatisfyingVersion,
    minSatisfyingVersion,
    getNewValue,
    sortVersions,
    subset,
};
exports.default = exports.api;
//# sourceMappingURL=index.js.map