"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.lookupUpdates = lookupUpdates;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const config_1 = require("../../../../config");
const error_messages_1 = require("../../../../constants/error-messages");
const logger_1 = require("../../../../logger");
const datasource_1 = require("../../../../modules/datasource");
const common_1 = require("../../../../modules/datasource/common");
const manager_1 = require("../../../../modules/manager");
const allVersioning = tslib_1.__importStar(require("../../../../modules/versioning"));
const docker_1 = require("../../../../modules/versioning/docker");
const external_host_error_1 = require("../../../../types/errors/external-host-error");
const assign_keys_1 = require("../../../../util/assign-keys");
const package_rules_1 = require("../../../../util/package-rules");
const regex_1 = require("../../../../util/regex");
const result_1 = require("../../../../util/result");
const bucket_1 = require("./bucket");
const current_1 = require("./current");
const filter_1 = require("./filter");
const filter_checks_1 = require("./filter-checks");
const generate_1 = require("./generate");
const rollback_1 = require("./rollback");
const utils_1 = require("./utils");
function getTimestamp(versions, version, versioning) {
    return versions.find((v) => versioning.isValid(v.version) && versioning.equals(v.version, version))?.releaseTimestamp;
}
async function lookupUpdates(inconfig) {
    let config = { ...inconfig };
    config.versioning ??= (0, common_1.getDefaultVersioning)(config.datasource);
    const versioning = allVersioning.get(config.versioning);
    const unconstrainedValue = !!config.lockedVersion && is_1.default.undefined(config.currentValue);
    let dependency = null;
    const res = {
        versioning: config.versioning,
        updates: [],
        warnings: [],
    };
    try {
        logger_1.logger.trace({
            dependency: config.packageName,
            currentValue: config.currentValue,
        }, 'lookupUpdates');
        if (config.currentValue && !is_1.default.string(config.currentValue)) {
            // If currentValue is not a string, then it's invalid
            if (config.currentValue) {
                logger_1.logger.debug(`Invalid currentValue for ${config.packageName}: ${JSON.stringify(config.currentValue)} (${typeof config.currentValue})`);
            }
            res.skipReason = 'invalid-value';
            return result_1.Result.ok(res);
        }
        if (!(0, datasource_1.isGetPkgReleasesConfig)(config) ||
            !(0, common_1.getDatasourceFor)(config.datasource)) {
            res.skipReason = 'invalid-config';
            return result_1.Result.ok(res);
        }
        let compareValue = config.currentValue;
        if (is_1.default.string(config.currentValue) &&
            is_1.default.string(config.versionCompatibility)) {
            const versionCompatbilityRegEx = (0, regex_1.regEx)(config.versionCompatibility);
            const regexMatch = versionCompatbilityRegEx.exec(config.currentValue);
            if (regexMatch?.groups) {
                logger_1.logger.debug({
                    versionCompatibility: config.versionCompatibility,
                    currentValue: config.currentValue,
                    packageName: config.packageName,
                    groups: regexMatch.groups,
                }, 'version compatibility regex match');
                config.currentCompatibility = regexMatch.groups.compatibility;
                compareValue = regexMatch.groups.version;
            }
            else {
                logger_1.logger.debug({
                    versionCompatibility: config.versionCompatibility,
                    currentValue: config.currentValue,
                    packageName: config.packageName,
                }, 'version compatibility regex mismatch');
            }
        }
        const isValid = is_1.default.string(compareValue) && versioning.isValid(compareValue);
        if (unconstrainedValue || isValid) {
            if (!config.updatePinnedDependencies &&
                // TODO #22198
                versioning.isSingleVersion(compareValue)) {
                res.skipReason = 'is-pinned';
                return result_1.Result.ok(res);
            }
            const { val: releaseResult, err: lookupError } = await (0, datasource_1.getRawPkgReleases)(config)
                .transform((res) => (0, datasource_1.applyDatasourceFilters)(res, config))
                .unwrap();
            if (lookupError instanceof Error) {
                throw lookupError;
            }
            if (lookupError) {
                // If dependency lookup fails then warn and return
                const warning = {
                    topic: config.packageName,
                    message: `Failed to look up ${config.datasource} package ${config.packageName}`,
                };
                logger_1.logger.debug({
                    dependency: config.packageName,
                    packageFile: config.packageFile,
                }, warning.message);
                // TODO: return warnings in own field
                res.warnings.push(warning);
                return result_1.Result.ok(res);
            }
            dependency = releaseResult;
            if (dependency.deprecationMessage) {
                logger_1.logger.debug(`Found deprecationMessage for ${config.datasource} package ${config.packageName}`);
            }
            (0, assign_keys_1.assignKeys)(res, dependency, [
                'deprecationMessage',
                'sourceUrl',
                'registryUrl',
                'sourceDirectory',
                'homepage',
                'changelogUrl',
                'dependencyUrl',
                'lookupName',
                'packageScope',
            ]);
            const latestVersion = dependency.tags?.latest;
            // Filter out any results from datasource that don't comply with our versioning
            let allVersions = dependency.releases.filter((release) => versioning.isVersion(release.version));
            // istanbul ignore if
            if (allVersions.length === 0) {
                const message = `Found no results from datasource that look like a version`;
                logger_1.logger.info({
                    dependency: config.packageName,
                    result: dependency,
                }, message);
                if (!config.currentDigest) {
                    return result_1.Result.ok(res);
                }
            }
            // Reapply package rules in case we missed something from sourceUrl
            config = (0, package_rules_1.applyPackageRules)({ ...config, sourceUrl: res.sourceUrl }, 'source-url');
            if (config.followTag) {
                const taggedVersion = dependency.tags?.[config.followTag];
                if (!taggedVersion) {
                    res.warnings.push({
                        topic: config.packageName,
                        message: `Can't find version with tag ${config.followTag} for ${config.datasource} package ${config.packageName}`,
                    });
                    return result_1.Result.ok(res);
                }
                allVersions = allVersions.filter((v) => v.version === taggedVersion ||
                    (v.version === compareValue &&
                        versioning.isGreaterThan(taggedVersion, compareValue)));
            }
            // Check that existing constraint can be satisfied
            const allSatisfyingVersions = allVersions.filter((v) => 
            // TODO #22198
            unconstrainedValue || versioning.matches(v.version, compareValue));
            if (!allSatisfyingVersions.length) {
                logger_1.logger.debug(`Found no satisfying versions with '${config.versioning}' versioning`);
            }
            if (config.rollbackPrs && !allSatisfyingVersions.length) {
                const rollback = (0, rollback_1.getRollbackUpdate)(config, allVersions, versioning);
                // istanbul ignore if
                if (!rollback) {
                    res.warnings.push({
                        topic: config.packageName,
                        // TODO: types (#22198)
                        message: `Can't find version matching ${compareValue} for ${config.datasource} package ${config.packageName}`,
                    });
                    return result_1.Result.ok(res);
                }
                res.updates.push(rollback);
            }
            let rangeStrategy = (0, manager_1.getRangeStrategy)(config);
            // istanbul ignore next
            if (config.isVulnerabilityAlert &&
                rangeStrategy === 'update-lockfile' &&
                !config.lockedVersion) {
                rangeStrategy = 'bump';
            }
            // unconstrained deps with lockedVersion
            if (config.isVulnerabilityAlert &&
                !config.currentValue &&
                config.lockedVersion) {
                rangeStrategy = 'update-lockfile';
            }
            const nonDeprecatedVersions = dependency.releases
                .filter((release) => !release.isDeprecated)
                .map((release) => release.version);
            let currentVersion;
            if (rangeStrategy === 'update-lockfile') {
                currentVersion = config.lockedVersion;
            }
            // TODO #22198
            currentVersion ??=
                (0, current_1.getCurrentVersion)(compareValue, config.lockedVersion, versioning, rangeStrategy, latestVersion, nonDeprecatedVersions) ??
                    (0, current_1.getCurrentVersion)(compareValue, config.lockedVersion, versioning, rangeStrategy, latestVersion, allVersions.map((v) => v.version));
            if (!currentVersion) {
                if (!config.lockedVersion) {
                    logger_1.logger.debug(`No currentVersion or lockedVersion found for ${config.packageName}`);
                    res.skipReason = 'invalid-value';
                }
                return result_1.Result.ok(res);
            }
            res.currentVersion = currentVersion;
            const currentVersionTimestamp = getTimestamp(allVersions, currentVersion, versioning);
            if (is_1.default.nonEmptyString(currentVersionTimestamp)) {
                res.currentVersionTimestamp = currentVersionTimestamp;
                if (config.packageRules?.some((rules) => is_1.default.nonEmptyString(rules.matchCurrentAge))) {
                    // Reapply package rules to check matches for matchCurrentAge
                    config = (0, package_rules_1.applyPackageRules)({ ...config, currentVersionTimestamp }, 'current-timestamp');
                }
            }
            if (compareValue &&
                currentVersion &&
                rangeStrategy === 'pin' &&
                !versioning.isSingleVersion(compareValue)) {
                res.updates.push({
                    updateType: 'pin',
                    isPin: true,
                    // TODO: newValue can be null! (#22198)
                    newValue: versioning.getNewValue({
                        currentValue: compareValue,
                        rangeStrategy,
                        currentVersion,
                        newVersion: currentVersion,
                    }),
                    newVersion: currentVersion,
                    newMajor: versioning.getMajor(currentVersion),
                });
            }
            if (rangeStrategy === 'pin') {
                // Fall back to replace once pinning logic is done
                rangeStrategy = 'replace';
            }
            // istanbul ignore if
            if (!versioning.isVersion(currentVersion)) {
                res.skipReason = 'invalid-version';
                return result_1.Result.ok(res);
            }
            // Filter latest, unstable, etc
            // TODO #22198
            let filteredReleases = (0, filter_1.filterVersions)(config, currentVersion, latestVersion, config.rangeStrategy === 'in-range-only'
                ? allSatisfyingVersions
                : allVersions, versioning).filter((v) => 
            // Leave only compatible versions
            unconstrainedValue ||
                versioning.isCompatible(v.version, compareValue));
            let shrinkedViaVulnerability = false;
            if (config.isVulnerabilityAlert) {
                if (config.vulnerabilityFixVersion) {
                    res.vulnerabilityFixVersion = config.vulnerabilityFixVersion;
                    res.vulnerabilityFixStrategy = config.vulnerabilityFixStrategy;
                    if (versioning.isValid(config.vulnerabilityFixVersion)) {
                        let fixedFilteredReleases;
                        if (versioning.isVersion(config.vulnerabilityFixVersion)) {
                            // Retain only releases greater than or equal to the fix version
                            fixedFilteredReleases = filteredReleases.filter((release) => !versioning.isGreaterThan(config.vulnerabilityFixVersion, release.version));
                        }
                        else {
                            // Retain only releases which max the fix constraint
                            fixedFilteredReleases = filteredReleases.filter((release) => versioning.matches(release.version, config.vulnerabilityFixVersion));
                        }
                        // Warn if this filtering results caused zero releases
                        if (fixedFilteredReleases.length === 0 && filteredReleases.length) {
                            logger_1.logger.warn({
                                releases: filteredReleases,
                                vulnerabilityFixVersion: config.vulnerabilityFixVersion,
                                packageName: config.packageName,
                            }, 'No releases satisfy vulnerabilityFixVersion');
                        }
                        // Use the additionally filtered releases
                        filteredReleases = fixedFilteredReleases;
                    }
                    else {
                        logger_1.logger.warn({
                            vulnerabilityFixVersion: config.vulnerabilityFixVersion,
                            packageName: config.packageName,
                        }, 'vulnerabilityFixVersion is not valid');
                    }
                }
                if (config.vulnerabilityFixStrategy === 'highest') {
                    // Don't shrink the list of releases - let Renovate use its normal logic
                    logger_1.logger.once.debug(`Using vulnerabilityFixStrategy=highest for ${config.packageName}`);
                }
                else {
                    // Shrink the list of releases to the lowest fixed version
                    logger_1.logger.once.debug(`Using vulnerabilityFixStrategy=lowest for ${config.packageName}`);
                    filteredReleases = filteredReleases.slice(0, 1);
                    shrinkedViaVulnerability = true;
                }
            }
            const buckets = {};
            for (const release of filteredReleases) {
                const bucket = (0, bucket_1.getBucket)(config, 
                // TODO #22198
                currentVersion, release.version, versioning);
                if (is_1.default.string(bucket)) {
                    if (buckets[bucket]) {
                        buckets[bucket].push(release);
                    }
                    else {
                        buckets[bucket] = [release];
                    }
                }
            }
            const depResultConfig = (0, config_1.mergeChildConfig)(config, res);
            for (const [bucket, releases] of Object.entries(buckets)) {
                const sortedReleases = releases.sort((r1, r2) => versioning.sortVersions(r1.version, r2.version));
                const { release, pendingChecks, pendingReleases } = await (0, filter_checks_1.filterInternalChecks)(depResultConfig, versioning, bucket, sortedReleases);
                // istanbul ignore next
                if (!release) {
                    return result_1.Result.ok(res);
                }
                const newVersion = release.version;
                const update = await (0, generate_1.generateUpdate)(config, compareValue, versioning, 
                // TODO #22198
                rangeStrategy, config.lockedVersion ?? currentVersion, bucket, release);
                // #29034
                if (config.manager === 'gomod' &&
                    compareValue?.startsWith('v0.0.0-') &&
                    update.newValue?.startsWith('v0.0.0-') &&
                    config.currentDigest !== update.newDigest) {
                    update.updateType = 'digest';
                }
                if (pendingChecks) {
                    update.pendingChecks = pendingChecks;
                }
                // TODO #22198
                if (pendingReleases.length) {
                    update.pendingVersions = pendingReleases.map((r) => r.version);
                }
                if (!update.newValue || update.newValue === compareValue) {
                    if (!config.lockedVersion) {
                        continue;
                    }
                    // istanbul ignore if
                    if (rangeStrategy === 'bump') {
                        logger_1.logger.trace({
                            packageName: config.packageName,
                            currentValue: config.currentValue,
                            lockedVersion: config.lockedVersion,
                            newVersion,
                        }, 'Skipping bump because newValue is the same');
                        continue;
                    }
                    res.isSingleVersion = true;
                }
                res.isSingleVersion ??=
                    is_1.default.string(update.newValue) &&
                        versioning.isSingleVersion(update.newValue);
                // istanbul ignore if
                if (config.versioning === docker_1.id &&
                    update.updateType !== 'rollback' &&
                    update.newValue &&
                    versioning.isVersion(update.newValue) &&
                    compareValue &&
                    versioning.isVersion(compareValue) &&
                    versioning.isGreaterThan(compareValue, update.newValue)) {
                    logger_1.logger.warn({
                        packageName: config.packageName,
                        currentValue: config.currentValue,
                        compareValue,
                        currentVersion: config.currentVersion,
                        update,
                        allVersionsLength: allVersions.length,
                        filteredReleaseVersions: filteredReleases.map((r) => r.version),
                        shrinkedViaVulnerability,
                    }, 'Unexpected downgrade detected: skipping');
                }
                else {
                    res.updates.push(update);
                }
            }
        }
        else if (compareValue) {
            logger_1.logger.debug(`Dependency ${config.packageName} has unsupported/unversioned value ${compareValue} (versioning=${config.versioning})`);
            if (!config.pinDigests && !config.currentDigest) {
                logger_1.logger.debug(`Skipping ${config.packageName} because no currentDigest or pinDigests`);
                res.skipReason = 'invalid-value';
            }
            else {
                delete res.skipReason;
            }
        }
        else {
            res.skipReason = 'invalid-value';
        }
        if ((0, utils_1.isReplacementRulesConfigured)(config)) {
            (0, utils_1.addReplacementUpdateIfValid)(res.updates, config);
        }
        // Record if the dep is fixed to a version
        if (config.lockedVersion) {
            res.currentVersion = config.lockedVersion;
            res.fixedVersion = config.lockedVersion;
        }
        else if (compareValue && versioning.isSingleVersion(compareValue)) {
            res.fixedVersion = compareValue.replace((0, regex_1.regEx)(/^=+/), '');
        }
        // massage versionCompatibility
        if (is_1.default.string(config.currentValue) &&
            is_1.default.string(compareValue) &&
            is_1.default.string(config.versionCompatibility)) {
            for (const update of res.updates) {
                logger_1.logger.debug({ update });
                if (is_1.default.string(config.currentValue) && is_1.default.string(update.newValue)) {
                    update.newValue = config.currentValue.replace(compareValue, update.newValue);
                }
            }
        }
        // Add digests if necessary
        if ((0, datasource_1.supportsDigests)(config.datasource)) {
            if (config.currentDigest) {
                if (!config.digestOneAndOnly || !res.updates.length) {
                    // digest update
                    res.updates.push({
                        updateType: 'digest',
                        newValue: config.currentValue,
                    });
                }
            }
            else if (config.pinDigests) {
                // Create a pin only if one doesn't already exists
                if (!res.updates.some((update) => update.updateType === 'pin')) {
                    // pin digest
                    res.updates.push({
                        isPinDigest: true,
                        updateType: 'pinDigest',
                        newValue: config.currentValue,
                    });
                }
            }
            if (versioning.valueToVersion) {
                // TODO #22198
                res.currentVersion = versioning.valueToVersion(res.currentVersion);
                for (const update of res.updates || /* istanbul ignore next*/ []) {
                    // TODO #22198
                    update.newVersion = versioning.valueToVersion(update.newVersion);
                }
            }
            if (res.registryUrl) {
                config.registryUrls = [res.registryUrl];
            }
            // update digest for all
            for (const update of res.updates) {
                if (config.pinDigests === true || config.currentDigest) {
                    const getDigestConfig = {
                        ...config,
                        registryUrl: update.registryUrl ?? res.registryUrl,
                        lookupName: res.lookupName,
                    };
                    // #20304 only pass it for replacement updates, otherwise we get wrong or invalid digest
                    if (update.updateType !== 'replacement') {
                        delete getDigestConfig.replacementName;
                    }
                    // #20304 don't use lookupName and currentDigest when we replace image name
                    if (update.updateType === 'replacement' &&
                        update.newName !== config.packageName) {
                        delete getDigestConfig.lookupName;
                        delete getDigestConfig.currentDigest;
                    }
                    // TODO #22198
                    update.newDigest ??=
                        dependency?.releases.find((r) => r.version === update.newValue)
                            ?.newDigest ??
                            (await (0, datasource_1.getDigest)(getDigestConfig, update.newValue));
                    // If the digest could not be determined, report this as otherwise the
                    // update will be omitted later on without notice.
                    if (update.newDigest === null) {
                        logger_1.logger.debug({
                            packageName: config.packageName,
                            currentValue: config.currentValue,
                            datasource: config.datasource,
                            newValue: update.newValue,
                            bucket: update.bucket,
                        }, 'Could not determine new digest for update.');
                        // Only report a warning if there is a current digest.
                        // Context: https://github.com/renovatebot/renovate/pull/20175#discussion_r1102615059.
                        if (config.currentDigest) {
                            res.warnings.push({
                                message: `Could not determine new digest for update (${config.datasource} package ${config.packageName})`,
                                topic: config.packageName,
                            });
                        }
                    }
                }
                else {
                    delete update.newDigest;
                }
                if (update.newVersion) {
                    const registryUrl = dependency?.releases?.find((release) => release.version === update.newVersion)?.registryUrl;
                    if (registryUrl && registryUrl !== res.registryUrl) {
                        update.registryUrl = registryUrl;
                    }
                }
            }
        }
        if (res.updates.length) {
            delete res.skipReason;
        }
        // Strip out any non-changed ones
        res.updates = res.updates
            .filter((update) => update.newValue !== null || config.currentValue === null)
            .filter((update) => update.newDigest !== null)
            .filter((update) => (is_1.default.string(update.newName) &&
            update.newName !== config.packageName) ||
            update.isReplacement === true ||
            update.newValue !== config.currentValue ||
            update.isLockfileUpdate === true ||
            // TODO #22198
            (update.newDigest &&
                !update.newDigest.startsWith(config.currentDigest)));
        // If range strategy specified in config is 'in-range-only', also strip out updates where currentValue !== newValue
        if (config.rangeStrategy === 'in-range-only') {
            res.updates = res.updates.filter((update) => update.newValue === config.currentValue);
        }
        // Handle a weird edge case involving followTag and fallbacks
        if (config.rollbackPrs && config.followTag) {
            res.updates = res.updates.filter((update) => res.updates.length === 1 ||
                /* istanbul ignore next */ update.updateType !== 'rollback');
        }
    }
    catch (err) /* istanbul ignore next */ {
        if (err instanceof external_host_error_1.ExternalHostError) {
            return result_1.Result.err(err);
        }
        if (err instanceof Error && err.message === error_messages_1.CONFIG_VALIDATION) {
            return result_1.Result.err(err);
        }
        logger_1.logger.error({
            currentDigest: config.currentDigest,
            currentValue: config.currentValue,
            datasource: config.datasource,
            packageName: config.packageName,
            digestOneAndOnly: config.digestOneAndOnly,
            followTag: config.followTag,
            lockedVersion: config.lockedVersion,
            packageFile: config.packageFile,
            pinDigests: config.pinDigests,
            rollbackPrs: config.rollbackPrs,
            isVulnerabilityAlert: config.isVulnerabilityAlert,
            updatePinnedDependencies: config.updatePinnedDependencies,
            unconstrainedValue,
            err,
        }, 'lookupUpdates error');
        res.skipReason = 'internal-error';
    }
    return result_1.Result.ok(res);
}
//# sourceMappingURL=index.js.map