// Regression for the user-reported "black canvas after returning to
// /map via the dropdown menu". Walks every non-map view the menu
// exposes, returns to /map through the same menu, and asserts the
// renderer mounts cleanly: status `ready`, no mount-error overlay,
// no console errors, AND the canvas pixel buffer is non-empty (a
// fully-black screen would mean the renderer mounted but never
// drew anything onto the new canvas).

import { fromJson, type JsonValue } from "@bufbuild/protobuf";
import { expect, test, type Page } from "@playwright/test";
import { ByteBuffer } from "flatbuffers";

import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
import { UUID } from "../../src/proto/galaxy/fbs/common";
import { UserGamesOrderGet } from "../../src/proto/galaxy/fbs/order";
import { GameReportRequest } from "../../src/proto/galaxy/fbs/report";
import { forgeExecuteCommandResponseJson } from "./fixtures/sign-response";
import {
	buildMyGamesListPayload,
	type GameFixture,
} from "./fixtures/lobby-fbs";
import { buildReportPayload } from "./fixtures/report-fbs";
import {
	buildOrderGetResponsePayload,
	buildOrderResponsePayload,
} from "./fixtures/order-fbs";

const SESSION_ID = "phase-21-map-roundtrip-session";
const GAME_ID = "21212121-cafe-cafe-cafe-cafecafecaff";

async function mockGateway(page: Page): Promise<void> {
	const game: GameFixture = {
		gameId: GAME_ID,
		gameName: "Map Roundtrip",
		gameType: "private",
		status: "running",
		ownerUserId: "user-1",
		minPlayers: 2,
		maxPlayers: 8,
		enrollmentEndsAtMs: BigInt(Date.now() + 86_400_000),
		createdAtMs: BigInt(Date.now() - 86_400_000),
		updatedAtMs: BigInt(Date.now()),
		currentTurn: 1,
	};

	await page.route(
		"**/galaxy.gateway.v1.EdgeGateway/ExecuteCommand",
		async (route) => {
			const reqText = route.request().postData();
			if (reqText === null) {
				await route.fulfill({ status: 400 });
				return;
			}
			const req = fromJson(
				ExecuteCommandRequestSchema,
				JSON.parse(reqText) as JsonValue,
			);

			let resultCode = "ok";
			let payload: Uint8Array;
			switch (req.messageType) {
				case "lobby.my.games.list":
					payload = buildMyGamesListPayload([game]);
					break;
				case "user.games.report": {
					GameReportRequest.getRootAsGameReportRequest(
						new ByteBuffer(req.payloadBytes),
					).gameId(new UUID());
					payload = buildReportPayload({
						turn: 1,
						mapWidth: 4000,
						mapHeight: 4000,
						race: "Earthlings",
						players: [{ name: "Earthlings", drive: 1 }],
						localPlanets: [
							{
								number: 1,
								name: "Earth",
								x: 2000,
								y: 2000,
								size: 1000,
								resources: 5,
								population: 800,
								industry: 600,
							},
						],
					});
					break;
				}
				case "user.games.order":
					payload = buildOrderResponsePayload(GAME_ID, [], Date.now());
					break;
				case "user.games.order.get":
					UserGamesOrderGet.getRootAsUserGamesOrderGet(
						new ByteBuffer(req.payloadBytes),
					);
					payload = buildOrderGetResponsePayload(GAME_ID, [], Date.now(), false);
					break;
				default:
					resultCode = "internal_error";
					payload = new Uint8Array();
			}

			const body = await forgeExecuteCommandResponseJson({
				requestId: req.requestId,
				timestampMs: BigInt(Date.now()),
				resultCode,
				payloadBytes: payload,
			});
			await route.fulfill({
				status: 200,
				contentType: "application/json",
				body,
			});
		},
	);

	await page.route(
		"**/galaxy.gateway.v1.EdgeGateway/SubscribeEvents",
		async () => {
			await new Promise<void>(() => {});
		},
	);
}

async function bootSession(page: Page): Promise<void> {
	await page.goto("/__debug/store");
	await expect(page.getByTestId("debug-store-ready")).toBeVisible();
	await page.waitForFunction(() => window.__galaxyDebug?.ready === true);
	await page.evaluate(() => window.__galaxyDebug!.clearSession());
	await page.evaluate(
		(id) => window.__galaxyDebug!.setDeviceSessionId(id),
		SESSION_ID,
	);
	await page.evaluate(
		(gameId) => window.__galaxyDebug!.clearOrderDraft(gameId),
		GAME_ID,
	);
}

// Reads the renderer's primitive count off the DEV-only debug
// surface (`window.__galaxyDebug.getMapPrimitives`, installed by
// `lib/active-view/map.svelte` on mount). A healthy renderer
// surfaces every world primitive — at least one planet point in
// this fixture. An empty list means the renderer re-mounted but
// never bound the new world snapshot, which is the user-reported
// "black canvas" symptom: the canvas DOM is fresh but Pixi never
// rebuilt the primitive graphics on it.
async function readPrimitiveCount(page: Page): Promise<number> {
	return page.evaluate(() => {
		const surface = (window as unknown as {
			__galaxyDebug?: {
				getMapPrimitives?: () => readonly unknown[];
			};
		}).__galaxyDebug;
		const prims = surface?.getMapPrimitives?.();
		if (prims === undefined) return -1;
		return prims.length;
	});
}

const NON_MAP_VIEWS: ReadonlyArray<{ label: string; testid: string }> = [
	{ label: "report", testid: "view-menu-item-report" },
	{ label: "designer-ship-class", testid: "view-menu-item-designer-ship-class" },
	{ label: "designer-science", testid: "view-menu-item-designer-science" },
	{ label: "mail", testid: "view-menu-item-mail" },
];

for (const view of NON_MAP_VIEWS) {
	test(`map → ${view.label} → map keeps the renderer alive`, async ({
		page,
	}, testInfo) => {
		test.skip(
			testInfo.project.name.startsWith("chromium-mobile"),
			"desktop layout only; mobile reuses the same store",
		);

		const consoleErrors: string[] = [];
		page.on("pageerror", (err) =>
			consoleErrors.push(`pageerror: ${err.message}`),
		);
		page.on("console", (msg) => {
			if (msg.type() === "error") {
				consoleErrors.push(`console.error: ${msg.text()}`);
			}
		});

		await mockGateway(page);
		await bootSession(page);

		await page.goto(`/games/${GAME_ID}/map`);
		await expect(page.getByTestId("active-view-map")).toHaveAttribute(
			"data-status",
			"ready",
		);
		await expect(page.getByTestId("map-mount-error")).toHaveCount(0);
		await expect
			.poll(() => readPrimitiveCount(page), {
				message: "first /map mount should publish primitives onto the debug surface",
				timeout: 3000,
			})
			.toBeGreaterThan(0);

		// Navigate via the dropdown to the non-map view.
		await page.getByTestId("view-menu-trigger").click();
		await page.getByTestId(view.testid).click();
		await expect(
			page.getByTestId(`active-view-${view.label}`),
		).toBeVisible();

		// Navigate back to /map via the same dropdown.
		await page.getByTestId("view-menu-trigger").click();
		await page.getByTestId("view-menu-item-map").click();
		await expect(page.getByTestId("active-view-map")).toBeVisible();
		await expect(page.getByTestId("active-view-map")).toHaveAttribute(
			"data-status",
			"ready",
		);
		await expect(page.getByTestId("map-mount-error")).toHaveCount(0);

		// The renderer must rebind primitives after the round-trip.
		// An empty list here is the "black canvas" symptom — the
		// canvas DOM is fresh, no mount-error overlay, but Pixi
		// never repopulated the world.
		await expect
			.poll(() => readPrimitiveCount(page), {
				message: `renderer published no primitives after returning from ${view.label} to /map`,
				timeout: 3000,
			})
			.toBeGreaterThan(0);

		expect(consoleErrors, consoleErrors.join("\n")).toEqual([]);
	});
}
