import * as fs from "node:fs";
import * as path from "node:path";

// --- CONFIGURATION ---
const ROOT_DIR: string = process.cwd();
const ROUTERS_DIR: string = path.join(ROOT_DIR, "src/server/api/routers");
const SCAN_DIRS: string[] = [
	path.join(ROOT_DIR, "src/pages"),
	path.join(ROOT_DIR, "src/components"),
	path.join(ROOT_DIR, "src/forms"),
	path.join(ROOT_DIR, "src/hooks"),
	path.join(ROOT_DIR, "src/lib"),
	path.join(ROOT_DIR, "src/providers"),
];
const IGNORE_DIRS: string[] = ["node_modules", ".next", "dist"];

// Regex to find tRPC procedure definitions.
// Matches `procedureName: someProcedure` where the procedure type is one of the known base procedures.
// This handles chained .input()/.output() before .query/.mutation/.subscription.
const PROCEDURE_REGEX: RegExp =
	/([a-zA-Z0-9_]+)\s*:\s*(?:adminProcedure|protectedProcedure|publicProcedure)\b/g;

interface ProcedureDefinition {
	name: string;
	file: string;
	line: number;
}

function getAllFiles(dir: string, ext: string[] = [".ts", ".tsx"]): string[] {
	let results: string[] = [];
	if (!fs.existsSync(dir)) return results;

	const list: string[] = fs.readdirSync(dir);
	list.forEach((file: string) => {
		const fullPath: string = path.join(dir, file);
		const stat = fs.statSync(fullPath);

		if (stat?.isDirectory()) {
			if (!IGNORE_DIRS.includes(file)) {
				results = results.concat(getAllFiles(fullPath, ext));
			}
		} else {
			if (ext.some((e) => file.endsWith(e))) {
				results.push(fullPath);
			}
		}
	});
	return results;
}

/**
 * Calculates the line number of a specific index in a string
 */
function getLineNumber(content: string, index: number): number {
	const linesBefore = content.substring(0, index).split("\n");
	return linesBefore.length;
}

function run(): void {
	console.log("🔍 Scanning for tRPC procedures...\n");

	const routerFiles: string[] = getAllFiles(ROUTERS_DIR);
	const procedures: ProcedureDefinition[] = [];

	// 1. Extract procedure names and their specific line numbers
	routerFiles.forEach((filePath: string) => {
		const content: string = fs.readFileSync(filePath, "utf8");
		let match: RegExpExecArray | null;
		match = PROCEDURE_REGEX.exec(content);
		while (match !== null) {
			if (match[1]) {
				procedures.push({
					name: match[1],
					file: filePath,
					line: getLineNumber(content, match.index),
				});
			}
			match = PROCEDURE_REGEX.exec(content);
		}
	});

	console.log(`Found ${procedures.length} procedures. Checking for usage...\n`);

	// 2. Get all files to scan
	let scanFiles: string[] = [];
	SCAN_DIRS.forEach((dir: string) => {
		scanFiles = scanFiles.concat(getAllFiles(dir));
	});

	// 3. Check for usage
	const usedNames = new Set<string>();

	scanFiles.forEach((filePath: string) => {
		const content: string = fs.readFileSync(filePath, "utf8");

		procedures.forEach((proc) => {
			if (filePath === proc.file) return;

			// Search for name with word boundaries
			const usageRegex = new RegExp(`\\b${proc.name}\\b`);
			if (usageRegex.test(content)) {
				usedNames.add(proc.name);
			}
		});
	});

	// 4. Report results
	const unused = procedures.filter((p) => !usedNames.has(p.name));

	if (unused.length === 0) {
		console.log("✅ All good! No unused tRPC procedures found.");
	} else {
		console.log(
			`🚨 Found ${unused.length} potentially unused tRPC procedures:\n`,
		);
		unused.forEach((p) => {
			const relativePath: string = path.relative(ROOT_DIR, p.file);
			// Format: path/to/file.ts:line
			// Most IDEs (VS Code, WebStorm) make this clickable in the terminal
			console.log(`- ${p.name} -> ${relativePath}:${p.line}`);
		});
	}
}

run();
