/**
 * Doc Scaffold Checker — validates the agentic docs scaffold is filled in.
 *
 * Purpose: Mechanical enforcement of harness-engineering principles. After
 * bootstrap or milestone close, check that scaffold files contain real content
 * beyond the template stubs. Reports findings so the agent knows what needs
 * attention — never blocks, only surfaces.
 *
 * Consumer: bootstrapProject (after scaffold init), milestone close workflows.
 */

import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { join } from "node:path";

export interface ScaffoldCheck {
	file: string;       // relative path from repo root
	status: "ok" | "empty" | "stub" | "missing";
	lines: number;
	note: string;
}

export interface DocScaffoldReport {
	checkedAt: string;
	repoRoot: string;
	checks: ScaffoldCheck[];
	summary: {
		total: number;
		ok: number;
		empty: number;
		stub: number;
		missing: number;
	};
}

/** Files created by ensureAgenticDocsScaffold that should contain real content. */
const SCAFFOLD_FILES = [
	// Root routing
	"AGENTS.md",
	"ARCHITECTURE.md",
	// docs/ structure
	"docs/AGENTS.md",
	"docs/PLANS.md",
	"docs/DESIGN.md",
	"docs/FRONTEND.md",
	"docs/QUALITY_SCORE.md",
	"docs/RELIABILITY.md",
	"docs/SECURITY.md",
	"docs/product-specs/index.md",
	"docs/product-specs/new-user-onboarding.md",
	"docs/design-docs/index.md",
	"docs/design-docs/core-beliefs.md",
	"docs/exec-plans/active/index.md",
	"docs/exec-plans/completed/index.md",
	"docs/exec-plans/tech-debt-tracker.md",
	"docs/exec-plans/AGENTS.md",
	"docs/records/index.md",
	"docs/records/AGENTS.md",
	"docs/RECORDS_KEEPER.md",
	// src/ and tests/ routing
	"src/AGENTS.md",
	"tests/AGENTS.md",
] as const;

// Minimum lines considered "real content" vs stub. Template stubs are ~3-8 lines.
const STUB_LINE_COUNT = 10;

// Files that are allowed to stay as stubs (index/placeholder files)
const STUB_ALLOWED = new Set([
	"docs/product-specs/index.md",
	"docs/design-docs/index.md",
	"docs/exec-plans/active/index.md",
	"docs/exec-plans/completed/index.md",
	"docs/records/index.md",
]);

function countContentLines(content: string): number {
	// Count non-empty, non-comment lines
	return content
		.split("\n")
		.filter((line) => {
			const trimmed = line.trim();
			return trimmed.length > 0 && !trimmed.startsWith("//") && !trimmed.startsWith("#");
		})
		.length;
}

function checkFile(repoRoot: string, relPath: string): ScaffoldCheck {
	const fullPath = join(repoRoot, relPath);
	if (!existsSync(fullPath)) {
		return {
			file: relPath,
			status: "missing",
			lines: 0,
			note: "File does not exist — scaffold not run or was interrupted",
		};
	}

	let content: string;
	try {
		const stat = statSync(fullPath);
		if (stat.isDirectory()) {
			return { file: relPath, status: "stub", lines: 0, note: "Is a directory, expected a file" };
		}
		content = readFileSync(fullPath, "utf-8");
	} catch {
		return { file: relPath, status: "stub", lines: 0, note: "Could not read file" };
	}

	const lines = content.split("\n").filter((l) => l.trim().length > 0).length;
	const contentLines = countContentLines(content);

	if (lines === 0) {
		return { file: relPath, status: "empty", lines: 0, note: "File is empty" };
	}

	if (contentLines < STUB_LINE_COUNT) {
		const note = STUB_ALLOWED.has(relPath)
			? `Stub file (${lines} lines) — acceptable for index/placeholder`
			: `Stub file (${lines} lines) — needs real content beyond template`;
		return {
			file: relPath,
			status: STUB_ALLOWED.has(relPath) ? "ok" : "stub",
			lines,
			note,
		};
	}

	return {
		file: relPath,
		status: "ok",
		lines,
		note: `Contains ${contentLines} content lines`,
	};
}

/**
 * Check all scaffold files in a repo. Returns a structured report.
 * Never throws — all errors are caught and reported as stub/missing.
 */
export function checkDocsScaffold(repoRoot: string): DocScaffoldReport {
	const checks: ScaffoldCheck[] = [];
	for (const file of SCAFFOLD_FILES) {
		checks.push(checkFile(repoRoot, file));
	}

	const summary = {
		total: checks.length,
		ok: checks.filter((c) => c.status === "ok").length,
		empty: checks.filter((c) => c.status === "empty").length,
		stub: checks.filter((c) => c.status === "stub").length,
		missing: checks.filter((c) => c.status === "missing").length,
	};

	return {
		checkedAt: new Date().toISOString(),
		repoRoot,
		checks,
		summary,
	};
}

/**
 * Format a report as human-readable text for logging to stderr.
 */
export function formatDocCheckReport(report: DocScaffoldReport): string {
	const lines: string[] = [];
	lines.push(`[doc-checker] Scaffold check — ${report.checkedAt}`);
	lines.push(
		`  ${report.summary.ok}/${report.summary.total} files OK`
			+ (report.summary.stub > 0 ? ` · ${report.summary.stub} need content` : "")
			+ (report.summary.missing > 0 ? ` · ${report.summary.missing} missing` : "")
			+ (report.summary.empty > 0 ? ` · ${report.summary.empty} empty` : ""),
	);

	const issues = report.checks.filter((c) => c.status !== "ok");
	if (issues.length > 0) {
		lines.push("  Files needing attention:");
		for (const issue of issues) {
			lines.push(`    [${issue.status}] ${issue.file} — ${issue.note}`);
		}
	} else {
		lines.push("  All scaffold files contain real content.");
	}

	return lines.join("\n");
}
