// Phase 28 — MailStore threading projection tests. Exercises the
// `entries` derived rune end-to-end with handcrafted inbox + sent
// fixtures; the network surface (GalaxyClient) is left null since
// these tests do not call init / setGame.

import { describe, expect, it } from "vitest";

import {
	MailStore,
	type MailListEntry,
	type MailStandalone,
	type MailThread,
} from "../src/lib/mail-store.svelte";
import type { MailMessage } from "../src/api/diplomail";

function makeMessage(overrides: Partial<MailMessage>): MailMessage {
	return {
		messageId: overrides.messageId ?? crypto.randomUUID(),
		gameId: overrides.gameId ?? "00000000-0000-0000-0000-000000000001",
		gameName: overrides.gameName ?? "Test game",
		kind: overrides.kind ?? "personal",
		senderKind: overrides.senderKind ?? "player",
		senderUserId: overrides.senderUserId ?? null,
		senderUsername: overrides.senderUsername ?? null,
		senderRaceName: overrides.senderRaceName ?? null,
		subject: overrides.subject ?? "",
		body: overrides.body ?? "",
		bodyLang: overrides.bodyLang ?? "en",
		broadcastScope: overrides.broadcastScope ?? "single",
		createdAt: overrides.createdAt ?? new Date(0),
		recipientUserId:
			overrides.recipientUserId ?? "00000000-0000-0000-0000-000000000099",
		recipientUserName: overrides.recipientUserName ?? "self",
		recipientRaceName: overrides.recipientRaceName ?? null,
		readAt: overrides.readAt ?? null,
		deletedAt: overrides.deletedAt ?? null,
		translatedSubject: overrides.translatedSubject ?? null,
		translatedBody: overrides.translatedBody ?? null,
		translationLang: overrides.translationLang ?? null,
		translator: overrides.translator ?? null,
	};
}

describe("MailStore.entries", () => {
	it("groups inbox + sent messages into per-race threads", () => {
		const store = new MailStore();
		const greysIncoming = makeMessage({
			messageId: "m1",
			senderKind: "player",
			senderRaceName: "Greys",
			body: "hi",
			createdAt: new Date("2026-05-15T12:00:00Z"),
		});
		const greysOutgoing = makeMessage({
			messageId: "m2",
			senderKind: "player",
			senderRaceName: "Self",
			recipientRaceName: "Greys",
			body: "reply",
			createdAt: new Date("2026-05-15T12:05:00Z"),
		});
		store.inbox = [greysIncoming];
		store.sent = [greysOutgoing];

		const entries = store.entries;
		expect(entries.length).toBe(1);
		const entry = entries[0];
		expect(entry.kind).toBe("thread");
		const thread = entry as MailThread;
		expect(thread.raceName).toBe("Greys");
		expect(thread.messages.map((m) => m.messageId)).toEqual(["m1", "m2"]);
	});

	it("surfaces system and admin messages as stand-alone items", () => {
		const store = new MailStore();
		const system = makeMessage({
			messageId: "sys-1",
			kind: "admin",
			senderKind: "system",
			subject: "game.paused: details",
			createdAt: new Date("2026-05-15T13:00:00Z"),
		});
		const admin = makeMessage({
			messageId: "adm-1",
			kind: "admin",
			senderKind: "admin",
			createdAt: new Date("2026-05-15T13:05:00Z"),
		});
		store.inbox = [system, admin];

		const entries: MailListEntry[] = store.entries;
		expect(entries.every((e) => e.kind === "standalone")).toBe(true);
		const standalones = entries as MailStandalone[];
		expect(standalones.map((e) => e.message.messageId).sort()).toEqual(
			["adm-1", "sys-1"],
		);
	});

	it("treats the local player's broadcasts as stand-alone outgoing", () => {
		const store = new MailStore();
		const broadcast = makeMessage({
			messageId: "br-1",
			senderKind: "player",
			senderRaceName: "Self",
			recipientRaceName: "Greys",
			broadcastScope: "game_broadcast",
			createdAt: new Date("2026-05-15T14:00:00Z"),
		});
		store.sent = [broadcast];

		const entries = store.entries;
		expect(entries.length).toBe(1);
		expect(entries[0].kind).toBe("standalone");
	});

	it("collapses fan-out rows for the same broadcast message_id", () => {
		// `/sent` returns one row per recipient for broadcast and admin
		// sends so the admin tooling can render the materialised
		// audience. The list pane must collapse the duplicates by
		// message_id, otherwise the {#each} key in `thread-list.svelte`
		// repeats and Svelte 5 surfaces `each_key_duplicate`.
		const store = new MailStore();
		const base = {
			messageId: "br-shared",
			senderKind: "player" as const,
			senderRaceName: "Self",
			broadcastScope: "game_broadcast" as const,
			createdAt: new Date("2026-05-15T14:00:00Z"),
		};
		store.sent = [
			makeMessage({ ...base, recipientRaceName: "Greys" }),
			makeMessage({ ...base, recipientRaceName: "Bajori" }),
			makeMessage({ ...base, recipientRaceName: "Romulans" }),
		];

		const entries = store.entries;
		expect(entries.length).toBe(1);
		expect(entries[0].kind).toBe("standalone");
		const standalone = entries[0] as MailStandalone;
		expect(standalone.message.messageId).toBe("br-shared");
	});

	it("counts unread incoming messages in unreadCount", () => {
		const store = new MailStore();
		store.inbox = [
			makeMessage({ readAt: null, createdAt: new Date(1) }),
			makeMessage({ readAt: new Date(2), createdAt: new Date(2) }),
			makeMessage({ readAt: null, createdAt: new Date(3) }),
		];
		expect(store.unreadCount).toBe(2);
	});
});
