import { describe, expect, it } from "vitest";
import type { Manifest } from "../contracts/extension.js";
import { resolveActivationOrder } from "./dag.js";

function manifest(id: string, deps?: readonly string[]): Manifest {
	const base: Manifest = {
		id,
		name: id,
		version: "1.0.0",
		apiVersion: "^0.1.0",
		trust: "bundled",
	};
	if (deps !== undefined) {
		return { ...base, dependsOn: deps };
	}
	return base;
}

describe("resolveActivationOrder", () => {
	it("returns empty array for no extensions", () => {
		expect(resolveActivationOrder([])).toEqual([]);
	});

	it("returns a single extension with no deps", () => {
		const result = resolveActivationOrder([manifest("a")]);
		expect(result.map((m) => m.id)).toEqual(["a"]);
	});

	it("orders a linear chain (A → B → C)", () => {
		const a = manifest("a");
		const b = manifest("b", ["a"]);
		const c = manifest("c", ["b"]);

		const result = resolveActivationOrder([c, b, a]);
		const ids = result.map((m) => m.id);

		expect(ids.indexOf("a")).toBeLessThan(ids.indexOf("b"));
		expect(ids.indexOf("b")).toBeLessThan(ids.indexOf("c"));
	});

	it("orders a diamond (A → B, A → C, B → D, C → D)", () => {
		const a = manifest("a");
		const b = manifest("b", ["a"]);
		const c = manifest("c", ["a"]);
		const d = manifest("d", ["b", "c"]);

		const result = resolveActivationOrder([d, c, b, a]);
		const ids = result.map((m) => m.id);

		expect(ids.indexOf("a")).toBeLessThan(ids.indexOf("b"));
		expect(ids.indexOf("a")).toBeLessThan(ids.indexOf("c"));
		expect(ids.indexOf("b")).toBeLessThan(ids.indexOf("d"));
		expect(ids.indexOf("c")).toBeLessThan(ids.indexOf("d"));
	});

	it("handles independent sets (no deps between them)", () => {
		const a = manifest("a");
		const b = manifest("b");
		const c = manifest("c");

		const result = resolveActivationOrder([a, b, c]);
		expect(result).toHaveLength(3);
		expect(result.map((m) => m.id).sort()).toEqual(["a", "b", "c"]);
	});

	it("throws on a cycle (A → B → A)", () => {
		const a = manifest("a", ["b"]);
		const b = manifest("b", ["a"]);

		expect(() => resolveActivationOrder([a, b])).toThrow(/cycle/i);
	});

	it("throws on a larger cycle (A → B → C → A)", () => {
		const a = manifest("a", ["c"]);
		const b = manifest("b", ["a"]);
		const c = manifest("c", ["b"]);

		expect(() => resolveActivationOrder([a, b, c])).toThrow(/cycle/i);
	});

	it("throws on a missing dependency", () => {
		const a = manifest("a", ["nonexistent"]);

		expect(() => resolveActivationOrder([a])).toThrow(/not available/);
	});

	it("throws on duplicate extension ids", () => {
		const a1 = manifest("a");
		const a2 = manifest("a");

		expect(() => resolveActivationOrder([a1, a2])).toThrow(/duplicate/i);
	});

	it("handles mixed independent and dependent extensions", () => {
		const a = manifest("a");
		const b = manifest("b", ["a"]);
		const c = manifest("c");
		const d = manifest("d", ["c"]);

		const result = resolveActivationOrder([a, b, c, d]);
		const ids = result.map((m) => m.id);

		expect(ids.indexOf("a")).toBeLessThan(ids.indexOf("b"));
		expect(ids.indexOf("c")).toBeLessThan(ids.indexOf("d"));
		expect(result).toHaveLength(4);
	});
});
