// Helpers that build FlatBuffers payloads for the lobby Playwright
// suite. Mirrors what `pkg/transcoder/lobby.go` produces in production,
// so the forged response goes through the same TS decoder the lobby
// page uses.

import { Builder } from "flatbuffers";

import {
	AccountResponse,
	AccountView,
	EntitlementSnapshot,
} from "../../../src/proto/galaxy/fbs/user";
import {
	ApplicationSubmitResponse,
	ApplicationSummary,
	GameCreateResponse,
	GameSummary,
	InviteDeclineResponse,
	InviteRedeemResponse,
	InviteSummary,
	MyApplicationsListResponse,
	MyGamesListResponse,
	MyInvitesListResponse,
	PublicGamesListResponse,
} from "../../../src/proto/galaxy/fbs/lobby";

export interface GameFixture {
	gameId: string;
	gameName: string;
	gameType: string;
	status: string;
	ownerUserId?: string;
	minPlayers?: number;
	maxPlayers?: number;
	enrollmentEndsAtMs?: bigint;
	createdAtMs?: bigint;
	updatedAtMs?: bigint;
	currentTurn?: number;
}

export interface ApplicationFixture {
	applicationId: string;
	gameId: string;
	applicantUserId: string;
	raceName: string;
	status: string;
	createdAtMs?: bigint;
	decidedAtMs?: bigint;
}

export interface InviteFixture {
	inviteId: string;
	gameId: string;
	inviterUserId: string;
	invitedUserId?: string;
	code?: string;
	raceName: string;
	status: string;
	createdAtMs?: bigint;
	expiresAtMs?: bigint;
	decidedAtMs?: bigint;
}

const DEFAULT_TIME_MS = 1_780_000_000_000n;

function encodeGame(builder: Builder, game: GameFixture): number {
	const gameId = builder.createString(game.gameId);
	const gameName = builder.createString(game.gameName);
	const gameType = builder.createString(game.gameType);
	const status = builder.createString(game.status);
	const ownerUserId = builder.createString(game.ownerUserId ?? "");
	GameSummary.startGameSummary(builder);
	GameSummary.addGameId(builder, gameId);
	GameSummary.addGameName(builder, gameName);
	GameSummary.addGameType(builder, gameType);
	GameSummary.addStatus(builder, status);
	GameSummary.addOwnerUserId(builder, ownerUserId);
	GameSummary.addMinPlayers(builder, game.minPlayers ?? 2);
	GameSummary.addMaxPlayers(builder, game.maxPlayers ?? 8);
	GameSummary.addEnrollmentEndsAtMs(builder, game.enrollmentEndsAtMs ?? DEFAULT_TIME_MS);
	GameSummary.addCreatedAtMs(builder, game.createdAtMs ?? DEFAULT_TIME_MS);
	GameSummary.addUpdatedAtMs(builder, game.updatedAtMs ?? DEFAULT_TIME_MS);
	GameSummary.addCurrentTurn(builder, game.currentTurn ?? 0);
	return GameSummary.endGameSummary(builder);
}

function encodeApplication(builder: Builder, app: ApplicationFixture): number {
	const applicationId = builder.createString(app.applicationId);
	const gameId = builder.createString(app.gameId);
	const applicantUserId = builder.createString(app.applicantUserId);
	const raceName = builder.createString(app.raceName);
	const status = builder.createString(app.status);
	ApplicationSummary.startApplicationSummary(builder);
	ApplicationSummary.addApplicationId(builder, applicationId);
	ApplicationSummary.addGameId(builder, gameId);
	ApplicationSummary.addApplicantUserId(builder, applicantUserId);
	ApplicationSummary.addRaceName(builder, raceName);
	ApplicationSummary.addStatus(builder, status);
	ApplicationSummary.addCreatedAtMs(builder, app.createdAtMs ?? DEFAULT_TIME_MS);
	ApplicationSummary.addDecidedAtMs(builder, app.decidedAtMs ?? 0n);
	return ApplicationSummary.endApplicationSummary(builder);
}

function encodeInvite(builder: Builder, invite: InviteFixture): number {
	const inviteId = builder.createString(invite.inviteId);
	const gameId = builder.createString(invite.gameId);
	const inviterUserId = builder.createString(invite.inviterUserId);
	const invitedUserId = builder.createString(invite.invitedUserId ?? "");
	const code = builder.createString(invite.code ?? "");
	const raceName = builder.createString(invite.raceName);
	const status = builder.createString(invite.status);
	InviteSummary.startInviteSummary(builder);
	InviteSummary.addInviteId(builder, inviteId);
	InviteSummary.addGameId(builder, gameId);
	InviteSummary.addInviterUserId(builder, inviterUserId);
	InviteSummary.addInvitedUserId(builder, invitedUserId);
	InviteSummary.addCode(builder, code);
	InviteSummary.addRaceName(builder, raceName);
	InviteSummary.addStatus(builder, status);
	InviteSummary.addCreatedAtMs(builder, invite.createdAtMs ?? DEFAULT_TIME_MS);
	InviteSummary.addExpiresAtMs(builder, invite.expiresAtMs ?? DEFAULT_TIME_MS);
	InviteSummary.addDecidedAtMs(builder, invite.decidedAtMs ?? 0n);
	return InviteSummary.endInviteSummary(builder);
}

export function buildMyGamesListPayload(games: GameFixture[]): Uint8Array {
	const builder = new Builder(256);
	const offsets = games.map((g) => encodeGame(builder, g));
	const items = MyGamesListResponse.createItemsVector(builder, offsets);
	MyGamesListResponse.startMyGamesListResponse(builder);
	MyGamesListResponse.addItems(builder, items);
	builder.finish(MyGamesListResponse.endMyGamesListResponse(builder));
	return builder.asUint8Array();
}

export function buildPublicGamesListPayload(
	games: GameFixture[],
	page = 1,
	pageSize = 50,
): Uint8Array {
	const builder = new Builder(256);
	const offsets = games.map((g) => encodeGame(builder, g));
	const items = PublicGamesListResponse.createItemsVector(builder, offsets);
	PublicGamesListResponse.startPublicGamesListResponse(builder);
	PublicGamesListResponse.addItems(builder, items);
	PublicGamesListResponse.addPage(builder, page);
	PublicGamesListResponse.addPageSize(builder, pageSize);
	PublicGamesListResponse.addTotal(builder, games.length);
	builder.finish(PublicGamesListResponse.endPublicGamesListResponse(builder));
	return builder.asUint8Array();
}

export function buildMyApplicationsListPayload(
	applications: ApplicationFixture[],
): Uint8Array {
	const builder = new Builder(256);
	const offsets = applications.map((a) => encodeApplication(builder, a));
	const items = MyApplicationsListResponse.createItemsVector(builder, offsets);
	MyApplicationsListResponse.startMyApplicationsListResponse(builder);
	MyApplicationsListResponse.addItems(builder, items);
	builder.finish(MyApplicationsListResponse.endMyApplicationsListResponse(builder));
	return builder.asUint8Array();
}

export function buildMyInvitesListPayload(invites: InviteFixture[]): Uint8Array {
	const builder = new Builder(256);
	const offsets = invites.map((i) => encodeInvite(builder, i));
	const items = MyInvitesListResponse.createItemsVector(builder, offsets);
	MyInvitesListResponse.startMyInvitesListResponse(builder);
	MyInvitesListResponse.addItems(builder, items);
	builder.finish(MyInvitesListResponse.endMyInvitesListResponse(builder));
	return builder.asUint8Array();
}

export function buildGameCreateResponsePayload(game: GameFixture): Uint8Array {
	const builder = new Builder(256);
	const summary = encodeGame(builder, game);
	GameCreateResponse.startGameCreateResponse(builder);
	GameCreateResponse.addGame(builder, summary);
	builder.finish(GameCreateResponse.endGameCreateResponse(builder));
	return builder.asUint8Array();
}

export function buildApplicationSubmitResponsePayload(
	application: ApplicationFixture,
): Uint8Array {
	const builder = new Builder(128);
	const app = encodeApplication(builder, application);
	ApplicationSubmitResponse.startApplicationSubmitResponse(builder);
	ApplicationSubmitResponse.addApplication(builder, app);
	builder.finish(ApplicationSubmitResponse.endApplicationSubmitResponse(builder));
	return builder.asUint8Array();
}

export function buildInviteRedeemResponsePayload(invite: InviteFixture): Uint8Array {
	const builder = new Builder(128);
	const summary = encodeInvite(builder, invite);
	InviteRedeemResponse.startInviteRedeemResponse(builder);
	InviteRedeemResponse.addInvite(builder, summary);
	builder.finish(InviteRedeemResponse.endInviteRedeemResponse(builder));
	return builder.asUint8Array();
}

export function buildInviteDeclineResponsePayload(invite: InviteFixture): Uint8Array {
	const builder = new Builder(128);
	const summary = encodeInvite(builder, invite);
	InviteDeclineResponse.startInviteDeclineResponse(builder);
	InviteDeclineResponse.addInvite(builder, summary);
	builder.finish(InviteDeclineResponse.endInviteDeclineResponse(builder));
	return builder.asUint8Array();
}

export interface AccountFixture {
	userId: string;
	email: string;
	userName: string;
	displayName: string;
}

export function buildAccountResponsePayload(account: AccountFixture): Uint8Array {
	const builder = new Builder(256);

	const planCode = builder.createString("free");
	const source = builder.createString("internal");
	const reasonCode = builder.createString("");
	EntitlementSnapshot.startEntitlementSnapshot(builder);
	EntitlementSnapshot.addPlanCode(builder, planCode);
	EntitlementSnapshot.addIsPaid(builder, false);
	EntitlementSnapshot.addSource(builder, source);
	EntitlementSnapshot.addReasonCode(builder, reasonCode);
	EntitlementSnapshot.addStartsAtMs(builder, 0n);
	EntitlementSnapshot.addEndsAtMs(builder, 0n);
	EntitlementSnapshot.addUpdatedAtMs(builder, 0n);
	const entitlement = EntitlementSnapshot.endEntitlementSnapshot(builder);

	const userId = builder.createString(account.userId);
	const email = builder.createString(account.email);
	const userName = builder.createString(account.userName);
	const displayName = builder.createString(account.displayName);
	const preferredLanguage = builder.createString("en");
	const timeZone = builder.createString("UTC");
	const declaredCountry = builder.createString("");
	AccountView.startAccountView(builder);
	AccountView.addUserId(builder, userId);
	AccountView.addEmail(builder, email);
	AccountView.addUserName(builder, userName);
	AccountView.addDisplayName(builder, displayName);
	AccountView.addPreferredLanguage(builder, preferredLanguage);
	AccountView.addTimeZone(builder, timeZone);
	AccountView.addDeclaredCountry(builder, declaredCountry);
	AccountView.addEntitlement(builder, entitlement);
	AccountView.addCreatedAtMs(builder, 0n);
	AccountView.addUpdatedAtMs(builder, 0n);
	const view = AccountView.endAccountView(builder);

	AccountResponse.startAccountResponse(builder);
	AccountResponse.addAccount(builder, view);
	builder.finish(AccountResponse.endAccountResponse(builder));
	return builder.asUint8Array();
}
