import { logger } from "../../../logger/index.js";
import { replaceUrlPath, resolveBaseUrl } from "../../../util/url.js";
import { find } from "../../../util/host-rules.js";
import { ExternalHostError } from "../../../types/errors/external-host-error.js";
import { withCache } from "../../../util/cache/package/with-cache.js";
import { Datasource } from "../datasource.js";
import { map } from "../../../util/promises.js";
import { id } from "../../versioning/composer/index.js";
import { PackagesResponse, PackagistFile, RegistryMeta, extractDepReleases, parsePackagesResponses } from "./schema.js";
import { isObject } from "@sindresorhus/is";
import { z } from "zod";

//#region lib/modules/datasource/packagist/index.ts
var PackagistDatasource = class PackagistDatasource extends Datasource {
	static id = "packagist";
	constructor() {
		super(PackagistDatasource.id);
	}
	defaultRegistryUrls = ["https://repo.packagist.org"];
	defaultVersioning = id;
	registryStrategy = "hunt";
	releaseTimestampSupport = true;
	releaseTimestampNote = "The release timestamp is determined from the `time` field in the results.";
	sourceUrlSupport = "package";
	sourceUrlNote = "The source URL is determined from `source` field in the results.";
	static getHostOpts(url) {
		const { username, password } = find({
			hostType: PackagistDatasource.id,
			url
		});
		return username && password ? {
			username,
			password
		} : {};
	}
	async getJson(url, schema) {
		const opts = PackagistDatasource.getHostOpts(url);
		const { body } = await this.http.getJson(url, opts, schema);
		return body;
	}
	async _getRegistryMeta(regUrl) {
		const url = resolveBaseUrl(regUrl, "packages.json");
		return await this.getJson(url, RegistryMeta);
	}
	getRegistryMeta(regUrl) {
		return withCache({
			namespace: `datasource-${PackagistDatasource.id}`,
			key: `getRegistryMeta:${regUrl}`
		}, () => this._getRegistryMeta(regUrl));
	}
	static isPrivatePackage(regUrl) {
		return !!PackagistDatasource.getHostOpts(regUrl).password;
	}
	static getPackagistFileUrl(regUrl, regFile) {
		const { key, hash } = regFile;
		return resolveBaseUrl(regUrl, hash ? key.replace("%hash%", hash) : key);
	}
	async _getPackagistFile(regUrl, regFile) {
		const url = PackagistDatasource.getPackagistFileUrl(regUrl, regFile);
		return await this.getJson(url, PackagistFile);
	}
	getPackagistFile(regUrl, regFile) {
		return withCache({
			namespace: `datasource-${PackagistDatasource.id}`,
			key: `getPackagistFile:${PackagistDatasource.getPackagistFileUrl(regUrl, regFile)}`,
			ttlMinutes: 1440,
			cacheable: !PackagistDatasource.isPrivatePackage(regUrl)
		}, () => this._getPackagistFile(regUrl, regFile));
	}
	async fetchProviderPackages(regUrl, meta) {
		await map(meta.files, async (file) => {
			const res = await this.getPackagistFile(regUrl, file);
			Object.assign(meta.providerPackages, res.providers);
		});
	}
	async fetchIncludesPackages(regUrl, meta) {
		await map(meta.includesFiles, async (file) => {
			const res = await this.getPackagistFile(regUrl, file);
			for (const [key, val] of Object.entries(res.packages)) meta.includesPackages[key] = extractDepReleases(val);
		});
	}
	async _packagistV2Lookup(registryUrl, metadataUrl, packageName) {
		const pkgUrl = replaceUrlPath(registryUrl, metadataUrl.replace("%package%", packageName));
		const pkgPromise = this.getJson(pkgUrl, z.unknown());
		const devUrl = replaceUrlPath(registryUrl, metadataUrl.replace("%package%", `${packageName}~dev`));
		const devPromise = this.getJson(devUrl, z.unknown()).then((x) => x, () => null);
		return parsePackagesResponses(packageName, await Promise.all([pkgPromise, devPromise]).then((responses) => responses.filter(isObject)));
	}
	packagistV2Lookup(registryUrl, metadataUrl, packageName) {
		return withCache({
			namespace: `datasource-${PackagistDatasource.id}`,
			key: `packagistV2Lookup:${registryUrl}:${metadataUrl}:${packageName}`,
			ttlMinutes: 10
		}, () => this._packagistV2Lookup(registryUrl, metadataUrl, packageName));
	}
	getPkgUrl(packageName, registryUrl, registryMeta) {
		if (registryMeta.providersUrl && packageName in registryMeta.providerPackages) {
			let url = registryMeta.providersUrl.replace("%package%", packageName);
			const hash = registryMeta.providerPackages[packageName];
			if (hash) url = url.replace("%hash%", hash);
			return replaceUrlPath(registryUrl, url);
		}
		if (registryMeta.providersLazyUrl) return replaceUrlPath(registryUrl, registryMeta.providersLazyUrl.replace("%package%", packageName));
		return null;
	}
	async getReleases({ packageName, registryUrl }) {
		logger.trace(`getReleases(${packageName})`);
		/* v8 ignore next 3 -- should never happen */
		if (!registryUrl) return null;
		try {
			const meta = await this.getRegistryMeta(registryUrl);
			if (meta.availablePackages && !meta.availablePackages.includes(packageName)) return null;
			if (meta.metadataUrl) return await this.packagistV2Lookup(registryUrl, meta.metadataUrl, packageName);
			if (meta.packages[packageName]) return extractDepReleases(meta.packages[packageName]);
			await this.fetchIncludesPackages(registryUrl, meta);
			if (meta.includesPackages[packageName]) return meta.includesPackages[packageName];
			await this.fetchProviderPackages(registryUrl, meta);
			const pkgUrl = this.getPkgUrl(packageName, registryUrl, meta);
			if (!pkgUrl) return null;
			const dep = extractDepReleases((await this.getJson(pkgUrl, PackagesResponse)).packages[packageName]);
			logger.trace({ dep }, "dep");
			return dep;
		} catch (err) 		/* istanbul ignore next */ {
			if (err.host === "packagist.org") {
				if (err.code === "ECONNRESET" || err.code === "ETIMEDOUT") throw new ExternalHostError(err);
				if (err.statusCode && err.statusCode >= 500 && err.statusCode < 600) throw new ExternalHostError(err);
			}
			throw err;
		}
	}
};

//#endregion
export { PackagistDatasource };
//# sourceMappingURL=index.js.map