/**
 * agents-manifest-contract.test.ts — repo overlay contract for .agents/.
 *
 * Purpose: keep SF's repo-local agent overlay explicit and portable. The
 * manifest should describe prompts, policies, skills, and generated runtime
 * context clearly enough that Copilot-style agents and SF agree on ownership.
 *
 * Consumer: contributors editing .agents/, bundled skills, or generated
 * runtime context policy.
 */

import { existsSync, readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { parse as parseYaml } from "yaml";

const root = process.cwd();

function readManifest(): Record<string, any> {
	return parseYaml(
		readFileSync(join(root, ".agents", "manifest.yaml"), "utf-8"),
	);
}

describe(".agents manifest contract", () => {
	test("agents_overlay_has_expected_directory_layout", () => {
		const agentsRoot = join(root, ".agents");
		const requiredDirs = [
			"prompts",
			"prompts/snippets",
			"modes",
			"policies",
			"skills",
			"scopes",
			"profiles",
			"adapters",
			"schemas",
			"state",
		];

		for (const rel of requiredDirs) {
			expect(existsSync(join(agentsRoot, rel)), `${rel} missing`).toBe(true);
		}
		expect(existsSync(join(agentsRoot, "state", ".gitignore"))).toBe(true);
	});

	test("agents_manifest_declares_sf_overlay_and_agent_skills_format", () => {
		const manifest = readManifest();

		expect(manifest.specVersion).toBe("1.0.0");
		expect(manifest.x?.sf?.schemaVersion).toBe("sf-agents-overlay/v1");
		expect(manifest.x?.sf?.contract).toMatchObject({
			canonicalRepoOverlay: ".agents/manifest.yaml",
			canonicalSkillPayload: "SKILL.md",
			optionalSkillMetadata: "skill.yaml",
			skillMetadataRequired: false,
			bundledUserSkillRoot: "../src/resources/skills/",
			bundledWorkflowSkillRoot: "../src/resources/workflow-skills/",
			bundledAgentOverlayRoot:
				"../src/resources/agent-overlays/singularity-forge/",
			runtimeStateRoot: "../.sf/",
			runtimeStateSourceOfTruth: false,
			projectSkillRootPurpose:
				"optional repo-local user skills and overrides only",
			projectOverlayPurpose: "optional repo-local overrides only",
			projectLearningTarget:
				"reviewed repo-local .agents overrides proposed from .sf evidence",
		});
		expect(manifest.x?.sf?.layoutFormat).toEqual({
			name: "agents-folder",
			spec: "https://github.com/agentsfolder/spec",
			role: "repo-overlay-layout",
		});
		expect(manifest.x?.sf?.canonicalSkillFormat).toEqual({
			name: "agent-skills",
			spec: "https://agentskills.io/specification",
			entrypoint: "SKILL.md",
		});
		expect(manifest.resolution?.precedence).toEqual([
			"project",
			"global",
			"bundled",
		]);
		expect(manifest.prompts).toEqual({});
		expect(manifest.policies).toEqual({});
		expect(manifest.skills).toEqual({});
		expect(manifest.enabled?.prompts).toEqual([]);
		expect(manifest.enabled?.policies).toEqual([]);
		expect(manifest.enabled?.skills).toEqual([]);
		expect(manifest.x?.sf?.runtimeGenerated?.repoMap?.path).toBe(
			"../.sf/repo-map/",
		);
		expect(manifest.x?.sf?.runtimeGenerated?.repoMap?.gitignored).toBe(true);
		expect(manifest.x?.sf?.runtimeGenerated?.repoMap?.sourceOfTruth).toBe(
			false,
		);
	});

	test("agents_manifest_paths_exist_for_enabled_project_resources", () => {
		const manifest = readManifest();
		const agentsRoot = join(root, ".agents");
		const enabledPrompts: string[] = manifest.enabled?.prompts ?? [];
		const enabledPolicies: string[] = manifest.enabled?.policies ?? [];
		const enabledSkills: string[] = manifest.enabled?.skills ?? [];

		for (const name of enabledPrompts) {
			const rel = manifest.prompts?.[name]?.path;
			expect(rel, `prompt ${name} missing path`).toBeTruthy();
			expect(existsSync(join(agentsRoot, rel))).toBe(true);
		}
		for (const name of enabledPolicies) {
			const rel = manifest.policies?.[name]?.path;
			expect(rel, `policy ${name} missing path`).toBeTruthy();
			expect(existsSync(join(agentsRoot, rel))).toBe(true);
		}
		for (const name of enabledSkills) {
			const rel = manifest.skills?.[name]?.path;
			expect(rel, `skill ${name} missing path`).toBeTruthy();
			expect(existsSync(join(agentsRoot, rel, "SKILL.md"))).toBe(true);
		}
	});

	test("agents_manifest_declares_every_project_skill_directory", () => {
		const manifest = readManifest();
		const skillsRoot = join(root, ".agents", "skills");
		const skillDirs = readdirSync(skillsRoot, { withFileTypes: true })
			.filter((entry) => entry.isDirectory())
			.map((entry) => entry.name)
			.sort();

		expect(Object.keys(manifest.skills ?? {}).sort()).toEqual(skillDirs);

		for (const name of skillDirs) {
			const rel = manifest.skills?.[name]?.path;
			expect(rel, `skill ${name} missing path`).toBe(`skills/${name}/`);
			expect(existsSync(join(skillsRoot, name, "SKILL.md"))).toBe(true);
		}
	});

	test("agents_project_skill_overlay_is_empty_by_default", () => {
		const skillsRoot = join(root, ".agents", "skills");
		const entries = readdirSync(skillsRoot).filter(
			(name) => name !== ".gitkeep",
		);

		expect(entries).toEqual([]);
	});

	test("agents_override_directories_are_empty_by_default", () => {
		const agentsRoot = join(root, ".agents");
		const overrideDirs = [
			"prompts",
			"policies",
			"modes",
			"adapters",
			"scopes",
			"profiles",
		];

		for (const rel of overrideDirs) {
			const entries = readdirSync(join(agentsRoot, rel)).filter(
				(name) => name !== ".gitkeep" && name !== "snippets",
			);
			expect(entries, `${rel} should contain only override files`).toEqual([]);
		}
	});

	test("bundled_agent_overlay_defaults_live_in_resources", () => {
		const overlayRoot = join(
			root,
			"src",
			"resources",
			"agent-overlays",
			"singularity-forge",
		);

		expect(existsSync(join(overlayRoot, "prompts", "base.md"))).toBe(true);
		expect(existsSync(join(overlayRoot, "prompts", "project.md"))).toBe(true);
		expect(existsSync(join(overlayRoot, "policies", "default-safe.yaml"))).toBe(
			true,
		);
	});

	test("bundled_user_skills_and_hidden_workflow_skills_are_separate", () => {
		const userSkillRoot = join(root, "src", "resources", "skills");
		const workflowSkillRoot = join(root, "src", "resources", "workflow-skills");
		const userSkillDirs = readdirSync(userSkillRoot, { withFileTypes: true })
			.filter((entry) => entry.isDirectory())
			.map((entry) => entry.name)
			.sort();
		const workflowSkillDirs = readdirSync(workflowSkillRoot, {
			withFileTypes: true,
		})
			.filter((entry) => entry.isDirectory())
			.map((entry) => entry.name)
			.sort();

		expect(userSkillDirs).toEqual(["create-skill"]);
		expect(workflowSkillDirs).toEqual([
			"assumption-log",
			"context-lean",
			"error-routing",
			"handoff-readability",
			"irreversible-ops",
			"observe-first",
			"sf-debug-forensics",
			"sf-human-writing",
			"sf-metric-optimization",
			"sf-repo-orientation",
			"sf-vcs-hygiene",
			"state-discipline",
			"vertical-slice",
		]);
		for (const name of workflowSkillDirs) {
			expect(existsSync(join(workflowSkillRoot, name, "SKILL.md"))).toBe(true);
		}
	});
});
