import { getEnv } from "../../../util/env.js";
import { newlineRegex, regEx } from "../../../util/regex.js";
import { logger } from "../../../logger/index.js";
import { joinUrlParts } from "../../../util/url.js";
import { ExternalHostError } from "../../../types/errors/external-host-error.js";
import { withCache } from "../../../util/cache/package/with-cache.js";
import { RequestError } from "../../../util/http/got.js";
import "../../../util/http/index.js";
import { asTimestamp } from "../../../util/timestamp.js";
import { Datasource } from "../datasource.js";
import { map } from "../../../util/promises.js";
import { BaseGoDatasource } from "./base.js";
import { parseGoproxy, parseNoproxy } from "./goproxy-parser.js";
import { getSourceUrl } from "./common.js";
import { GoDirectDatasource } from "./releases-direct.js";
import { filterMap } from "../../../util/filter-map.js";
import api from "../../versioning/go-mod-directive/index.js";
import { isNonEmptyStringAndNotWhitespace, isTruthy } from "@sindresorhus/is";
//#region lib/modules/datasource/go/releases-goproxy.ts
const modRegex = regEx(/^(?<baseMod>.*?)(?:[./]v(?<majorVersion>\d+))?$/);
/**
* @see https://go.dev/ref/mod#pseudo-versions
*/
const pseudoVersionRegex = regEx(/v\d+\.\d+\.\d+-(?:\w+\.)?(?:0\.)?(?<timestamp>\d{14})-(?<digest>[a-f0-9]{12})/i);
function pseudoVersionToRelease(pseudoVersion) {
	const match = pseudoVersion.match(pseudoVersionRegex)?.groups;
	if (!match) return null;
	const { digest: newDigest, timestamp } = match;
	return {
		version: pseudoVersion,
		newDigest,
		releaseTimestamp: asTimestamp(timestamp)
	};
}
var GoProxyDatasource = class GoProxyDatasource extends Datasource {
	static id = "go-proxy";
	constructor() {
		super(GoProxyDatasource.id);
	}
	direct = new GoDirectDatasource();
	async _getReleases(config) {
		const { packageName } = config;
		logger.trace(`goproxy.getReleases(${packageName})`);
		const goproxy = getEnv().GOPROXY ?? "https://proxy.golang.org,direct";
		if (goproxy === "direct") return this.direct.getReleases(config);
		const proxyList = parseGoproxy(goproxy);
		const noproxy = parseNoproxy();
		let result = null;
		if (noproxy?.test(packageName)) {
			logger.debug(`Fetching ${packageName} via GONOPROXY match`);
			result = await this.direct.getReleases(config);
			return result;
		}
		for (const { url, fallback } of proxyList) try {
			if (url === "off") break;
			else if (url === "direct") {
				result = await this.direct.getReleases(config);
				break;
			}
			const res = await this.getVersionsWithInfo(url, packageName);
			if (res.releases.length) {
				result = res;
				break;
			}
		} catch (err) {
			const statusCode = (err instanceof ExternalHostError ? err.err : err)?.response?.statusCode;
			const canFallback = fallback === "|" ? true : statusCode === 404 || statusCode === 410;
			const msg = canFallback ? "Goproxy error: trying next URL provided with GOPROXY" : "Goproxy error: skipping other URLs provided with GOPROXY";
			logger.debug({ err }, msg);
			if (!canFallback) break;
		}
		if (result && !result.sourceUrl) try {
			const sourceUrl = getSourceUrl(await BaseGoDatasource.getDatasource(packageName));
			if (sourceUrl) result.sourceUrl = sourceUrl;
		} catch (err) {
			logger.trace({ err }, `Can't get datasource for ${packageName}`);
		}
		return result;
	}
	getReleases(config) {
		return withCache({
			namespace: `datasource-${GoProxyDatasource.id}`,
			key: GoProxyDatasource.getCacheKey(config),
			fallback: true
		}, () => this._getReleases(config));
	}
	/**
	* Avoid ambiguity when serving from case-insensitive file systems.
	*
	* @see https://golang.org/ref/mod#goproxy-protocol
	*/
	encodeCase(input) {
		return input.replace(regEx(/([A-Z])/g), (x) => `!${x.toLowerCase()}`);
	}
	async listVersions(baseUrl, packageName) {
		const url = joinUrlParts(baseUrl, this.encodeCase(packageName), "@v", "list");
		const { body } = await this.http.getText(url);
		return filterMap(body.split(newlineRegex), (str) => {
			if (!isNonEmptyStringAndNotWhitespace(str)) return null;
			const [version, timestamp] = str.trim().split(regEx(/\s+/));
			const release = pseudoVersionToRelease(version) ?? { version };
			const releaseTimestamp = asTimestamp(timestamp);
			if (releaseTimestamp) release.releaseTimestamp = releaseTimestamp;
			return release;
		});
	}
	async versionInfo(baseUrl, packageName, version) {
		const url = joinUrlParts(baseUrl, this.encodeCase(packageName), "@v", `${version}.info`);
		const res = await this.http.getJsonUnchecked(url);
		const result = { version: res.body.Version };
		const releaseTimestamp = asTimestamp(res.body.Time);
		if (releaseTimestamp) result.releaseTimestamp = releaseTimestamp;
		return result;
	}
	async getLatestVersion(baseUrl, packageName) {
		try {
			const url = joinUrlParts(baseUrl, this.encodeCase(packageName), "@latest");
			return (await this.http.getJsonUnchecked(url)).body.Version;
		} catch (err) {
			logger.trace({ err }, "Failed to get latest version");
			return null;
		}
	}
	async getVersionsWithInfo(baseUrl, packageName) {
		const isGopkgin = packageName.startsWith("gopkg.in/");
		const majorSuffixSeparator = isGopkgin ? "." : "/";
		const modParts = packageName.match(modRegex)?.groups;
		const baseMod = modParts?.baseMod ?? packageName;
		const packageMajor = parseInt(modParts?.majorVersion ?? "0", 10);
		const result = { releases: [] };
		for (let major = packageMajor;; major += 1) {
			let pkg = `${baseMod}${majorSuffixSeparator}v${major}`;
			if (!isGopkgin && major < 2) {
				pkg = baseMod;
				major += 1;
			}
			let releases = [];
			try {
				releases = await map((await this.listVersions(baseUrl, pkg)).filter(({ version }) => {
					if (major < 2) return true;
					return version.split(regEx(/[^\d]+/)).find(isTruthy) === major.toString();
				}), async (versionInfo) => {
					const { version, newDigest, releaseTimestamp } = versionInfo;
					if (releaseTimestamp) return {
						version,
						newDigest,
						releaseTimestamp
					};
					try {
						return await this.versionInfo(baseUrl, pkg, version);
					} catch (err) {
						logger.trace({ err }, `Can't obtain data from ${baseUrl}`);
						return { version };
					}
				});
				result.releases.push(...releases);
			} catch (err) {
				const potentialHttpError = err instanceof ExternalHostError ? err.err : err;
				const status = potentialHttpError.response?.statusCode;
				if (potentialHttpError instanceof RequestError && (status === 404 || status === 403) && major !== packageMajor) break;
				throw err;
			}
			const latestVersion = await this.getLatestVersion(baseUrl, pkg);
			if (latestVersion) {
				result.tags ??= {};
				result.tags.latest ??= latestVersion;
				if (api.isGreaterThan(latestVersion, result.tags.latest)) result.tags.latest = latestVersion;
				if (!result.releases.length) {
					const releaseFromLatest = pseudoVersionToRelease(latestVersion);
					if (releaseFromLatest) result.releases.push(releaseFromLatest);
				}
			}
			if (!releases.length) break;
		}
		return result;
	}
	static getCacheKey({ packageName }) {
		return `${packageName}@@${getEnv().GOPROXY}@@${parseNoproxy()?.toString()}`;
	}
};
//#endregion
export { GoProxyDatasource };

//# sourceMappingURL=releases-goproxy.js.map