import { describe, expect, it } from "vitest";
import { tourDraftGraphSchema, tourSchema } from "./tour.js";

const validTour = {
	id: "tr_7f3a9c",
	version: 1,
	name: "Onboarding nouveaux utilisateurs",
	trigger: { urlPattern: "/dashboard*" },
	steps: [
		{ id: "st_intro", type: "modal", title: "Bienvenue" },
		{
			id: "st_create",
			type: "tooltip",
			title: "Creez un projet",
			target: { candidates: [{ kind: "testid", value: "create-project" }] },
			advance: "element-click",
		},
		{
			id: "st_role",
			type: "question",
			title: "Quelle est votre equipe ?",
			options: [
				{ id: "op_tech", label: "Technique", next: "st_intro" },
				{ id: "op_sales", label: "Commerciale", next: "st_create" },
			],
		},
	],
};

/** Renvoie les chemins d'erreur sous la forme utilisee par l'API (section 5.2). */
function errorPaths(input: unknown): string[] {
	const result = tourSchema.safeParse(input);
	if (result.success) return [];
	return result.error.issues.map((issue) => issue.path.join("."));
}

describe("tourSchema", () => {
	it("accepte un parcours complet et applique les valeurs par defaut", () => {
		const result = tourSchema.parse(validTour);
		const [, tooltip] = result.steps;

		expect(result.trigger.frequency).toBe("once");
		expect(tooltip).toMatchObject({ placement: "bottom", highlight: false });
	});

	it("ignore les champs inconnus au lieu de les rejeter", () => {
		// Compatibilite ascendante : un SDK ancien doit lire un parcours recent (section 7.1).
		const result = tourSchema.parse({ ...validTour, futureField: "inconnu" });
		expect(result).not.toHaveProperty("futureField");
	});

	it("refuse une option qui pointe vers une etape inexistante", () => {
		const tour = structuredClone(validTour);
		tour.steps[2].options[0].next = "st_absent";

		expect(errorPaths(tour)).toContain("steps.2.options.0.next");
	});

	it("refuse une etape dont le next ne resout pas", () => {
		const tour = structuredClone(validTour);
		tour.steps[0].next = "st_absent";

		expect(errorPaths(tour)).toContain("steps.0.next");
	});

	it("refuse deux etapes portant le meme identifiant", () => {
		const tour = structuredClone(validTour);
		tour.steps[1].id = "st_intro";

		expect(errorPaths(tour)).toContain("steps.1.id");
	});

	it("refuse une infobulle sans cible", () => {
		const tour = {
			...validTour,
			steps: [
				validTour.steps[0],
				{ id: "st_create", type: "tooltip", title: "Creez un projet" },
				validTour.steps[2],
			],
		};

		expect(errorPaths(tour)).toContain("steps.1.target");
	});

	it("refuse une cible sans aucun candidat", () => {
		const tour = structuredClone(validTour);
		tour.steps[1].target.candidates = [];

		expect(errorPaths(tour)).toContain("steps.1.target.candidates");
	});

	it("refuse une option sans libelle", () => {
		const tour = structuredClone(validTour);
		tour.steps[2].options[0].label = "";

		expect(errorPaths(tour)).toContain("steps.2.options.0.label");
	});

	it("refuse une question offrant moins de deux options", () => {
		const tour = structuredClone(validTour);
		tour.steps[2].options = [tour.steps[2].options[0]];

		expect(errorPaths(tour)).toContain("steps.2.options");
	});
});

describe("tourDraftGraphSchema", () => {
	const draftBase = {
		name: validTour.name,
		trigger: validTour.trigger,
		steps: validTour.steps,
	};

	it("accepte un brouillon sans identifiant ni version", () => {
		const result = tourDraftGraphSchema.parse(draftBase);
		expect(result.name).toBe(validTour.name);
		expect(result).not.toHaveProperty("id");
		expect(result).not.toHaveProperty("version");
	});

	it("refuse un brouillon dont un branchement ne resout pas", () => {
		const draft = structuredClone(draftBase);
		draft.steps[0] = { ...draft.steps[0], next: "st_absent" };
		const result = tourDraftGraphSchema.safeParse(draft);
		expect(result.success).toBe(false);
		if (result.success) return;
		expect(result.error.issues.map((issue) => issue.path.join("."))).toContain(
			"steps.0.next",
		);
	});
});
