// The transforms have to leave a document the roster parser still understands,
// and the whole point is that what comes out the far end reads like the official
// card.  These tests go through the real parser rather than inspecting the DOM.

import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";

import { Create40kRoster11th } from "../roster40k-11th.js";
import { applyTransforms } from "./index.js";

const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), "__fixtures__");
const GAME_TYPE = "Warhammer 40,000 11th Edition";

/** Parse a fixture, optionally transforming it, and build the roster. */
function build(name, { transform }) {
	const xml = readFileSync(join(FIXTURES, `${name}.ros`), "utf8");
	const doc = new DOMParser().parseFromString(xml, "text/xml");
	if (transform) applyTransforms(doc);
	return Create40kRoster11th(doc, GAME_TYPE);
}

const units = (roster) => roster.forces.flatMap((force) => force.units);
const unitNamed = (roster, name) =>
	units(roster).find((unit) => unit.name.includes(name));

describe("the transformed roster still parses", () => {
	it.each(["bellisarius-cawl", "datasmith", "rangers", "skatros"])(
		"builds a roster from %s",
		(example) => {
			const roster = build(example, { transform: true });
			expect(roster).toBeTruthy();
			expect(roster.forces.length).toBeGreaterThan(0);
			expect(units(roster).length).toBeGreaterThan(0);
		},
	);
});

describe("merging duplicates", () => {
	it("turns two Skatros cards into one carrying both weapons", () => {
		const before = build("skatros", { transform: false });
		const after = build("skatros", { transform: true });

		const skatrosBefore = units(before).filter((unit) =>
			unit.name.includes("Skatros"),
		);
		const skatrosAfter = units(after).filter((unit) =>
			unit.name.includes("Skatros"),
		);
		expect(skatrosBefore.length).toBe(2);
		expect(skatrosAfter.length).toBe(1);

		// The single card now lists the weapon each copy was taken for.
		const weapons = [
			...skatrosAfter[0].rangedWeapons,
			...skatrosAfter[0].meleeWeapons,
		]
			.map((weapon) => weapon.name)
			.join(" | ");
		expect(weapons).toMatch(/jezzail/i);
		expect(weapons).toMatch(/arquebus/i);
	});

	it("brings an option's own ability along with the model that carries it", () => {
		const after = build("rangers", { transform: true });
		const rangers = unitNamed(after, "Ranger");

		const abilityNames = Object.values(rangers.abilities).flatMap((group) =>
			Array.from(group.keys()),
		);
		expect(abilityNames.join(" | ")).toMatch(/data-tether/i);
	});
});

describe("stripping Leader/Support", () => {
	// The Datasmith is a Support character, not a Leader; the transform strips
	// both, and this is the example that has one.
	it("leaves no attachment ability on the card", () => {
		const before = build("datasmith", { transform: false });
		const after = build("datasmith", { transform: true });

		const abilitiesOf = (roster) =>
			units(roster)
				.flatMap((unit) => Object.values(unit.abilities))
				.flatMap((group) => Array.from(group.keys()))
				.join(" | ");

		expect(abilitiesOf(before)).toMatch(/^Support \|/);
		expect(abilitiesOf(after)).not.toMatch(/Leader|Support/);
	});
});

describe("splitting choice abilities", () => {
	it("gives each Canticle its own row under one titled group", () => {
		const after = build("bellisarius-cawl", { transform: true });
		const cawl = unitNamed(after, "Cawl");

		const group = cawl.abilities["CANTICLES OF THE OMNISSIAH"];
		expect(group).toBeTruthy();
		expect(group.size).toBeGreaterThan(1);

		// The option text keeps GW's ^^keyword^^ / **emphasis** markers for the
		// renderer to format; only the row titles are plain text, since those
		// render as-is.
		expect([...group.values()].join("\n")).toMatch(/\^\^/);
		for (const name of group.keys()) {
			expect(name).not.toMatch(/\^\^|\*\*/);
		}
	});
});
