import { log } from "../log.js";
import { getMatches } from "../file-matching.js";
import { batchMap } from "../utilities/formatting.js";
import { defineSource } from "../source.js";
import { stat } from "node:fs/promises";
import { join, resolve } from "node:path";
import { simpleGit } from "simple-git";
//#region src/lib/sources/git-stats.ts
const gitStatsSource = defineSource({
	async discover(context) {
		return (await getMatches(context.options, [".git/config"])).map((value) => resolve(value, "../../"));
	},
	key: "gitStats",
	async parse(input, context) {
		log.debug("Extracting git statistics metadata...");
		const git = simpleGit(input);
		const [statusResult, logResult, branchResult, tagResult, commitDateFirst, trackedFiles, remotes, submoduleCount, hasLfs] = await Promise.all([
			git.status(),
			git.log(),
			git.branch(),
			git.tags(),
			git.raw([
				"rev-list",
				"--max-parents=0",
				"HEAD",
				"--format=%aI"
			]).then((output) => {
				return output.trim().split("\n").filter((line) => !line.startsWith("commit ")).at(-1) ?? void 0;
			}),
			git.raw(["ls-files"]).then((output) => output.trim().split("\n").filter(Boolean)),
			git.getRemotes(),
			git.raw(["submodule", "status"]).then((output) => {
				const count = output.trim().split("\n").filter(Boolean).length;
				return count > 0 ? count : void 0;
			}).catch(() => void 0),
			git.raw(["lfs", "ls-files"]).then((output) => output.trim().length > 0 ? true : void 0).catch(() => void 0)
		]);
		const trackedSizeBytes = (await batchMap(trackedFiles, async (file) => {
			try {
				return (await stat(join(context.options.path, file))).size;
			} catch {
				return 0;
			}
		})).reduce((sum, size) => sum + size, 0);
		const contributors = new Set(logResult.all.map((commit) => commit.author_email));
		let tagDateLatest;
		const tagNameLatest = tagResult.latest ?? void 0;
		if (tagNameLatest) try {
			tagDateLatest = (await git.raw([
				"log",
				"-1",
				"--format=%aI",
				tagNameLatest
			])).trim() || void 0;
		} catch {}
		const versionTagPattern = /^v?\d+(?:\.\d+){1,2}$/;
		let tagReleaseCount;
		let tagVersionLatest;
		let tagVersionDateLatest;
		try {
			const versionTags = (await git.raw(["tag", "--sort=-creatordate"])).trim().split("\n").filter(Boolean).filter((tag) => versionTagPattern.test(tag));
			tagReleaseCount = versionTags.length > 0 ? versionTags.length : void 0;
			const match = versionTags[0];
			if (match) {
				const tagDate = await git.raw([
					"log",
					"-1",
					"--format=%aI",
					match
				]);
				tagVersionLatest = match.replace(/^v/, "");
				tagVersionDateLatest = tagDate.trim() || void 0;
			}
		} catch {}
		const remoteStatusEntries = await Promise.all(remotes.map(async (remote) => {
			for (const branch of ["main", "master"]) {
				const reference = `${remote.name}/${branch}`;
				try {
					const [ahead, behind] = (await git.raw([
						"rev-list",
						"--left-right",
						"--count",
						`HEAD...${reference}`
					])).trim().split("	").map(Number);
					return [remote.name, {
						ahead,
						behind
					}];
				} catch {}
			}
		}));
		const remoteStatus = {};
		for (const entry of remoteStatusEntries) if (entry) remoteStatus[entry[0]] = entry[1];
		const remoteStatusValues = Object.values(remoteStatus);
		const totalAhead = remoteStatusValues.length > 0 ? remoteStatusValues.reduce((sum, s) => sum + s.ahead, 0) : void 0;
		const totalBehind = remoteStatusValues.length > 0 ? remoteStatusValues.reduce((sum, s) => sum + s.behind, 0) : void 0;
		return {
			data: {
				branchCount: branchResult.all.length,
				branchCurrent: branchResult.current,
				commitCount: logResult.total,
				commitDateFirst,
				commitDateLast: logResult.latest?.date ?? void 0,
				contributorCount: contributors.size,
				hasLfs,
				isClean: statusResult.isClean(),
				isDirty: !statusResult.isClean(),
				isRemoteAhead: Object.values(remoteStatus).some((s) => s.behind > 0) || void 0,
				remoteCount: remotes.length,
				remoteStatus: Object.keys(remoteStatus).length > 0 ? remoteStatus : void 0,
				submoduleCount,
				tagCount: tagResult.all.length,
				tagDateLatest,
				tagNameLatest,
				tagReleaseCount,
				tagVersionDateLatest,
				tagVersionLatest,
				totalAhead,
				totalBehind,
				trackedFileCount: trackedFiles.length,
				trackedSizeBytes,
				uncommittedFileCount: statusResult.files.length
			},
			source: input
		};
	},
	phase: 1
});
//#endregion
export { gitStatsSource };
