// SF Workflow Templates — Unit Tests
//
// Tests registry loading, template resolution, auto-detection, and listing.

import { describe, it } from "vitest";
import assert from "node:assert/strict";
import {
	autoDetect,
	formatStartUsage,
	getTemplateInfo,
	listTemplates,
	loadRegistry,
	loadWorkflowTemplate,
	resolveByName,
	scaffoldMilestoneSlices,
	workflowTemplateCommandDefinitions,
} from "../workflow-templates.ts";

describe("workflow-templates", () => {
	describe("Registry Loading", () => {
		it("should load registry with correct schema and templates", () => {
			const registry = loadRegistry();
			assert.ok(registry !== null, "Registry should load");
			assert.deepStrictEqual(
				registry.schemaVersion,
				1,
				"Registry schemaVersion should be 1",
			);
			assert.ok(
				Object.keys(registry.templates).length >= 8,
				"Should have at least 8 templates",
			);

			// Verify required template keys exist
			const expectedIds = [
				"full-project",
				"bugfix",
				"small-feature",
				"refactor",
				"spike",
				"hotfix",
				"security-audit",
				"dep-upgrade",
				"product-plan",
				"product-tracking",
			];
			for (const id of expectedIds) {
				assert.ok(
					id in registry.templates,
					`Template "${id}" should exist in registry`,
				);
			}

			// Verify each template has required fields
			for (const [id, entry] of Object.entries(registry.templates)) {
				assert.ok(
					typeof entry.name === "string" && entry.name.length > 0,
					`${id}: name should be non-empty string`,
				);
				assert.ok(
					typeof entry.description === "string" &&
						entry.description.length > 0,
					`${id}: description should be non-empty`,
				);
				assert.ok(
					typeof entry.file === "string" && entry.file.endsWith(".md"),
					`${id}: file should be a .md path`,
				);
				assert.ok(
					Array.isArray(entry.phases) && entry.phases.length > 0,
					`${id}: phases should be non-empty array`,
				);
				assert.ok(
					Array.isArray(entry.triggers) && entry.triggers.length > 0,
					`${id}: triggers should be non-empty array`,
				);
				assert.ok(
					loadWorkflowTemplate(id) !== null,
					`${id}: registered template file should load`,
				);
			}
		});
	});

	describe("Resolve by Name", () => {
		it("should resolve exact match", () => {
			const bugfix = resolveByName("bugfix");
			assert.ok(bugfix !== null, 'Should resolve "bugfix"');
			assert.deepStrictEqual(bugfix!.id, "bugfix", 'ID should be "bugfix"');
			assert.deepStrictEqual(
				bugfix!.confidence,
				"exact",
				"Exact name should have exact confidence",
			);
		});

		it("should resolve case-insensitive name match", () => {
			const spike = resolveByName("Research Spike");
			assert.ok(spike !== null, 'Should resolve "Research Spike" by name');
			assert.deepStrictEqual(spike!.id, "spike", "Should resolve to spike");
		});

		it("should resolve aliases", () => {
			const bug = resolveByName("bug");
			assert.ok(bug !== null, 'Should resolve "bug" alias');
			assert.deepStrictEqual(
				bug!.id,
				"bugfix",
				'Alias "bug" should map to bugfix',
			);

			const feat = resolveByName("feat");
			assert.ok(feat !== null, 'Should resolve "feat" alias');
			assert.deepStrictEqual(
				feat!.id,
				"small-feature",
				'Alias "feat" should map to small-feature',
			);

			const deps = resolveByName("deps");
			assert.ok(deps !== null, 'Should resolve "deps" alias');
			assert.deepStrictEqual(
				deps!.id,
				"dep-upgrade",
				'Alias "deps" should map to dep-upgrade',
			);

			const telemetry = resolveByName("telemetry");
			assert.ok(telemetry !== null, 'Should resolve "telemetry" alias');
			assert.deepStrictEqual(
				telemetry!.id,
				"product-tracking",
				'Alias "telemetry" should map to product-tracking',
			);

			const product = resolveByName("product");
			assert.ok(product !== null, 'Should resolve "product" alias');
			assert.deepStrictEqual(
				product!.id,
				"product-plan",
				'Alias "product" should map to product-plan',
			);
		});

		it("should return null for unknown template", () => {
			const missing = resolveByName("nonexistent-template");
			assert.ok(missing === null, "Should return null for unknown template");
		});
	});

	describe("Auto-Detection", () => {
		it("should detect bugfix from 'fix' keyword", () => {
			const fixMatches = autoDetect("fix the login button");
			assert.ok(
				fixMatches.length > 0,
				'Should detect matches for "fix the login button"',
			);
			assert.ok(
				fixMatches.some((m) => m.id === "bugfix"),
				"Should include bugfix in matches",
			);
		});

		it("should detect spike from 'research' keyword", () => {
			const researchMatches = autoDetect("research authentication libraries");
			assert.ok(
				researchMatches.length > 0,
				'Should detect matches for "research"',
			);
			assert.ok(
				researchMatches.some((m) => m.id === "spike"),
				"Should include spike in matches",
			);
		});

		it("should detect hotfix from 'urgent' keyword", () => {
			const urgentMatches = autoDetect("urgent production is down");
			assert.ok(
				urgentMatches.length > 0,
				'Should detect matches for "urgent"',
			);
			assert.ok(
				urgentMatches.some((m) => m.id === "hotfix"),
				"Should include hotfix in matches",
			);
		});

		it("should detect dep-upgrade from 'upgrade' keyword", () => {
			const upgradeMatches = autoDetect("upgrade react to v19");
			assert.ok(
				upgradeMatches.length > 0,
				'Should detect matches for "upgrade"',
			);
			assert.ok(
				upgradeMatches.some((m) => m.id === "dep-upgrade"),
				"Should include dep-upgrade in matches",
			);
		});

		it("should detect product-tracking from product analytics phrasing", () => {
			const trackingMatches = autoDetect(
				"create product analytics tracking plan",
			);
			assert.ok(
				trackingMatches.length > 0,
				'Should detect matches for "product analytics tracking plan"',
			);
			assert.ok(
				trackingMatches.some((m) => m.id === "product-tracking"),
				"Should include product-tracking in matches",
			);
		});

		it("should detect product-plan from planning phrasing", () => {
			const productPlanMatches = autoDetect(
				"plan the product we need to develop",
			);
			assert.ok(
				productPlanMatches.length > 0,
				'Should detect matches for "plan the product we need to develop"',
			);
			assert.ok(
				productPlanMatches.some((m) => m.id === "product-plan"),
				"Should include product-plan in matches",
			);
		});

		it("should score multi-word triggers higher", () => {
			const projectMatches = autoDetect("create a new project from scratch");
			const projectMatch = projectMatches.find((m) => m.id === "full-project");
			assert.ok(
				projectMatch !== undefined,
				'Should detect full-project for "from scratch"',
			);
		});

		it("should return no matches for empty input", () => {
			const emptyMatches = autoDetect("");
			assert.deepStrictEqual(
				emptyMatches.length,
				0,
				"Empty input should return no matches",
			);
		});
	});

	describe("List Templates", () => {
		it("should list templates with header and usage hint", () => {
			const output = listTemplates();
			assert.ok(output.includes("Workflow Templates"), "Should have header");
			assert.ok(output.includes("bugfix"), "Should list bugfix");
			assert.ok(output.includes("spike"), "Should list spike");
			assert.ok(output.includes("hotfix"), "Should list hotfix");
			assert.ok(
				output.includes("product-tracking"),
				"Should list product-tracking",
			);
			assert.ok(output.includes("product-plan"), "Should list product-plan");
			assert.ok(output.includes("/sf start"), "Should include usage hint");
		});
	});

	describe("Start Usage and Completions", () => {
		it("should include all templates in command definitions and usage", () => {
			const registry = loadRegistry();
			const commandDefs = workflowTemplateCommandDefinitions();
			const commandIds = commandDefs.map((entry) => entry.cmd);
			for (const id of Object.keys(registry.templates)) {
				assert.ok(
					commandIds.includes(id),
					`Start completions should include ${id}`,
				);
			}

			const usage = formatStartUsage();
			assert.ok(
				usage.includes("product-plan"),
				"Usage should include product-plan",
			);
			assert.ok(
				usage.includes("/sf workflow run"),
				"Usage should distinguish YAML workflow definitions",
			);
		});
	});

	describe("Template Info", () => {
		it("should return info for known templates", () => {
			const info = getTemplateInfo("bugfix");
			assert.ok(info !== null, "Should return info for bugfix");
			assert.ok(info!.includes("Bug Fix"), "Should include template name");
			assert.ok(info!.includes("triage"), "Should include phase names");
			assert.ok(info!.includes("Triggers"), "Should include triggers section");
		});

		it("should return null for unknown template", () => {
			const missing = getTemplateInfo("nonexistent");
			assert.ok(missing === null, "Should return null for unknown template");
		});
	});

	describe("Load Workflow Template Content", () => {
		it("should load bugfix template content", () => {
			const content = loadWorkflowTemplate("bugfix");
			assert.ok(content !== null, "Should load bugfix template");
			assert.ok(
				content!.includes("Bugfix Workflow"),
				"Should contain workflow title",
			);
			assert.ok(
				content!.includes("Phase 1: Triage"),
				"Should contain triage phase",
			);
			assert.ok(
				content!.includes("Phase 4: Ship"),
				"Should contain ship phase",
			);
		});

		it("should load hotfix template content", () => {
			const hotfixContent = loadWorkflowTemplate("hotfix");
			assert.ok(hotfixContent !== null, "Should load hotfix template");
			assert.ok(
				hotfixContent!.includes("Hotfix Workflow"),
				"Should contain hotfix title",
			);
		});

		it("should load product-tracking template content", () => {
			const productTrackingContent = loadWorkflowTemplate("product-tracking");
			assert.ok(
				productTrackingContent !== null,
				"Should load product-tracking template",
			);
			assert.ok(
				productTrackingContent!.includes("Product Tracking Workflow"),
				"Should contain product-tracking title",
			);
			assert.ok(
				productTrackingContent!.includes("Phase 5: Implement Tracking"),
				"Should contain implementation phase",
			);
		});

		it("should load product-plan template content", () => {
			const productPlanContent = loadWorkflowTemplate("product-plan");
			assert.ok(productPlanContent !== null, "Should load product-plan template");
			assert.ok(
				productPlanContent!.includes("Product Plan Workflow"),
				"Should contain product-plan title",
			);
			assert.ok(
				productPlanContent!.includes("Phase 3: Plan Implementation Slices"),
				"Should contain implementation slice planning phase",
			);
		});

		it("should return null for unknown template", () => {
			const missingContent = loadWorkflowTemplate("nonexistent");
			assert.ok(
				missingContent === null,
				"Should return null for unknown template",
			);
		});
	});

	describe("Milestone Scaffolding", () => {
		it("should scaffold bugfix milestone slices", () => {
			const bugfixSlices = scaffoldMilestoneSlices("bugfix");
			assert.ok(bugfixSlices !== null, "Should scaffold bugfix milestone slices");
			assert.equal(
				bugfixSlices!.length,
				3,
				"Bugfix scaffold should create 3 slices",
			);
			assert.equal(bugfixSlices![0].sliceId, "S01");
			assert.ok(bugfixSlices![1].depends.includes("S01"));
		});

		it("should scaffold via alias", () => {
			const featureSlices = scaffoldMilestoneSlices("feat");
			assert.ok(featureSlices !== null, "Should scaffold via alias");
			assert.equal(
				featureSlices![0].title,
				"Define the user-facing contract",
			);
		});

		it("should scaffold product-plan milestone slices", () => {
			const productPlanSlices = scaffoldMilestoneSlices("product-plan");
			assert.ok(
				productPlanSlices !== null,
				"Should scaffold product-plan milestone slices",
			);
			assert.equal(
				productPlanSlices!.length,
				4,
				"Product-plan scaffold should create 4 slices",
			);
			assert.equal(
				productPlanSlices![0].title,
				"Model the product and value flow",
			);
			assert.ok(productPlanSlices![2].depends.includes("S02"));
		});

		it("should return null for unknown template", () => {
			const missingScaffold = scaffoldMilestoneSlices("nonexistent");
			assert.equal(missingScaffold, null, "Unknown template should not scaffold");
		});
	});
});
