import { HOST_DISABLED } from "../../constants/error-messages.js";
import { get, set } from "../../util/cache/memory/index.js";
import { GlobalConfig } from "../../config/global.js";
import { logger } from "../../logger/index.js";
import { clone } from "../../util/clone.js";
import { trimTrailingSlash } from "../../util/url.js";
import { coerceArray } from "../../util/array.js";
import { ExternalHostError } from "../../types/errors/external-host-error.js";
import { DatasourceCacheStats } from "../../util/stats.js";
import { get as get$1, set as set$1 } from "../../util/cache/package/index.js";
import { AsyncResult, Result } from "../../util/result.js";
import { resolveRegistryUrl, setNpmrc } from "./npm/npmrc.js";
import "./npm/index.js";
import { filterMap } from "../../util/filter-map.js";
import { get as get$2 } from "../versioning/index.js";
import { addMetaData } from "./metadata.js";
import api from "./api.js";
import { applyConstraintsFiltering, applyExtractVersion, applyVersionCompatibility, filterValidVersions, getDatasourceFor, isGetPkgReleasesConfig, sortAndRemoveDuplicates } from "./common.js";
import { dequal } from "dequal";
import { isFunction, isNonEmptyArray, isString } from "@sindresorhus/is";

//#region lib/modules/datasource/index.ts
const getDatasources = () => api;
const getDatasourceList = () => Array.from(api.keys());
function logError(datasource, packageName, err) {
	const { statusCode, code: errCode, url } = err;
	if (statusCode === 404) logger.debug({
		datasource,
		packageName,
		url
	}, "Datasource 404");
	else if (statusCode === 401 || statusCode === 403) logger.debug({
		datasource,
		packageName,
		url
	}, "Datasource unauthorized");
	else if (errCode) logger.debug({
		datasource,
		packageName,
		url,
		errCode
	}, "Datasource connection error");
	else logger.debug({
		datasource,
		packageName,
		err
	}, "Datasource unknown error");
}
async function getRegistryReleases(datasource, config, registryUrl) {
	const cacheNamespace = `datasource-releases-${datasource.id}`;
	const cacheKey = `${registryUrl}:${config.packageName}`;
	const cacheEnabled = !!datasource.caching;
	const cacheForced = GlobalConfig.get("cachePrivatePackages", false);
	if (cacheEnabled || cacheForced) {
		const cachedResult = await get$1(cacheNamespace, cacheKey);
		if (cachedResult) {
			logger.trace({ cacheKey }, "Returning cached datasource response");
			DatasourceCacheStats.hit(datasource.id, registryUrl, config.packageName);
			return cachedResult;
		}
		DatasourceCacheStats.miss(datasource.id, registryUrl, config.packageName);
	}
	const res = await datasource.getReleases({
		...config,
		registryUrl
	});
	if (res?.releases.length) res.registryUrl ??= registryUrl;
	if (!res) return null;
	let cache = false;
	if (cacheForced) cache = true;
	else if (cacheEnabled && !res.isPrivate) cache = true;
	if (cache) {
		logger.trace({ cacheKey }, "Caching datasource response");
		await set$1(cacheNamespace, cacheKey, res, 15);
		DatasourceCacheStats.set(datasource.id, registryUrl, config.packageName);
	} else DatasourceCacheStats.skip(datasource.id, registryUrl, config.packageName);
	return res;
}
function firstRegistry(config, datasource, registryUrls) {
	if (registryUrls.length > 1) logger.warn({
		datasource: datasource.id,
		packageName: config.packageName,
		registryUrls
	}, "Excess registryUrls found for datasource lookup - using first configured only");
	const registryUrl = registryUrls[0];
	return getRegistryReleases(datasource, config, registryUrl);
}
async function huntRegistries(config, datasource, registryUrls) {
	let res = null;
	let caughtError;
	for (const registryUrl of registryUrls) try {
		res = await getRegistryReleases(datasource, config, registryUrl);
		if (res) break;
	} catch (err) {
		if (err instanceof ExternalHostError) throw err;
		caughtError = err;
		logger.trace({ err }, "datasource hunt failure");
	}
	if (res) return res;
	if (caughtError) throw caughtError;
	return null;
}
async function mergeRegistries(config, datasource, registryUrls) {
	let combinedRes;
	let lastErr;
	let singleRegistry = true;
	const releaseVersioning = get$2(config.versioning);
	for (const registryUrl of registryUrls) try {
		const res = await getRegistryReleases(datasource, config, registryUrl);
		if (!res) continue;
		if (!combinedRes) {
			combinedRes = res;
			continue;
		}
		if (singleRegistry) {
			for (const release of coerceArray(combinedRes.releases)) release.registryUrl ??= combinedRes.registryUrl;
			singleRegistry = false;
		}
		const releases = coerceArray(res.releases);
		for (const release of releases) release.registryUrl ??= res.registryUrl;
		combinedRes.releases.push(...releases);
		let tags = combinedRes.tags;
		if (tags) {
			if (res.tags) for (const tag of ["release", "latest"]) {
				const existingTag = combinedRes?.tags?.[tag];
				const newTag = res.tags?.[tag];
				if (isString(newTag) && releaseVersioning.isVersion(newTag)) if (isString(existingTag) && releaseVersioning.isVersion(existingTag)) {
					if (releaseVersioning.isGreaterThan(newTag, existingTag)) tags[tag] = newTag;
				} else tags[tag] = newTag;
			}
		} else tags = res.tags;
		combinedRes = {
			...res,
			...combinedRes
		};
		if (tags) combinedRes.tags = tags;
		delete combinedRes.registryUrl;
	} catch (err) {
		if (err instanceof ExternalHostError) throw err;
		lastErr = err;
		logger.trace({ err }, "datasource merge failure");
	}
	if (!combinedRes) {
		if (lastErr) throw lastErr;
		return null;
	}
	const seenVersions = /* @__PURE__ */ new Set();
	combinedRes.releases = filterMap(combinedRes.releases, (release) => {
		if (seenVersions.has(release.version)) return null;
		seenVersions.add(release.version);
		return release;
	});
	return combinedRes;
}
function massageRegistryUrls(registryUrls) {
	return registryUrls.filter(Boolean).map(trimTrailingSlash);
}
function resolveRegistryUrls(datasource, defaultRegistryUrls, registryUrls, additionalRegistryUrls) {
	if (!datasource.customRegistrySupport) {
		if (isNonEmptyArray(registryUrls) || isNonEmptyArray(defaultRegistryUrls) || isNonEmptyArray(additionalRegistryUrls)) logger.warn({
			datasource: datasource.id,
			registryUrls,
			defaultRegistryUrls,
			additionalRegistryUrls
		}, "Custom registries are not allowed for this datasource and will be ignored");
		return isFunction(datasource.defaultRegistryUrls) ? datasource.defaultRegistryUrls() : datasource.defaultRegistryUrls ?? [];
	}
	const customUrls = registryUrls?.filter(Boolean);
	let resolvedUrls = [];
	if (isNonEmptyArray(customUrls)) resolvedUrls = [...customUrls];
	else if (isNonEmptyArray(defaultRegistryUrls)) {
		resolvedUrls = [...defaultRegistryUrls];
		resolvedUrls = resolvedUrls.concat(additionalRegistryUrls ?? []);
	} else if (isFunction(datasource.defaultRegistryUrls)) {
		resolvedUrls = [...datasource.defaultRegistryUrls()];
		resolvedUrls = resolvedUrls.concat(additionalRegistryUrls ?? []);
	} else if (isNonEmptyArray(datasource.defaultRegistryUrls)) {
		resolvedUrls = [...datasource.defaultRegistryUrls];
		resolvedUrls = resolvedUrls.concat(additionalRegistryUrls ?? []);
	}
	return massageRegistryUrls(resolvedUrls);
}
function applyReplacements(config) {
	if (config.replacementName && config.replacementVersion) return {
		replacementName: config.replacementName,
		replacementVersion: config.replacementVersion
	};
}
async function fetchReleases(config) {
	const { datasource: datasourceName } = config;
	let { registryUrls } = config;
	// istanbul ignore if: need test
	if (!datasourceName || getDatasourceFor(datasourceName) === void 0) {
		logger.warn({ datasource: datasourceName }, "Unknown datasource");
		return null;
	}
	if (datasourceName === "npm") {
		if (isString(config.npmrc)) setNpmrc(config.npmrc);
		if (!isNonEmptyArray(registryUrls)) registryUrls = [resolveRegistryUrl(config.packageName)];
	}
	const datasource = getDatasourceFor(datasourceName);
	// istanbul ignore if: needs test
	if (!datasource) {
		logger.warn({ datasource: datasourceName }, "Unknown datasource");
		return null;
	}
	registryUrls = resolveRegistryUrls(datasource, config.defaultRegistryUrls, registryUrls, config.additionalRegistryUrls);
	let dep = null;
	const registryStrategy = config.registryStrategy ?? datasource.registryStrategy ?? "hunt";
	try {
		if (isNonEmptyArray(registryUrls)) {
			if (registryStrategy === "first") dep = await firstRegistry(config, datasource, registryUrls);
			else if (registryStrategy === "hunt") dep = await huntRegistries(config, datasource, registryUrls);
			else if (registryStrategy === "merge") dep = await mergeRegistries(config, datasource, registryUrls);
		} else dep = await datasource.getReleases(config);
	} catch (err) {
		if (err.message === HOST_DISABLED || err.err?.message === HOST_DISABLED) return null;
		if (err instanceof ExternalHostError) throw err;
		logError(datasource.id, config.packageName, err);
	}
	if (!dep || dequal(dep, { releases: [] })) return null;
	addMetaData(dep, datasourceName, config.packageName);
	dep = {
		...dep,
		...applyReplacements(config)
	};
	return dep;
}
function fetchCachedReleases(config) {
	const { datasource, packageName, registryUrls } = config;
	const cacheKey = `datasource-mem:releases:${datasource}:${packageName}:${config.registryStrategy}:${String(registryUrls)}`;
	const cachedResult = get(cacheKey);
	// istanbul ignore if
	if (cachedResult !== void 0) return cachedResult;
	const promisedRes = fetchReleases(config);
	set(cacheKey, promisedRes);
	return promisedRes;
}
function getRawPkgReleases(config) {
	if (!config.datasource) {
		logger.warn("No datasource found");
		return AsyncResult.err("no-datasource");
	}
	const packageName = config.packageName;
	if (!packageName) {
		logger.error({ config }, "Datasource getReleases without packageName");
		return AsyncResult.err("no-package-name");
	}
	return Result.wrapNullable(fetchCachedReleases(config), "no-result").catch((e) => {
		if (e instanceof ExternalHostError) {
			e.hostType = config.datasource;
			e.packageName = packageName;
		}
		return Result.err(e);
	}).transform(clone);
}
function applyDatasourceFilters(releaseResult, config) {
	let res = releaseResult;
	res = applyExtractVersion(res, config.extractVersion);
	res = applyVersionCompatibility(res, config.versionCompatibility, config.currentCompatibility);
	res = filterValidVersions(res, config);
	res = sortAndRemoveDuplicates(res, config);
	res = applyConstraintsFiltering(res, config);
	return res;
}
async function getPkgReleases(config) {
	const { val = null, err } = await getRawPkgReleases(config).transform((res) => applyDatasourceFilters(res, config)).unwrap();
	if (err instanceof Error) throw err;
	return val;
}
function supportsDigests(datasource) {
	const ds = !!datasource && getDatasourceFor(datasource);
	return !!ds && "getDigest" in ds;
}
function getDigestConfig(datasource, config) {
	const { lookupName, currentValue, currentDigest } = config;
	return {
		lookupName,
		packageName: config.replacementName ?? config.packageName,
		registryUrl: config.registryUrl ?? resolveRegistryUrls(datasource, config.defaultRegistryUrls, config.registryUrls, config.additionalRegistryUrls)[0],
		currentValue,
		currentDigest
	};
}
function getDigest(config, value) {
	const datasource = getDatasourceFor(config.datasource);
	// istanbul ignore if: need test
	if (!datasource || !("getDigest" in datasource)) return Promise.resolve(null);
	const digestConfig = getDigestConfig(datasource, config);
	return datasource.getDigest(digestConfig, value);
}
function getDefaultConfig(datasource) {
	const loadedDatasource = getDatasourceFor(datasource);
	return Promise.resolve(loadedDatasource?.defaultConfig ?? Object.create({}));
}

//#endregion
export { applyDatasourceFilters, getDatasourceList, getDatasources, getDefaultConfig, getDigest, getPkgReleases, getRawPkgReleases, isGetPkgReleasesConfig, supportsDigests };
//# sourceMappingURL=index.js.map