import { log } from "../log.js";
import { defineSource } from "../source.js";
import { ensureArray } from "../utilities/template-helpers.js";
import { fetchWithRetry } from "../utilities/fetch.js";
import { pythonPkgInfoSource } from "./python-pkg-info.js";
import { pythonPyprojectTomlSource } from "./python-pyproject-toml.js";
import { pythonSetupCfgSource } from "./python-setup-cfg.js";
import { pythonSetupPySource } from "./python-setup-py.js";
import { z } from "zod";
//#region src/lib/sources/python-pypi-registry.ts
const pypiResponseSchema = z.object({
	info: z.object({
		version: z.string(),
		yanked: z.boolean().optional(),
		yanked_reason: z.string().nullable().optional()
	}),
	releases: z.record(z.string(), z.array(z.unknown())),
	urls: z.array(z.object({
		size: z.number().optional(),
		upload_time_iso_8601: z.string().optional()
	}))
});
const pypistatsRecentSchema = z.object({ data: z.object({
	last_day: z.number(),
	last_month: z.number(),
	last_week: z.number()
}) });
const pypistatsOverallSchema = z.object({ data: z.array(z.object({
	category: z.string(),
	downloads: z.number()
})) });
const pythonPypiRegistrySource = defineSource({
	async discover(context) {
		if (context.options.offline) {
			log.debug("Skipping Python PyPI registry data source since we're in offline mode");
			return [];
		}
		let packageNames = [];
		packageNames = ensureArray(context.metadata?.pythonPyprojectToml).filter((value) => !hasPrivateClassifier(value.data.project?.classifiers, value.data.project?.name, context.options.path)).map((value) => value.data.project?.name).filter((value) => value !== void 0);
		if (packageNames.length === 0) packageNames = ensureArray(context.metadata?.pythonSetupCfg).filter((value) => !hasPrivateClassifier(value.data.classifiers, value.data.name, context.options.path)).map((value) => value.data.name).filter((value) => value !== void 0);
		if (packageNames.length === 0) packageNames = ensureArray(context.metadata?.pythonSetupPy).filter((value) => !hasPrivateClassifier(value.data.classifiers, value.data.name, context.options.path)).map((value) => value.data.name).filter((value) => value !== void 0);
		if (packageNames.length === 0) packageNames = ensureArray(context.metadata?.pythonPkgInfo).filter((value) => !hasPrivateClassifier(value.data.classifiers, value.data.name, context.options.path)).map((value) => value.data.name).filter((value) => value !== void 0);
		if (packageNames.length === 0) {
			if (![
				"pythonPyprojectToml",
				"pythonSetupCfg",
				"pythonSetupPy",
				"pythonPkgInfo"
			].some((key) => context.completedSources?.has(key))) {
				log.debug(`Missing python package names in source context metadata for ${context.options.path}, extracting them now...`);
				packageNames = ensureArray(await pythonPyprojectTomlSource.extract(context)).filter((value) => !hasPrivateClassifier(value.data.project?.classifiers, value.data.project?.name, context.options.path)).map((value) => value.data.project?.name).filter((value) => value !== void 0);
				if (packageNames.length === 0) packageNames = ensureArray(await pythonSetupCfgSource.extract(context)).filter((value) => !hasPrivateClassifier(value.data.classifiers, value.data.name, context.options.path)).map((value) => value.data.name).filter((value) => value !== void 0);
				if (packageNames.length === 0) packageNames = ensureArray(await pythonSetupPySource.extract(context)).filter((value) => !hasPrivateClassifier(value.data.classifiers, value.data.name, context.options.path)).map((value) => value.data.name).filter((value) => value !== void 0);
				if (packageNames.length === 0) packageNames = ensureArray(await pythonPkgInfoSource.extract(context)).filter((value) => !hasPrivateClassifier(value.data.classifiers, value.data.name, context.options.path)).map((value) => value.data.name).filter((value) => value !== void 0);
			}
		}
		return packageNames;
	},
	key: "pythonPypiRegistry",
	async parse(input) {
		log.debug("Extracting PyPI metadata...");
		const name = input;
		const [pypiResult, pypistatsRecentResult, pypistatsOverallResult] = await Promise.all([
			fetchWithRetry(`https://pypi.org/pypi/${encodeURIComponent(name)}/json`).then(async (response) => {
				if (!response.ok) return;
				return pypiResponseSchema.parse(await response.json());
			}).catch(() => void 0),
			fetchWithRetry(`https://pypistats.org/api/packages/${encodeURIComponent(name)}/recent`).then(async (response) => {
				if (!response.ok) return;
				return pypistatsRecentSchema.parse(await response.json());
			}).catch(() => void 0),
			fetchWithRetry(`https://pypistats.org/api/packages/${encodeURIComponent(name)}/overall?mirrors=false`).then(async (response) => {
				if (!response.ok) return;
				return pypistatsOverallSchema.parse(await response.json());
			}).catch(() => void 0)
		]);
		if (!pypiResult) return;
		const latestUploadTime = pypiResult.urls[0]?.upload_time_iso_8601;
		const sizeBytes = pypiResult.urls[0]?.size;
		const info = {
			publishDateLatest: latestUploadTime,
			releaseCount: Object.keys(pypiResult.releases).length,
			sizeBytes,
			url: `https://pypi.org/project/${encodeURIComponent(name)}/`,
			versionLatest: pypiResult.info.version
		};
		if (pypiResult.info.yanked) {
			info.yanked = true;
			if (pypiResult.info.yanked_reason) info.yankedReason = pypiResult.info.yanked_reason;
		}
		if (pypistatsRecentResult) {
			info.downloadsDaily = pypistatsRecentResult.data.last_day;
			info.downloadsMonthly = pypistatsRecentResult.data.last_month;
			info.downloadsWeekly = pypistatsRecentResult.data.last_week;
		}
		if (pypistatsOverallResult) info.downloads180Days = pypistatsOverallResult.data.reduce((sum, entry) => sum + entry.downloads, 0) || void 0;
		return {
			data: info,
			source: `https://pypi.org/project/${encodeURIComponent(name)}/`
		};
	},
	phase: 2
});
/**
* Check if classifiers contain a "Private ::" prefix, which signals that the
* package is not intended for publication on PyPI. PyPI itself rejects uploads
* with this classifier.
*/
function hasPrivateClassifier(classifiers, packageName, path) {
	const isPrivate = classifiers?.some((c) => c.startsWith("Private :: ")) ?? false;
	if (isPrivate) log.debug(`Skipping PyPI registry lookup for "${packageName}" in "${path}" because it has a "Private ::" classifier`);
	return isPrivate;
}
//#endregion
export { pythonPypiRegistrySource };
