import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { buildLoreIndex, normalizeName, parseLore } from "./lore";

const csv = (...lines) => `name|legend\n${lines.join("\n")}\n`;

describe("normalizeName", () => {
	it("collapses the casing, punctuation and quote style that vary between exports", () => {
		expect(normalizeName("Tech-priest Dominus")).toBe("tech priest dominus");
		expect(normalizeName("Tech-Priest Dominus")).toBe("tech priest dominus");
		expect(normalizeName("Khorne’s Hounds")).toBe("khorne s hounds");
		expect(normalizeName("Khorne's Hounds")).toBe("khorne s hounds");
	});

	it("survives missing input", () => {
		expect(normalizeName(undefined)).toBe("");
		expect(normalizeName(null)).toBe("");
	});
});

describe("parseLore", () => {
	it("reads the pipe-delimited export, BOM and CRLF included", () => {
		const rows = parseLore("﻿name|legend\r\nCustodian Guard|Stalwart.\r\n");
		expect(rows).toEqual([
			{ name: "Custodian Guard", legend: "Stalwart.", faction: "" },
		]);
	});

	it("drops rows whose legend has not been filled in", () => {
		const rows = parseLore(csv("Webway Gate|", "Bonesinger|Sings to bone."));
		expect(rows.map((row) => row.name)).toEqual(["Bonesinger"]);
	});

	it("locates columns by header, so an added faction column just works", () => {
		const rows = parseLore(
			"legend|faction|name\nSings to bone.|Aeldari|Bonesinger\n",
		);
		expect(rows).toEqual([
			{ name: "Bonesinger", legend: "Sings to bone.", faction: "Aeldari" },
		]);
	});

	it("returns nothing for junk rather than throwing", () => {
		expect(parseLore("")).toEqual([]);
		expect(parseLore("unit;text\nfoo;bar")).toEqual([]);
	});
});

describe("buildLoreIndex", () => {
	it("matches names that differ only in case or punctuation", () => {
		const { lookup } = buildLoreIndex(csv("Tech-priest Dominus|Theocrat."));
		expect(lookup("Tech-Priest Dominus")).toBe("Theocrat.");
	});

	it("matches a plural roster name against a singular entry", () => {
		const { lookup } = buildLoreIndex(csv("Myphitic Blight-hauler|Belching."));
		expect(lookup("Myphitic Blight-haulers")).toBe("Belching.");
	});

	it("matches a singular roster name against a plural entry", () => {
		const { lookup } = buildLoreIndex(csv("Plaguebearers|Foot soldiers."));
		expect(lookup("Plaguebearer")).toBe("Foot soldiers.");
	});

	it("strips a faction prefix the export does not carry", () => {
		const { lookup } = buildLoreIndex(csv("Chaos Spawn|Roiling flesh."));
		expect(lookup("Thousand Sons Chaos Spawn")).toBe("Roiling flesh.");
	});

	it("will not match on a single trailing word", () => {
		const { lookup } = buildLoreIndex(csv("Guard|Some other unit entirely."));
		expect(lookup("Custodian Guard")).toBeUndefined();
	});

	it("prefers the entry whose faction matches the card", () => {
		const { lookup } = buildLoreIndex(
			"name|legend|faction\n" +
				"Plaguebearers|Daemon version, which is the longer of the two.|Chaos Daemons\n" +
				"Plaguebearers|Guard version.|Death Guard\n",
		);
		expect(lookup("Plaguebearers", "Death Guard")).toBe("Guard version.");
		expect(lookup("Plaguebearers", "Chaos Daemons")).toBe(
			"Daemon version, which is the longer of the two.",
		);
	});

	it("falls back to the longest legend when the faction cannot decide it", () => {
		const { lookup } = buildLoreIndex(
			csv("Servitors|Short.", "Servitors|The longer, fuller entry."),
		);
		expect(lookup("Servitors")).toBe("The longer, fuller entry.");
		expect(lookup("Servitors", "Adeptus Mechanicus")).toBe(
			"The longer, fuller entry.",
		);
	});

	it("returns undefined for a unit the export does not cover", () => {
		const { lookup } = buildLoreIndex(csv("Bonesinger|Sings to bone."));
		expect(lookup("Rein and Raus")).toBeUndefined();
		expect(lookup("")).toBeUndefined();
	});
});

// Guards the shipped export itself: a re-export that changes the delimiter or
// the header names would otherwise fail silently, every card simply losing its
// flavour text.
describe("public/Lore.csv", () => {
	const index = buildLoreIndex(readFileSync("public/Lore.csv", "utf8"));

	it("parses into a usable number of entries", () => {
		expect(index.size).toBeGreaterThan(1000);
	});

	it("covers the units in the bundled example rosters", () => {
		for (const name of [
			"Custodian Guard",
			"Bladeguard Veteran Squad",
			"Plague Marines",
			"Myphitic Blight-haulers",
			"Thousand Sons Chaos Spawn",
		]) {
			expect(index.lookup(name), name).toBeTruthy();
		}
	});
});
