import "../../../../constants/error-messages.js";
import { regEx } from "../../../../util/regex.js";
import { logger } from "../../../../logger/index.js";
import { mergeChildConfig } from "../../../../config/utils.js";
import { ExternalHostError } from "../../../../types/errors/external-host-error.js";
import "../../../../modules/versioning/docker/index.js";
import { get } from "../../../../modules/versioning/index.js";
import { Result } from "../../../../util/result.js";
import { assignKeys } from "../../../../util/assign-keys.js";
import { getElapsedDays } from "../../../../util/date.js";
import { getDatasourceFor, getDefaultVersioning, isGetPkgReleasesConfig } from "../../../../modules/datasource/common.js";
import { applyDatasourceFilters, getDigest, getRawPkgReleases, supportsDigests } from "../../../../modules/datasource/index.js";
import { getRangeStrategy } from "../../../../modules/manager/index.js";
import "../../../../config/index.js";
import { applyPackageRules } from "../../../../util/package-rules/index.js";
import { postprocessRelease } from "../../../../modules/datasource/postprocess-release.js";
import { calculateAbandonment } from "./abandonment.js";
import { getBucket } from "./bucket.js";
import { getCurrentVersion } from "./current.js";
import { filterVersions } from "./filter.js";
import { filterInternalChecks } from "./filter-checks.js";
import { generateUpdate } from "./generate.js";
import { getRollbackUpdate } from "./rollback.js";
import { calculateMostRecentTimestamp } from "./timestamps.js";
import { addReplacementUpdateIfValid, isReplacementRulesConfigured } from "./utils.js";
import { isNonEmptyString, isString, isUndefined } from "@sindresorhus/is";
//#region lib/workers/repository/process/lookup/index.ts
async function getTimestamp(config, versions, version, versioningApi) {
	const currentRelease = versions.find((v) => versioningApi.isValid(v.version) && versioningApi.equals(v.version, version));
	if (!currentRelease) return null;
	if (currentRelease.releaseTimestamp) return currentRelease.releaseTimestamp;
	return (await postprocessRelease(config, currentRelease))?.releaseTimestamp;
}
async function lookupUpdates(inconfig) {
	let config = { ...inconfig };
	config.versioning ??= getDefaultVersioning(config.datasource);
	const versioningApi = get(config.versioning);
	let dependency = null;
	const res = {
		versioning: config.versioning,
		updates: [],
		warnings: []
	};
	try {
		logger.trace({
			dependency: config.packageName,
			currentValue: config.currentValue
		}, "lookupUpdates");
		if (config.currentValue && !isString(config.currentValue)) {
			// v8 ignore else -- TODO: add test #40625
			if (config.currentValue) logger.debug(`Invalid currentValue for ${config.packageName}: ${JSON.stringify(config.currentValue)} (${typeof config.currentValue})`);
			res.skipReason = "invalid-value";
			return Result.ok(res);
		}
		if (!isGetPkgReleasesConfig(config) || !getDatasourceFor(config.datasource)) {
			res.skipReason = "invalid-config";
			return Result.ok(res);
		}
		let compareValue = config.currentValue;
		if (isString(config.currentValue) && isString(config.versionCompatibility)) {
			const regexMatch = regEx(config.versionCompatibility).exec(config.currentValue);
			if (regexMatch?.groups) {
				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.debug({
				versionCompatibility: config.versionCompatibility,
				currentValue: config.currentValue,
				packageName: config.packageName
			}, "version compatibility regex mismatch");
		}
		const isValid = isString(compareValue) && versioningApi.isValid(compareValue);
		const unconstrainedValue = !!config.lockedVersion && isUndefined(config.currentValue);
		if (isValid || unconstrainedValue) {
			if (!config.updatePinnedDependencies && versioningApi.isSingleVersion(compareValue)) {
				res.skipReason = "is-pinned";
				return Result.ok(res);
			}
			const { val: releaseResult, err: lookupError } = await getRawPkgReleases(config).transform((res) => calculateMostRecentTimestamp(versioningApi, res)).transform((res) => calculateAbandonment(res, config)).transform((res) => applyDatasourceFilters(res, config)).unwrap();
			if (lookupError instanceof Error) throw lookupError;
			if (lookupError) {
				const warning = {
					topic: config.packageName,
					message: `Failed to look up ${config.datasource} package ${config.packageName}: ${lookupError}`
				};
				logger.debug({
					dependency: config.packageName,
					packageFile: config.packageFile
				}, warning.message);
				res.warnings.push(warning);
				return Result.ok(res);
			}
			dependency = releaseResult;
			if (dependency.deprecationMessage) logger.debug(`Found deprecationMessage for ${config.datasource} package ${config.packageName}`);
			assignKeys(res, dependency, [
				"deprecationMessage",
				"sourceUrl",
				"registryUrl",
				"sourceDirectory",
				"homepage",
				"changelogUrl",
				"dependencyUrl",
				"lookupName",
				"packageScope",
				"mostRecentTimestamp",
				"isAbandoned",
				"respectLatest"
			]);
			const latestVersion = dependency.tags?.latest;
			let allVersions = dependency.releases.filter((release) => versioningApi.isVersion(release.version));
			const allReleaseVersions = new Set(allVersions.map((r) => r.version));
			// istanbul ignore if
			if (allVersions.length === 0) {
				logger.info({
					dependency: config.packageName,
					result: dependency
				}, `Found no results from datasource that look like a version`);
				if (!config.currentDigest) return Result.ok(res);
			}
			config = await 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.ok(res);
				}
				allVersions = allVersions.filter((v) => v.version === taggedVersion || v.version === compareValue && versioningApi.isGreaterThan(taggedVersion, compareValue));
			}
			const inRangeOnlyStrategy = config.rangeStrategy === "in-range-only";
			const allSatisfyingVersions = (inRangeOnlyStrategy || config.rollbackPrs) && !unconstrainedValue ? allVersions.filter((v) => versioningApi.matches(v.version, compareValue)) : allVersions;
			if (!allSatisfyingVersions.length) logger.debug(`Found no satisfying versions with '${config.versioning}' versioning`);
			if (config.rollbackPrs && !allSatisfyingVersions.length) {
				const rollback = getRollbackUpdate(config, allVersions, versioningApi);
				// istanbul ignore if
				if (!rollback) {
					res.warnings.push({
						topic: config.packageName,
						message: `Can't find version matching ${compareValue} for ${config.datasource} package ${config.packageName}`
					});
					return Result.ok(res);
				}
				res.updates.push(rollback);
			}
			let rangeStrategy = getRangeStrategy(config);
			// istanbul ignore next
			if (config.isVulnerabilityAlert && rangeStrategy === "update-lockfile" && !config.lockedVersion) rangeStrategy = "bump";
			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;
			else if (compareValue && versioningApi.isSingleVersion(compareValue) && allVersions.find((v) => v.version === compareValue)) currentVersion = compareValue;
			currentVersion ??= getCurrentVersion(compareValue, config.lockedVersion, versioningApi, rangeStrategy, latestVersion, nonDeprecatedVersions) ?? getCurrentVersion(compareValue, config.lockedVersion, versioningApi, rangeStrategy, latestVersion, allVersions.map((v) => v.version));
			if (!currentVersion) {
				// v8 ignore else -- TODO: add test #40625
				if (!config.lockedVersion) {
					logger.debug(`No currentVersion or lockedVersion found for ${config.packageName}`);
					res.skipReason = "invalid-value";
				}
				return Result.ok(res);
			}
			res.currentVersion = currentVersion;
			const versionForTimestamp = config.lockedVersion ?? currentVersion;
			const currentVersionTimestamp = await getTimestamp(config, allVersions, versionForTimestamp, versioningApi);
			if (isNonEmptyString(currentVersionTimestamp)) {
				res.currentVersionTimestamp = currentVersionTimestamp;
				res.currentVersionAgeInDays = getElapsedDays(currentVersionTimestamp);
				if (config.packageRules?.some((rule) => isNonEmptyString(rule.matchCurrentAge))) config = await applyPackageRules({
					...config,
					currentVersionTimestamp
				}, "current-timestamp");
			}
			if (compareValue && currentVersion && rangeStrategy === "pin" && !versioningApi.isSingleVersion(compareValue)) {
				const newValue = versioningApi.getPinnedValue?.(currentVersion) ?? currentVersion;
				res.updates.push({
					updateType: "pin",
					isPin: true,
					newValue,
					newVersion: currentVersion,
					newMajor: versioningApi.getMajor(currentVersion)
				});
			}
			if (rangeStrategy === "pin") rangeStrategy = "replace";
			// istanbul ignore if
			if (!versioningApi.isVersion(currentVersion)) {
				res.skipReason = "invalid-version";
				return Result.ok(res);
			}
			let filteredReleases = filterVersions(config, currentVersion, latestVersion, inRangeOnlyStrategy ? allSatisfyingVersions : allVersions, versioningApi).filter((v) => unconstrainedValue || versioningApi.isCompatible(v.version, compareValue));
			let shrinkedViaVulnerability = false;
			if (config.isVulnerabilityAlert) {
				if (config.vulnerabilityFixVersion) {
					res.vulnerabilityFixVersion = config.vulnerabilityFixVersion;
					res.vulnerabilityFixStrategy = config.vulnerabilityFixStrategy;
					if (versioningApi.isValid(config.vulnerabilityFixVersion)) {
						let fixedFilteredReleases;
						if (versioningApi.isVersion(config.vulnerabilityFixVersion)) fixedFilteredReleases = filteredReleases.filter((release) => !versioningApi.isGreaterThan(config.vulnerabilityFixVersion, release.version));
						else fixedFilteredReleases = filteredReleases.filter((release) => versioningApi.matches(release.version, config.vulnerabilityFixVersion));
						if (fixedFilteredReleases.length === 0 && filteredReleases.length) logger.warn({
							releases: filteredReleases,
							vulnerabilityFixVersion: config.vulnerabilityFixVersion,
							packageName: config.packageName
						}, "No releases satisfy vulnerabilityFixVersion");
						filteredReleases = fixedFilteredReleases;
					} else logger.warn({
						vulnerabilityFixVersion: config.vulnerabilityFixVersion,
						packageName: config.packageName
					}, "vulnerabilityFixVersion is not valid");
				}
				if (config.vulnerabilityFixStrategy === "highest") logger.once.debug(`Using vulnerabilityFixStrategy=highest for ${config.packageName}`);
				else {
					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 = getBucket(config, currentVersion, release.version, versioningApi);
				// v8 ignore else -- TODO: add test #40625
				if (isString(bucket)) if (buckets[bucket]) buckets[bucket].push(release);
				else buckets[bucket] = [release];
			}
			const depResultConfig = mergeChildConfig(config, res);
			for (const [bucket, releases] of Object.entries(buckets)) {
				const { release, pendingChecks, pendingReleases } = await filterInternalChecks(depResultConfig, versioningApi, bucket, releases.sort((r1, r2) => versioningApi.sortVersions(r1.version, r2.version)));
				// istanbul ignore next
				if (!release) return Result.ok(res);
				const newVersion = release.version;
				const update = await generateUpdate(config, compareValue, versioningApi, rangeStrategy, config.lockedVersion ?? currentVersion, bucket, release, allReleaseVersions);
				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;
				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.trace({
							packageName: config.packageName,
							currentValue: config.currentValue,
							lockedVersion: config.lockedVersion,
							newVersion
						}, "Skipping bump because newValue is the same");
						continue;
					}
					res.isSingleVersion = true;
				}
				res.isSingleVersion ??= isString(update.newValue) && versioningApi.isSingleVersion(update.newValue);
				// istanbul ignore if
				if (config.versioning === "docker" && update.updateType !== "rollback" && update.newValue && versioningApi.isVersion(update.newValue) && compareValue && versioningApi.isVersion(compareValue) && versioningApi.isGreaterThan(compareValue, update.newValue)) 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.debug(`Dependency ${config.packageName} has unsupported/unversioned value ${compareValue} (versioning=${config.versioning})`);
			if (!config.pinDigests && !config.currentDigest) {
				logger.debug(`Skipping ${config.packageName} because no currentDigest or pinDigests`);
				res.skipReason = "invalid-value";
			} else delete res.skipReason;
		} else res.skipReason = "invalid-value";
		if (isReplacementRulesConfigured(config)) addReplacementUpdateIfValid(res.updates, config);
		else if (dependency?.replacementName && dependency.replacementVersion) res.updates.push({
			updateType: "replacement",
			newName: dependency.replacementName,
			newValue: dependency.replacementVersion
		});
		if (config.lockedVersion) {
			res.currentVersion = config.lockedVersion;
			res.fixedVersion = config.lockedVersion;
		} else if (compareValue && versioningApi.isSingleVersion(compareValue)) res.fixedVersion = compareValue.replace(regEx(/^=+/), "");
		if (isString(config.currentValue) && isString(compareValue) && isString(config.versionCompatibility)) for (const update of res.updates) {
			logger.debug({ update });
			// v8 ignore else -- TODO: add test #40625
			if (isString(config.currentValue) && isString(update.newValue)) update.newValue = config.currentValue.replace(compareValue, update.newValue);
		}
		if (supportsDigests(config.datasource)) {
			if (config.currentDigest) {
				if (!config.digestOneAndOnly || !res.updates.length) res.updates.push({
					updateType: "digest",
					newValue: config.currentValue
				});
			} else if (config.pinDigests && !res.updates.some((update) => update.updateType === "pin")) res.updates.push({
				isPinDigest: true,
				updateType: "pinDigest",
				newValue: config.currentValue
			});
			if (versioningApi.valueToVersion) {
				res.currentVersion = versioningApi.valueToVersion(res.currentVersion);
				for (const update of res.updates) update.newVersion = versioningApi.valueToVersion(update.newVersion);
			}
			if (res.registryUrl) config.registryUrls = [res.registryUrl];
			for (const update of res.updates) {
				if (config.pinDigests === true || config.currentDigest) {
					const getDigestConfig = {
						...config,
						registryUrl: update.registryUrl ?? res.registryUrl,
						lookupName: res.lookupName
					};
					if (update.updateType !== "replacement") delete getDigestConfig.replacementName;
					if (update.updateType === "replacement" && update.newName !== config.packageName) {
						delete getDigestConfig.lookupName;
						delete getDigestConfig.currentDigest;
						getDigestConfig.replacementName = update.newName;
					}
					if (update.updateType !== "replacement" || update.newName === config.packageName) update.newDigest ??= dependency?.releases.find((r) => r.version === update.newValue)?.newDigest;
					update.newDigest ??= await getDigest(getDigestConfig, update.newValue);
					if (update.newDigest === null) {
						logger.debug({
							packageName: config.packageName,
							currentValue: config.currentValue,
							datasource: config.datasource,
							newValue: update.newValue,
							bucket: update.bucket
						}, "Could not determine new digest for update.");
						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;
		res.updates = res.updates.filter((update) => update.newValue !== null || config.currentValue === null).filter((update) => update.newDigest !== null).filter((update) => isString(update.newName) && update.newName !== config.packageName || update.isReplacement === true || update.newValue !== config.currentValue || update.isLockfileUpdate === true || update.newDigest && !update.newDigest.startsWith(config.currentDigest));
		if (config.rangeStrategy === "in-range-only") res.updates = res.updates.filter((update) => update.newValue === config.currentValue);
		if (config.rollbackPrs && config.followTag) res.updates = res.updates.filter((update) => update.updateType !== "rollback" || res.updates.length === 1);
		const release = res.updates.length > 0 ? dependency?.releases.find((r) => r.version === res.updates[0].newValue) ?? dependency?.releases.find((r) => r.version === res.updates[0].newVersion) : null;
		if (release?.changelogContent) {
			res.changelogContent = release.changelogContent;
			res.changelogUrl = release.changelogUrl;
		}
	} catch (err) 	/* istanbul ignore next */ {
		if (err instanceof ExternalHostError) return Result.err(err);
		if (err instanceof Error && err.message === "config-validation") return Result.err(err);
		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,
			err
		}, "lookupUpdates error");
		res.skipReason = "internal-error";
	}
	return Result.ok(res);
}
//#endregion
export { lookupUpdates };

//# sourceMappingURL=index.js.map