import { execFileSync } from "node:child_process";
import { basename } from "node:path";

let cache = null;
let lastFetch = 0;
function getRepoName(cwd) {
	try {
		const root = execFileSync("git", ["rev-parse", "--show-toplevel"], {
			cwd,
			encoding: "utf-8",
			stdio: ["pipe", "pipe", "ignore"],
			timeout: 1500,
		}).trim();
		return root ? basename(root) : basename(cwd) || null;
	} catch {
		return basename(cwd) || null;
	}
}
function getLastCommit(cwd) {
	try {
		const raw = execFileSync("git", ["log", "-1", "--format=%cr|%s"], {
			cwd,
			encoding: "utf-8",
			stdio: ["pipe", "pipe", "ignore"],
			timeout: 1500,
		}).trim();
		const sep = raw.indexOf("|");
		if (sep > 0) {
			return {
				timeAgo: raw.slice(0, sep).replace(/ ago$/, ""),
				message: raw.slice(sep + 1),
			};
		}
	} catch {
		/* ignore */
	}
	return null;
}
function getDiffStats(cwd) {
	try {
		const raw = execFileSync("git", ["diff", "HEAD", "--stat"], {
			cwd,
			encoding: "utf-8",
			stdio: ["pipe", "pipe", "ignore"],
			timeout: 1500,
		});
		let added = 0;
		let deleted = 0;
		let modified = 0;
		for (const line of raw.split("\n")) {
			const addMatch = line.match(/(\d+) insertion/);
			const delMatch = line.match(/(\d+) deletion/);
			if (addMatch || delMatch) {
				const a = addMatch ? parseInt(addMatch[1], 10) : 0;
				const d = delMatch ? parseInt(delMatch[1], 10) : 0;
				if (a) added += a;
				if (d) deleted += d;
				if (a || d) modified++;
			}
		}
		return { added, deleted, modified };
	} catch {
		return { added: 0, deleted: 0, modified: 0 };
	}
}
export function refreshGitStatus(cwd) {
	const now = Date.now();
	if (now - lastFetch < 400 && cache) return cache;
	lastFetch = now;
	const repo = getRepoName(cwd);
	let branch = null;
	try {
		branch =
			execFileSync("git", ["branch", "--show-current"], {
				cwd,
				encoding: "utf-8",
				stdio: ["pipe", "pipe", "ignore"],
				timeout: 1500,
			}).trim() || null;
	} catch {
		cache = {
			repo,
			branch: null,
			dirty: false,
			untracked: false,
			ahead: 0,
			behind: 0,
			added: 0,
			deleted: 0,
			modified: 0,
			lastCommit: null,
		};
		return cache;
	}
	try {
		const status = execFileSync("git", ["status", "--porcelain"], {
			cwd,
			encoding: "utf-8",
			stdio: ["pipe", "pipe", "ignore"],
			timeout: 1500,
		});
		const lines = status.split("\n").filter((l) => l.length > 2);
		const dirty = lines.some((l) => {
			const x = l[0] ?? " ";
			const y = l[1] ?? " ";
			return (x !== "?" && x !== " " && x !== "!") || (y !== " " && y !== "?");
		});
		const untracked = lines.some((l) => l.startsWith("??"));
		let ahead = 0;
		let behind = 0;
		try {
			const ab = execFileSync(
				"git",
				["rev-list", "--left-right", "--count", "HEAD...@{u}"],
				{
					cwd,
					encoding: "utf-8",
					stdio: ["pipe", "pipe", "ignore"],
					timeout: 1500,
				},
			).trim();
			const [a, b] = ab.split("\t").map((n) => parseInt(n, 10));
			ahead = Number.isNaN(a) ? 0 : a;
			behind = Number.isNaN(b) ? 0 : b;
		} catch {
			/* no upstream */
		}
		const diff = getDiffStats(cwd);
		const lastCommit = getLastCommit(cwd);
		cache = {
			repo,
			branch,
			dirty,
			untracked,
			ahead,
			behind,
			...diff,
			lastCommit,
		};
	} catch {
		cache = {
			repo,
			branch,
			dirty: false,
			untracked: false,
			ahead: 0,
			behind: 0,
			added: 0,
			deleted: 0,
			modified: 0,
			lastCommit: getLastCommit(cwd),
		};
	}
	return cache;
}
export function invalidateGitStatus() {
	lastFetch = 0;
}
