import { regEx } from "../../../util/regex.js";
import { logger } from "../../../logger/index.js";
import { getQueryString, joinUrlParts } from "../../../util/url.js";
import { ExternalHostError } from "../../../types/errors/external-host-error.js";
import { id } from "../../versioning/hashicorp/index.js";
import { withCache } from "../../../util/cache/package/with-cache.js";
import { map } from "../../../util/promises.js";
import { TerraformDatasource } from "../terraform-module/base.js";
import { createSDBackendURL } from "../terraform-module/utils.js";
import { OpenTofuProviderDocsResponse, TerraformProviderReleaseBackend, TerraformProviderV2Response, TerraformProviderVersions, TerraformRegistryBuildResponse, TerraformRegistryVersions, VersionDetailResponse } from "./schema.js";
//#region lib/modules/datasource/terraform-provider/index.ts
var TerraformProviderDatasource = class TerraformProviderDatasource extends TerraformDatasource {
	static id = "terraform-provider";
	static hashicorpReleaseUrl = "https://releases.hashicorp.com";
	static defaultRegistryUrls = [TerraformProviderDatasource.terraformRegistryUrl, TerraformProviderDatasource.hashicorpReleaseUrl];
	static repositoryRegex = regEx(/^hashicorp\/(?<packageName>\S+)$/);
	constructor() {
		super(TerraformProviderDatasource.id);
	}
	defaultRegistryUrls = TerraformProviderDatasource.defaultRegistryUrls;
	defaultVersioning = id;
	registryStrategy = "hunt";
	releaseTimestampSupport = true;
	releaseTimestampNote = "The release timestamp is only available for `registry.terraform.io` (v2 API) and `registry.opentofu.org` (via `api.opentofu.org`). Other registries using the Provider Registry Protocol do not provide timestamps.";
	sourceUrlSupport = "package";
	sourceUrlNote = "For `registry.terraform.io`, the source URL is taken from the `source` field of the v2 API response. For `registry.opentofu.org`, it is derived from the package name following the OpenTofu registry policy of `github.com/NAMESPACE/terraform-provider-NAME`.";
	async _getReleases({ packageName, registryUrl }) {
		/* v8 ignore next 3 -- should never happen */
		if (!registryUrl) return null;
		logger.trace(`terraform-provider.getDependencies() packageName: ${packageName}`);
		if (registryUrl === TerraformProviderDatasource.terraformRegistryUrl) return await this.queryTerraformRegistryV2(registryUrl, packageName);
		if (registryUrl === TerraformProviderDatasource.openTofuRegistryUrl || registryUrl === TerraformProviderDatasource.openTofuApiUrl) return await this.queryOpenTofuRegistry(packageName);
		if (registryUrl === TerraformProviderDatasource.hashicorpReleaseUrl) return await this.queryReleaseBackend(packageName, registryUrl);
		return await this.queryProviderRegistry(registryUrl, packageName);
	}
	getReleases(config) {
		const url = config.registryUrl;
		const repo = TerraformProviderDatasource.getRepository(config);
		return withCache({
			namespace: `datasource-${TerraformProviderDatasource.id}`,
			key: `getReleases:${url}/${repo}`,
			fallback: true
		}, () => this._getReleases(config));
	}
	static getRepository({ packageName }) {
		return packageName.includes("/") ? packageName : `hashicorp/${packageName}`;
	}
	/**
	* Query the Terraform Registry using the undocumented v2 JSON:API.
	*
	* Returns release timestamps for all versions, unlike the v1 API
	* which only exposed the timestamp for the latest version.
	*/
	async queryTerraformRegistryV2(registryUrl, packageName) {
		const repository = TerraformProviderDatasource.getRepository({ packageName });
		const providerUrl = `${joinUrlParts(registryUrl, "v2/providers", repository)}?${getQueryString({ include: "provider-versions" })}`;
		const { body: res } = await this.http.getJson(providerUrl, TerraformProviderV2Response);
		res.homepage = `${registryUrl}/providers/${repository}`;
		return res;
	}
	/**
	* Query the OpenTofu registry docs API.
	* https://api.opentofu.org/
	*
	* Used when the registry URL is `registry.opentofu.org`.
	* Queries `api.opentofu.org` for provider versions with release timestamps.
	*/
	async queryOpenTofuRegistry(packageName) {
		const repository = TerraformProviderDatasource.getRepository({ packageName });
		const docsUrl = joinUrlParts(TerraformProviderDatasource.openTofuApiUrl, "registry/docs/providers", repository, "index.json");
		const { body: res } = await this.http.getJson(docsUrl, OpenTofuProviderDocsResponse);
		res.homepage = `https://search.opentofu.org/provider/${repository}`;
		const [namespace, name] = repository.split("/");
		res.sourceUrl = `https://github.com/${namespace}/terraform-provider-${name}`;
		return res;
	}
	/**
	* Query a registry using the Provider Registry Protocol that all registries
	* are required to implement.
	* https://www.terraform.io/internals/provider-registry-protocol
	*/
	async queryProviderRegistry(registryUrl, packageName) {
		const repository = TerraformProviderDatasource.getRepository({ packageName });
		const backendURL = createSDBackendURL(registryUrl, "providers.v1", await this.getTerraformServiceDiscoveryResult(registryUrl), `${repository}/versions`);
		return { releases: (await this.http.getJson(backendURL, TerraformProviderVersions)).body.versions.map(({ version }) => ({ version })) };
	}
	async queryReleaseBackend(packageName, registryURL) {
		const backendLookUpName = `terraform-provider-${packageName.replace("hashicorp/", "")}`;
		const backendURL = joinUrlParts(registryURL, backendLookUpName, `index.json`);
		const res = (await this.http.getJson(backendURL, TerraformProviderReleaseBackend)).body;
		return {
			releases: Object.keys(res.versions).map((version) => ({ version })),
			sourceUrl: joinUrlParts("https://github.com/terraform-providers", backendLookUpName)
		};
	}
	async _getBuilds(registryURL, repository, version) {
		if (registryURL === TerraformProviderDatasource.hashicorpReleaseUrl) {
			const repositoryRegexResult = TerraformProviderDatasource.repositoryRegex.exec(repository)?.groups;
			if (!repositoryRegexResult) return null;
			const backendLookUpName = `terraform-provider-${repositoryRegexResult.packageName}`;
			let versionReleaseBackend;
			try {
				versionReleaseBackend = await this.getReleaseBackendIndex(backendLookUpName, version);
			} catch (err) {
				if (err instanceof ExternalHostError) throw err;
				logger.debug({
					err,
					backendLookUpName,
					version
				}, `Failed to retrieve builds for ${backendLookUpName} ${version}`);
				throw new ExternalHostError(err);
			}
			return versionReleaseBackend.builds;
		}
		const serviceDiscovery = await this.getTerraformServiceDiscoveryResult(registryURL);
		if (!serviceDiscovery) throw new ExternalHostError(/* @__PURE__ */ new Error(`Service discovery not found for ${registryURL}`));
		const backendURL = createSDBackendURL(registryURL, "providers.v1", serviceDiscovery, repository);
		const versionsResponse = (await this.http.getJson(`${backendURL}/versions`, TerraformRegistryVersions)).body;
		if (!versionsResponse.versions) throw new ExternalHostError(/* @__PURE__ */ new Error(`Failed to retrieve version list for ${backendURL}`));
		const builds = versionsResponse.versions.find((value) => value.version === version);
		if (!builds) throw new ExternalHostError(/* @__PURE__ */ new Error(`No builds found for ${repository}:${version} on ${registryURL}`));
		return await map(builds.platforms, async (platform) => {
			const buildURL = `${backendURL}/${version}/download/${platform.os}/${platform.arch}`;
			try {
				const res = (await this.http.getJson(buildURL, TerraformRegistryBuildResponse)).body;
				return {
					name: repository,
					url: res.download_url,
					version,
					...res
				};
			} catch (err) {
				/* v8 ignore next 3 -- hard to test */
				if (err instanceof ExternalHostError) throw err;
				logger.debug({
					err,
					url: buildURL
				}, "Failed to retrieve build");
				throw new ExternalHostError(err);
			}
		}, { concurrency: 4 });
	}
	getBuilds(registryURL, repository, version) {
		return withCache({
			namespace: `datasource-${TerraformProviderDatasource.id}`,
			key: `getBuilds:${registryURL}/${repository}/${version}`
		}, () => this._getBuilds(registryURL, repository, version));
	}
	async _getZipHashes(zipHashUrl) {
		let rawHashData;
		try {
			rawHashData = (await this.http.getText(zipHashUrl)).body;
		} catch (err) {
			/* v8 ignore next 3 -- hard to test */
			if (err instanceof ExternalHostError) throw err;
			logger.debug({
				err,
				zipHashUrl
			}, `Failed to retrieve zip hashes from ${zipHashUrl}`);
			return;
		}
		return rawHashData.trimEnd().split("\n").map((line) => line.split(/\s/)[0]);
	}
	getZipHashes(zipHashUrl) {
		return withCache({
			namespace: `datasource-${TerraformProviderDatasource.id}`,
			key: `getZipHashes:${zipHashUrl}`
		}, () => this._getZipHashes(zipHashUrl));
	}
	async _getReleaseBackendIndex(backendLookUpName, version) {
		return (await this.http.getJson(`${TerraformProviderDatasource.hashicorpReleaseUrl}/${backendLookUpName}/${version}/index.json`, VersionDetailResponse)).body;
	}
	getReleaseBackendIndex(backendLookUpName, version) {
		return withCache({
			namespace: `datasource-${TerraformProviderDatasource.id}`,
			key: `getReleaseBackendIndex:${backendLookUpName}/${version}`
		}, () => this._getReleaseBackendIndex(backendLookUpName, version));
	}
};
//#endregion
export { TerraformProviderDatasource };

//# sourceMappingURL=index.js.map