/**
 * auto-prompts-m005-migration.test.mjs — M005 S01/S02: Phase 3 v2 migration contracts.
 *
 * Purpose:
 *   1. Prove that the 5 simple builders migrated in M005 S01
 *      (discuss-project, discuss-requirements, research-project,
 *      discuss-milestone, rewrite-docs) produce prompts containing the
 *      expected artifact sections via composeUnitContext v2.
 *   2. Prove that all 26 declared unit types have manifests in UNIT_MANIFESTS
 *      with valid inline artifact orderings.
 *   3. Prove that prompt-cache-optimizer.js is no longer importable (dead code
 *      removal verification).
 *
 * Consumer: CI regression guard for M005 prompt modularization.
 */
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import * as AutoPrompts from "../auto-prompts.js";
import {
	closeDatabase,
	insertMilestone,
	insertSlice,
	insertTask,
	openDatabase,
} from "../sf-db.js";
import {
	ARTIFACT_KEYS,
	KNOWN_UNIT_TYPES,
	resolveManifest,
	UNIT_MANIFESTS,
} from "../unit-context-manifest.js";

let tempDirs = [];

function makeProject(opts = {}) {
	const dir = mkdtempSync(join(tmpdir(), "sf-m005-prompts-"));
	tempDirs.push(dir);
	const mid = opts.mid ?? "M910";
	const sid = opts.sid ?? "S01";
	const tid = opts.tid ?? "T01";
	mkdirSync(join(dir, ".sf", "milestones", mid, "slices", sid, "tasks"), {
		recursive: true,
	});
	writeFileSync(
		join(dir, ".sf", "milestones", mid, `${mid}-ROADMAP.md`),
		`# ${mid}: Test Milestone\n\n## ${sid}: Test Slice\n`,
	);
	writeFileSync(
		join(dir, ".sf", "milestones", mid, `${mid}-CONTEXT.md`),
		`# Context\n\nMilestone context for testing.\n`,
	);
	writeFileSync(
		join(dir, ".sf", "milestones", mid, "slices", sid, `${sid}-CONTEXT.md`),
		`# Slice Context\n\nSlice context for testing.\n`,
	);
	writeFileSync(
		join(dir, ".sf", "milestones", mid, "slices", sid, `${sid}-PLAN.md`),
		`# ${sid}: Test Slice\n\n## Tasks\n\n- ${tid}: Do the thing\n`,
	);
	return { dir, mid, sid, tid };
}

afterEach(() => {
	closeDatabase();
	for (const dir of tempDirs) {
		rmSync(dir, { recursive: true, force: true });
	}
	tempDirs = [];
});

// ─── R007: knowledge and graph are in ARTIFACT_KEYS ───────────────────────
describe("R007 — knowledge and graph in ARTIFACT_KEYS", () => {
	test("knowledge_is_declared_in_artifact_keys", () => {
		expect(ARTIFACT_KEYS).toContain("knowledge");
	});

	test("graph_is_declared_in_artifact_keys", () => {
		expect(ARTIFACT_KEYS).toContain("graph");
	});
});

// ─── R009: Builder ordering safety — manifest coverage ─────────────────────
describe("R009 — manifest coverage for all 26 unit types", () => {
	test("every_known_unit_type_has_a_manifest", () => {
		const missing = KNOWN_UNIT_TYPES.filter(
			(type) => resolveManifest(type) === null,
		);
		expect(missing).toHaveLength(0);
		expect(missing).toEqual([]);
	});

	test("every_unit_type_in_unit_manifests_is_known", () => {
		const extra = Object.keys(UNIT_MANIFESTS).filter(
			(type) => !KNOWN_UNIT_TYPES.includes(type),
		);
		expect(extra).toHaveLength(0);
		expect(extra).toEqual([]);
	});

	test("all_manifests_declare_inline_artifacts_array", () => {
		const withoutInline = KNOWN_UNIT_TYPES.filter((type) => {
			const m = resolveManifest(type);
			return !m?.artifacts?.inline || !Array.isArray(m.artifacts.inline);
		});
		expect(withoutInline).toHaveLength(0);
		expect(withoutInline).toEqual([]);
	});

	test("inline_artifacts_do_not_contain_undeclared_keys", () => {
		// Every artifact key referenced in any manifest's inline array must be
		// a registered ARTIFACT_KEY.
		const undeclared = [];
		for (const type of KNOWN_UNIT_TYPES) {
			const m = resolveManifest(type);
			if (!m?.artifacts?.inline) continue;
			for (const key of m.artifacts.inline) {
				if (!ARTIFACT_KEYS.includes(key)) {
					undeclared.push({ type, key });
				}
			}
		}
		expect(undeclared).toHaveLength(0);
		expect(undeclared).toEqual([]);
	});

	test("inline_artifacts_declare_computed_keys_only_when_registered", () => {
		// For each manifest that declares computed entries, verify those keys
		// are registered in ARTIFACT_KEYS.
		const issues = [];
		for (const type of KNOWN_UNIT_TYPES) {
			const m = resolveManifest(type);
			if (!m?.artifacts?.computed) continue;
			for (const key of m.artifacts.computed) {
				if (!ARTIFACT_KEYS.includes(key)) {
					issues.push({ type, key });
				}
			}
		}
		expect(issues).toHaveLength(0);
		expect(issues).toEqual([]);
	});
});

// ─── S01 Simple Builder Migration Tests ──────────────────────────────────
describe("buildDiscussProjectPrompt v2", () => {
	test("discuss_project_prompt_uses_composer_and_contains_inlined_context", async () => {
		const { dir } = makeProject({ mid: "M901" });
		openDatabase(join(dir, ".sf", "sf.db"));
		insertMilestone({
			id: "M901",
			title: "Discuss Project",
			status: "active",
			planning: { vision: "Test.", successCriteria: [] },
		});

		const prompt = await AutoPrompts.buildDiscussProjectPrompt(dir, "false");

		expect(prompt).toContain("## Inlined Context");
		// The composer wires project through inlineProjectFromDb
		expect(prompt).toContain("Project");
	});

	test("discuss_project_prompt_passes_structured_questions_variable", async () => {
		const { dir } = makeProject({ mid: "M902" });
		openDatabase(join(dir, ".sf", "sf.db"));
		insertMilestone({
			id: "M902",
			title: "Discuss Project",
			status: "active",
			planning: { vision: "Test.", successCriteria: [] },
		});

		const prompt = await AutoPrompts.buildDiscussProjectPrompt(dir, "false");

		// Template substitution: structuredQuestionsAvailable is substituted with "false"
		expect(prompt).toContain("**Structured questions available: false**");
	});
});

describe("buildDiscussRequirementsPrompt v2", () => {
	test("discuss_requirements_prompt_uses_composer_and_contains_inlined_context", async () => {
		const { dir } = makeProject({ mid: "M903" });
		openDatabase(join(dir, ".sf", "sf.db"));
		insertMilestone({
			id: "M903",
			title: "Discuss Requirements",
			status: "active",
			planning: { vision: "Test.", successCriteria: [] },
		});

		const prompt = await AutoPrompts.buildDiscussRequirementsPrompt(
			dir,
			"false",
		);

		expect(prompt).toContain("## Inlined Context");
		// Composer wires requirements through inlineRequirementsFromDb
		expect(prompt).toContain("Requirements");
	});
});

describe("buildResearchProjectPrompt v2", () => {
	test("research_project_prompt_uses_composer_and_contains_inlined_context", async () => {
		const { dir } = makeProject({ mid: "M904" });
		openDatabase(join(dir, ".sf", "sf.db"));
		insertMilestone({
			id: "M904",
			title: "Research Project",
			status: "active",
			planning: { vision: "Test.", successCriteria: [] },
		});

		const prompt = await AutoPrompts.buildResearchProjectPrompt(dir, "false");

		expect(prompt).toContain("## Inlined Context");
		// Composer wires project, requirements, decisions through DB helpers
		expect(prompt).toContain("Project");
	});
});

describe("buildDiscussMilestonePrompt v2", () => {
	test("discuss_milestone_prompt_uses_composer_and_contains_inlined_context", async () => {
		const { dir, mid } = makeProject({ mid: "M905" });
		openDatabase(join(dir, ".sf", "sf.db"));
		insertMilestone({
			id: mid,
			title: "Discuss Milestone",
			status: "active",
			planning: { vision: "Test.", successCriteria: [] },
		});

		const prompt = await AutoPrompts.buildDiscussMilestonePrompt(
			mid,
			"Discuss Milestone",
			dir,
			"false",
		);

		expect(prompt).toContain("## Inlined Context");
		// Composer wires milestone-context
		expect(prompt).toContain("Milestone Context");
	});

	test("discuss_milestone_prompt_appends_draft_seed_when_present", async () => {
		const { dir, mid } = makeProject({ mid: "M906" });
		openDatabase(join(dir, ".sf", "sf.db"));
		insertMilestone({
			id: mid,
			title: "Discuss Milestone",
			status: "active",
			planning: { vision: "Test.", successCriteria: [] },
		});
		// Write a CONTEXT-DRAFT
		writeFileSync(
			join(dir, ".sf", "milestones", mid, `${mid}-CONTEXT-DRAFT.md`),
			"# Draft Context\n\nThis is a draft.\n",
		);

		const prompt = await AutoPrompts.buildDiscussMilestonePrompt(
			mid,
			"Discuss Milestone",
			dir,
			"false",
		);

		expect(prompt).toContain("Prior Discussion (Draft Seed)");
	});
});

describe("buildRewriteDocsPrompt v2", () => {
	test("rewrite_docs_prompt_uses_composer_and_includes_inlined_context_header", async () => {
		const { dir, mid, sid } = makeProject({ mid: "M907", sid: "S01" });
		openDatabase(join(dir, ".sf", "sf.db"));
		insertMilestone({
			id: mid,
			title: "Rewrite Docs",
			status: "active",
			planning: { vision: "Test.", successCriteria: [] },
		});
		insertSlice({
			milestoneId: mid,
			id: sid,
			title: "Docs Slice",
			status: "active",
			risk: "low",
			depends: [],
			demo: "Docs done.",
			sequence: 1,
		});

		const prompt = await AutoPrompts.buildRewriteDocsPrompt(
			mid,
			"Rewrite Docs",
			{ id: sid, title: "Docs Slice" },
			dir,
			[],
		);

		// Composer produces the inlined context section header
		expect(prompt).toContain("## Inlined Context");
		// The composeUnitContext produces the section even when resolvers return null
		// (empty project/requirements/decisions in test fixture — fine for structural test)
	});

	test("rewrite_docs_prompt_includes_document_list_with_slice_plan_path", async () => {
		const { dir, mid, sid } = makeProject({ mid: "M908", sid: "S01" });
		openDatabase(join(dir, ".sf", "sf.db"));
		insertMilestone({
			id: mid,
			title: "Rewrite Docs",
			status: "active",
			planning: { vision: "Test.", successCriteria: [] },
		});
		insertSlice({
			milestoneId: mid,
			id: sid,
			title: "Docs Slice",
			status: "active",
			risk: "low",
			depends: [],
			demo: "Docs done.",
			sequence: 1,
		});

		const prompt = await AutoPrompts.buildRewriteDocsPrompt(
			mid,
			"Rewrite Docs",
			{ id: sid, title: "Docs Slice" },
			dir,
			[],
		);

		// The docList includes the slice plan path (rendered from {{documentList}})
		expect(prompt).toContain("S01-PLAN.md");
		// Document list section header is present
		expect(prompt).toContain("Documents to Review and Update");
	});
});

// ─── R009: Builder ordering safety — 11 remaining builders ────────────────
describe("buildPlanSlicePrompt v2", () => {
	test("plan_slice_prompt_uses_composer_and_contains_inlined_context", async () => {
		const { dir, mid, sid } = makeProject({ mid: "M920", sid: "S01" });
		openDatabase(join(dir, ".sf", "sf.db"));
		insertMilestone({
			id: mid,
			title: "Plan Slice",
			status: "active",
			planning: { vision: "Test.", successCriteria: [] },
		});
		insertSlice({
			milestoneId: mid,
			id: sid,
			title: "Test Slice",
			status: "active",
			risk: "low",
			depends: [],
			demo: "Done.",
			sequence: 1,
		});

		const prompt = await AutoPrompts.buildPlanSlicePrompt(
			mid,
			"Plan Slice",
			sid,
			"Test Slice",
			dir,
			"full",
			{},
		);

		expect(prompt).toContain("## Inlined Context");
		// renderSlicePrompt wires roadmap through inlineRoadmapExcerpt
		expect(prompt).toContain("Milestone Roadmap");
	});
});

describe("buildRefineSlicePrompt v2", () => {
	test("refine_slice_prompt_uses_composer_and_contains_slice_context", async () => {
		const { dir, mid, sid } = makeProject({ mid: "M921", sid: "S01" });
		openDatabase(join(dir, ".sf", "sf.db"));
		insertMilestone({
			id: mid,
			title: "Refine Slice",
			status: "active",
			planning: { vision: "Test.", successCriteria: [] },
		});
		insertSlice({
			milestoneId: mid,
			id: sid,
			title: "Test Slice",
			status: "active",
			risk: "low",
			depends: [],
			demo: "Done.",
			sequence: 1,
		});

		const prompt = await AutoPrompts.buildRefineSlicePrompt(
			mid,
			"Refine Slice",
			sid,
			"Test Slice",
			dir,
			"full",
			{},
		);

		expect(prompt).toContain("## Inlined Context");
		// renderSlicePrompt wires slice-context through inlineFileOptional
		expect(prompt).toContain("Slice Context");
	});
});

describe("buildExecuteTaskPrompt v2", () => {
	test("execute_task_prompt_inlines_task_plan_and_templates", async () => {
		const { dir, mid, sid, tid } = makeProject({
			mid: "M930",
			sid: "S01",
			tid: "T01",
		});
		openDatabase(join(dir, ".sf", "sf.db"));
		insertMilestone({
			id: mid,
			title: "Execute Task",
			status: "active",
			planning: { vision: "Test.", successCriteria: [] },
		});
		insertSlice({
			milestoneId: mid,
			id: sid,
			title: "Test Slice",
			status: "active",
			risk: "low",
			depends: [],
			demo: "Done.",
			sequence: 1,
		});

		const prompt = await AutoPrompts.buildExecuteTaskPrompt(
			mid,
			sid,
			"Test Slice",
			tid,
			"Test Task",
			dir,
			"full",
		);

		// buildExecuteTaskPrompt uses {{inlinedTemplates}} (task-summary + decisions + composed)
		expect(prompt).toContain("Inlined Task Plan");
		expect(prompt).toContain("Task Summary");
	});
});

describe("buildReactiveExecutePrompt v2", () => {
	test("reactive_execute_prompt_includes_ready_task_list_and_templates", async () => {
		const { dir, mid, sid } = makeProject({ mid: "M931", sid: "S01" });
		openDatabase(join(dir, ".sf", "sf.db"));
		insertMilestone({
			id: mid,
			title: "Reactive Execute",
			status: "active",
			planning: { vision: "Test.", successCriteria: [] },
		});
		insertSlice({
			milestoneId: mid,
			id: sid,
			title: "Test Slice",
			status: "active",
			risk: "low",
			depends: [],
			demo: "Done.",
			sequence: 1,
		});

		const prompt = await AutoPrompts.buildReactiveExecutePrompt(
			mid,
			"Reactive Execute",
			sid,
			"Test Slice",
			[],
			dir,
			undefined,
			{},
		);

		// buildReactiveExecutePrompt uses {{inlinedTemplates}} (task-summary template)
		expect(prompt).toContain("Task Summary");
		// Shows ready task count section
		expect(prompt).toContain("ready task");
	});
});

describe("buildCompleteMilestonePrompt v2", () => {
	test("complete_milestone_prompt_uses_composer_and_inlines_roadmap", async () => {
		const { dir, mid } = makeProject({ mid: "M940" });
		openDatabase(join(dir, ".sf", "sf.db"));
		insertMilestone({
			id: mid,
			title: "Complete Milestone",
			status: "active",
			planning: { vision: "Test.", successCriteria: [] },
		});

		const prompt = await AutoPrompts.buildCompleteMilestonePrompt(
			mid,
			"Complete Milestone",
			dir,
			"standard",
		);

		expect(prompt).toContain("## Inlined Context");
		// buildCompleteMilestonePrompt wires roadmap through inlineRoadmapExcerpt
		expect(prompt).toContain("Milestone Roadmap");
	});
});

describe("buildGateEvaluatePrompt v2", () => {
	test("gate_evaluate_prompt_includes_slice_plan_and_gates", async () => {
		const { dir, mid, sid } = makeProject({ mid: "M950", sid: "S01" });
		openDatabase(join(dir, ".sf", "sf.db"));
		insertMilestone({
			id: mid,
			title: "Gate Evaluate",
			status: "active",
			planning: { vision: "Test.", successCriteria: [] },
		});
		insertSlice({
			milestoneId: mid,
			id: sid,
			title: "Test Slice",
			status: "active",
			risk: "low",
			depends: [],
			demo: "Done.",
			sequence: 1,
		});

		const prompt = await AutoPrompts.buildGateEvaluatePrompt(
			mid,
			"Gate Evaluate",
			sid,
			"Test Slice",
			dir,
			undefined,
		);

		// buildGateEvaluatePrompt renders Quality Gate Evaluation
		expect(prompt).toContain("Quality Gate Evaluation");
		// Slice plan content is inlined
		expect(prompt).toContain("Slice Plan Context");
	});
});

// ─── R010 verification deferred to S02 ─────────────────────────────────────
// prompt-cache-optimizer.js removal is handled in M005/S02.
