import { regEx } from "../../../util/regex.js";
import { logger } from "../../../logger/index.js";
import { readLocalFile } from "../../../util/fs/index.js";
import { Json, LooseArray, LooseRecord, withDebugMessage } from "../../../util/schema-utils/index.js";
import { GitTagsDatasource } from "../../datasource/git-tags/index.js";
import { GithubTagsDatasource } from "../../datasource/github-tags/index.js";
import { BitbucketTagsDatasource } from "../../datasource/bitbucket-tags/index.js";
import api from "../../versioning/composer/index.js";
import { PackagistDatasource } from "../../datasource/packagist/index.js";
import { z } from "zod/v3";

//#region lib/modules/manager/composer/schema.ts
const ComposerRepo = z.object({
	type: z.literal("composer"),
	url: z.string().transform((url) => url.replace(/\/packages\.json$/, ""))
});
const GitRepo = z.object({
	type: z.enum(["vcs", "git"]).transform(() => "git"),
	url: z.string(),
	name: z.string().optional()
});
const PathRepo = z.object({
	type: z.literal("path"),
	url: z.string(),
	name: z.string().optional()
});
const PackageRepo = z.object({ type: z.literal("package") });
const Repo = z.discriminatedUnion("type", [
	ComposerRepo,
	GitRepo,
	PathRepo,
	PackageRepo
]);
const NamedRepo = z.discriminatedUnion("type", [
	ComposerRepo,
	GitRepo.extend({ name: z.string() }),
	PathRepo.extend({ name: z.string() }),
	PackageRepo
]);
z.object({ type: z.literal("disable-packagist") });
const bitbucketUrlRegex = regEx(/^(?:https:\/\/|git@)bitbucket\.org[/:](?<packageName>[^/]+\/[^/]+?)(?:\.git)?$/);
const ReposRecord = LooseRecord(z.union([Repo, z.literal(false)]), { onError: ({ error: err }) => {
	logger.debug({ err }, "Composer: error parsing repositories object");
} }).transform((repos) => {
	const result = [];
	for (const [name, repo] of Object.entries(repos)) {
		if (repo === false) {
			if (name === "packagist" || name === "packagist.org") result.push({ type: "disable-packagist" });
			continue;
		}
		if (repo.type === "path" || repo.type === "git") {
			result.push({
				name,
				...repo
			});
			continue;
		}
		if (repo.type === "composer") {
			result.push(repo);
			continue;
		}
	}
	return result;
});
const ReposArray = LooseArray(z.union([Repo, z.union([z.object({ packagist: z.literal(false) }), z.object({ "packagist.org": z.literal(false) })]).transform(() => ({ type: "disable-packagist" }))]), { onError: ({ error: err }) => {
	logger.debug({ err }, "Composer: error parsing repositories array");
} }).transform((repos) => {
	const result = [];
	for (let idx = 0; idx < repos.length; idx++) {
		const repo = repos[idx];
		if (repo.type === "path" || repo.type === "git") result.push({
			name: `__${idx}`,
			...repo
		});
		else result.push(repo);
	}
	return result;
});
const Repos = z.union([ReposRecord, ReposArray]).default([]).catch(withDebugMessage([], "Composer: invalid \"repositories\" field")).transform((repos) => {
	let packagist = true;
	const repoUrls = [];
	const gitRepos = {};
	const pathRepos = {};
	for (const repo of repos) if (repo.type === "composer") repoUrls.push(repo.url);
	else if (repo.type === "git") gitRepos[repo.name] = repo;
	else if (repo.type === "path") pathRepos[repo.name] = repo;
	else if (repo.type === "disable-packagist") packagist = false;
	if (packagist && repoUrls.length) repoUrls.push("https://repo.packagist.org");
	return {
		registryUrls: repoUrls.length ? repoUrls : null,
		gitRepos,
		pathRepos
	};
});
const RequireDefs = LooseRecord(z.string().transform((x) => x.trim())).catch({});
const PackageFile = z.object({
	type: z.string().optional(),
	config: z.object({ platform: z.object({ php: z.string() }) }).nullable().catch(null),
	repositories: Repos,
	require: RequireDefs,
	"require-dev": RequireDefs
}).transform(({ type: composerJsonType, config, repositories, require, "require-dev": requireDev }) => ({
	composerJsonType,
	config,
	repositories,
	require,
	requireDev
}));
const LockedPackage = z.object({
	name: z.string(),
	version: z.string()
});
const Lockfile = z.object({
	"plugin-api-version": z.string().optional(),
	packages: LooseArray(LockedPackage).catch([]),
	"packages-dev": LooseArray(LockedPackage).catch([])
}).transform(({ "plugin-api-version": pluginApiVersion, packages, "packages-dev": packagesDev }) => ({
	pluginApiVersion,
	packages,
	packagesDev
}));
const ComposerExtract = z.object({
	content: z.string(),
	fileName: z.string()
}).transform(({ content, fileName }) => {
	const lockfileName = fileName.replace(/\.json$/, ".lock");
	return {
		file: content,
		lockfileName,
		lockfile: lockfileName
	};
}).pipe(z.object({
	file: Json.pipe(PackageFile),
	lockfileName: z.string(),
	lockfile: z.string().transform((lockfileName) => readLocalFile(lockfileName, "utf8")).pipe(z.union([z.null(), z.string().pipe(Json).pipe(Lockfile).nullable().catch(withDebugMessage(null, "Composer: does not match schema"))]))
})).transform(({ file, lockfile, lockfileName }) => {
	const { composerJsonType, require, requireDev } = file;
	const { registryUrls, gitRepos, pathRepos } = file.repositories;
	const deps = [];
	const profiles = [{
		depType: "require",
		req: require,
		locked: lockfile?.packages ?? []
	}, {
		depType: "require-dev",
		req: requireDev,
		locked: lockfile?.packagesDev ?? []
	}];
	for (const { depType, req, locked } of profiles) for (const [depName, currentValue] of Object.entries(req)) {
		if (depName === "php") {
			deps.push({
				depType,
				depName,
				currentValue,
				datasource: GithubTagsDatasource.id,
				packageName: "containerbase/php-prebuild"
			});
			continue;
		}
		if (pathRepos[depName]) {
			deps.push({
				depType,
				depName,
				currentValue,
				skipReason: "path-dependency"
			});
			continue;
		}
		const dep = {
			depType,
			depName,
			currentValue
		};
		if (!depName.includes("/")) dep.skipReason = "unsupported";
		const lockedDep = locked.find((item) => item.name === depName);
		if (lockedDep && api.isVersion(lockedDep.version)) dep.lockedVersion = lockedDep.version.replace(regEx(/^v/i), "");
		const gitRepo = gitRepos[depName];
		if (gitRepo) {
			const bitbucketMatchGroups = bitbucketUrlRegex.exec(gitRepo.url)?.groups;
			if (bitbucketMatchGroups) {
				dep.datasource = BitbucketTagsDatasource.id;
				dep.packageName = bitbucketMatchGroups.packageName;
				deps.push(dep);
				continue;
			}
			dep.datasource = GitTagsDatasource.id;
			dep.packageName = gitRepo.url;
			deps.push(dep);
			continue;
		}
		dep.datasource = PackagistDatasource.id;
		if (registryUrls) dep.registryUrls = registryUrls;
		deps.push(dep);
	}
	if (!deps.length) return null;
	const res = { deps };
	if (composerJsonType) res.managerData = { composerJsonType };
	if (require.php) res.extractedConstraints = { php: require.php };
	if (lockfile) res.lockFiles = [lockfileName];
	return res;
});

//#endregion
export { ComposerExtract, Lockfile, PackageFile };
//# sourceMappingURL=schema.js.map