import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { basename, join } from "node:path";
import type { NextApiRequest, NextApiResponse } from "next";

type ProjectMetadata = {
	name: string;
	path: string;
	kind: "active-sf" | "empty-sf" | "brownfield" | "blank";
	signals: {
		hasSfFolder: boolean;
		hasPlanningFolder: boolean;
		hasGitRepo: boolean;
		hasPackageJson: boolean;
		fileCount: number;
		hasMilestones?: boolean;
		hasCargo?: boolean;
		hasGoMod?: boolean;
		hasPyproject?: boolean;
		isMonorepo: boolean;
	};
	lastModified: number;
	progress?: {
		activeMilestone: string | null;
		activeSlice: string | null;
		phase: string | null;
		milestonesCompleted: number;
		milestonesTotal: number;
	} | null;
};

const EXCLUDED_DIRS = new Set(["node_modules", ".git"]);

function detectProject(path: string): ProjectMetadata["signals"] {
	const hasGitRepo = existsSync(join(path, ".git"));
	const hasSfFolder = existsSync(join(path, ".sf"));
	const hasPlanningFolder = existsSync(join(path, "planning"));
	const hasPackageJson = existsSync(join(path, "package.json"));
	const hasCargo = existsSync(join(path, "Cargo.toml"));
	const hasGoMod = existsSync(join(path, "go.mod"));
	const hasPyproject = existsSync(join(path, "pyproject.toml"));
	const hasMilestones = existsSync(join(path, ".sf", "milestones"));
	const fileCount = readdirSync(path).filter(
		(entry) => entry !== ".git",
	).length;
	const isMonorepo =
		existsSync(join(path, "pnpm-workspace.yaml")) ||
		existsSync(join(path, "lerna.json")) ||
		existsSync(join(path, "package.json"));
	return {
		hasSfFolder,
		hasPlanningFolder,
		hasGitRepo,
		hasPackageJson,
		fileCount,
		hasMilestones,
		hasCargo,
		hasGoMod,
		hasPyproject,
		isMonorepo,
	};
}

function readProgress(path: string): ProjectMetadata["progress"] {
	try {
		const content = readFileSync(join(path, ".sf", "STATE.md"), "utf8");
		let activeMilestone: string | null = null;
		let activeSlice: string | null = null;
		let phase: string | null = null;
		let milestonesCompleted = 0;
		let milestonesTotal = 0;

		for (const line of content.split("\n")) {
			const trimmed = line.trim();
			if (trimmed.startsWith("**Active Milestone:**")) {
				activeMilestone =
					trimmed.replace("**Active Milestone:**", "").trim() || null;
			} else if (trimmed.startsWith("**Active Slice:**")) {
				activeSlice = trimmed.replace("**Active Slice:**", "").trim() || null;
			} else if (trimmed.startsWith("**Phase:**")) {
				phase = trimmed.replace("**Phase:**", "").trim() || null;
			} else if (trimmed.startsWith("- \u2705")) {
				milestonesCompleted++;
				milestonesTotal++;
			} else if (trimmed.startsWith("- \u{1f504}")) {
				milestonesTotal++;
			}
		}
		return {
			activeMilestone,
			activeSlice,
			phase,
			milestonesCompleted,
			milestonesTotal,
		};
	} catch {
		return null;
	}
}

function projectMetadata(
	path: string,
	includeProgress: boolean,
): ProjectMetadata {
	const stat = statSync(path);
	const signals = detectProject(path);
	const kind = signals.hasSfFolder
		? signals.hasMilestones
			? "active-sf"
			: "empty-sf"
		: signals.hasGitRepo || signals.hasPackageJson
			? "brownfield"
			: "blank";
	return {
		name: basename(path),
		path,
		kind,
		signals,
		lastModified: stat.mtimeMs,
		...(includeProgress ? { progress: readProgress(path) } : {}),
	};
}

function discoverProjects(root: string, includeProgress: boolean) {
	const rootSignals = detectProject(root);
	if (rootSignals.hasSfFolder || rootSignals.isMonorepo) {
		return [projectMetadata(root, includeProgress)];
	}

	return readdirSync(root, { withFileTypes: true })
		.filter((entry) => entry.isDirectory())
		.filter((entry) => !entry.name.startsWith("."))
		.filter((entry) => !EXCLUDED_DIRS.has(entry.name))
		.map((entry) => join(root, entry.name))
		.filter((path) => {
			const signals = detectProject(path);
			return (
				signals.hasSfFolder || signals.hasGitRepo || signals.hasPackageJson
			);
		})
		.map((path) => projectMetadata(path, includeProgress))
		.sort((a, b) => a.name.localeCompare(b.name));
}

// Returns discovered projects under a configured development root.
export default function handler(req: NextApiRequest, res: NextApiResponse) {
	const rootParam = req.query.root ?? req.query.devRoot;
	const devRoot = Array.isArray(rootParam) ? rootParam[0] : rootParam;
	if (!devRoot) return res.status(400).json({ error: "Missing devRoot" });

	try {
		const includeProgress = req.query.detail === "true";
		res.status(200).json(discoverProjects(devRoot, includeProgress));
	} catch (e) {
		res.status(500).json({ error: (e as Error).message });
	}
}
