// FlatBuffers payload builders for the Phase 14 / Phase 15 Playwright
// suites. Mirrors what `pkg/transcoder/order.go` produces in production
// for the `user.games.order` POST response and the
// `user.games.order.get` GET response. Phase 15 extends the fixture
// with a `setProductionType` variant so a single mocked gateway can
// echo either rename or production-switch commands back to the client.

import { Builder } from "flatbuffers";

import { uuidToHiLo } from "../../../src/api/game-state";
import { UUID } from "../../../src/proto/galaxy/fbs/common";
import {
	CommandItem,
	CommandPayload,
	CommandPlanetProduce,
	CommandPlanetRename,
	CommandPlanetRouteRemove,
	CommandPlanetRouteSet,
	CommandRaceRelation,
	CommandRaceVote,
	CommandScienceCreate,
	CommandScienceRemove,
	CommandShipClassCreate,
	CommandShipClassRemove,
	PlanetProduction,
	PlanetRouteLoadType,
	Relation,
	UserGamesOrder,
	UserGamesOrderGetResponse,
	UserGamesOrderResponse,
} from "../../../src/proto/galaxy/fbs/order";

interface CommandResultFixtureBase {
	cmdId: string;
	applied: boolean | null;
	errorCode: number | null;
}

export interface PlanetRenameResultFixture extends CommandResultFixtureBase {
	kind: "planetRename";
	planetNumber: number;
	name: string;
}

export interface SetProductionTypeResultFixture
	extends CommandResultFixtureBase {
	kind: "setProductionType";
	planetNumber: number;
	productionType:
		| "MAT"
		| "CAP"
		| "DRIVE"
		| "WEAPONS"
		| "SHIELDS"
		| "CARGO"
		| "SCIENCE"
		| "SHIP";
	subject: string;
}

export interface SetCargoRouteResultFixture extends CommandResultFixtureBase {
	kind: "setCargoRoute";
	sourcePlanetNumber: number;
	destinationPlanetNumber: number;
	loadType: "COL" | "CAP" | "MAT" | "EMP";
}

export interface RemoveCargoRouteResultFixture
	extends CommandResultFixtureBase {
	kind: "removeCargoRoute";
	sourcePlanetNumber: number;
	loadType: "COL" | "CAP" | "MAT" | "EMP";
}

export interface CreateShipClassResultFixture extends CommandResultFixtureBase {
	kind: "createShipClass";
	name: string;
	drive: number;
	armament: number;
	weapons: number;
	shields: number;
	cargo: number;
}

export interface RemoveShipClassResultFixture extends CommandResultFixtureBase {
	kind: "removeShipClass";
	name: string;
}

export interface CreateScienceResultFixture extends CommandResultFixtureBase {
	kind: "createScience";
	name: string;
	drive: number;
	weapons: number;
	shields: number;
	cargo: number;
}

export interface RemoveScienceResultFixture extends CommandResultFixtureBase {
	kind: "removeScience";
	name: string;
}

export interface SetDiplomaticStanceResultFixture
	extends CommandResultFixtureBase {
	kind: "setDiplomaticStance";
	acceptor: string;
	relation: "WAR" | "PEACE";
}

export interface SetVoteRecipientResultFixture
	extends CommandResultFixtureBase {
	kind: "setVoteRecipient";
	acceptor: string;
}

export type CommandResultFixture =
	| PlanetRenameResultFixture
	| SetProductionTypeResultFixture
	| SetCargoRouteResultFixture
	| RemoveCargoRouteResultFixture
	| CreateShipClassResultFixture
	| RemoveShipClassResultFixture
	| CreateScienceResultFixture
	| RemoveScienceResultFixture
	| SetDiplomaticStanceResultFixture
	| SetVoteRecipientResultFixture;

export function buildOrderResponsePayload(
	gameId: string,
	commands: CommandResultFixture[],
	updatedAt: number,
): Uint8Array {
	const builder = new Builder(256);
	const itemOffsets = commands.map((c) => encodeItem(builder, c));
	const commandsVec = UserGamesOrderResponse.createCommandsVector(
		builder,
		itemOffsets,
	);
	const [hi, lo] = uuidToHiLo(gameId);
	const gameIdOffset = UUID.createUUID(builder, hi, lo);
	UserGamesOrderResponse.startUserGamesOrderResponse(builder);
	UserGamesOrderResponse.addGameId(builder, gameIdOffset);
	UserGamesOrderResponse.addUpdatedAt(builder, BigInt(updatedAt));
	UserGamesOrderResponse.addCommands(builder, commandsVec);
	const offset = UserGamesOrderResponse.endUserGamesOrderResponse(builder);
	builder.finish(offset);
	return builder.asUint8Array();
}

export function buildOrderGetResponsePayload(
	gameId: string,
	commands: CommandResultFixture[],
	updatedAt: number,
	found = true,
): Uint8Array {
	const builder = new Builder(256);

	let orderOffset = 0;
	if (found) {
		const itemOffsets = commands.map((c) => encodeItem(builder, c));
		const commandsVec = UserGamesOrder.createCommandsVector(
			builder,
			itemOffsets,
		);
		const [hi, lo] = uuidToHiLo(gameId);
		const gameIdOffset = UUID.createUUID(builder, hi, lo);
		UserGamesOrder.startUserGamesOrder(builder);
		UserGamesOrder.addGameId(builder, gameIdOffset);
		UserGamesOrder.addUpdatedAt(builder, BigInt(updatedAt));
		UserGamesOrder.addCommands(builder, commandsVec);
		orderOffset = UserGamesOrder.endUserGamesOrder(builder);
	}

	UserGamesOrderGetResponse.startUserGamesOrderGetResponse(builder);
	UserGamesOrderGetResponse.addFound(builder, found);
	if (orderOffset !== 0) {
		UserGamesOrderGetResponse.addOrder(builder, orderOffset);
	}
	const offset =
		UserGamesOrderGetResponse.endUserGamesOrderGetResponse(builder);
	builder.finish(offset);
	return builder.asUint8Array();
}

function encodeItem(builder: Builder, c: CommandResultFixture): number {
	const cmdIdOffset = builder.createString(c.cmdId);
	let payloadType: CommandPayload;
	let inner: number;
	switch (c.kind) {
		case "planetRename": {
			const nameOffset = builder.createString(c.name);
			inner = CommandPlanetRename.createCommandPlanetRename(
				builder,
				BigInt(c.planetNumber),
				nameOffset,
			);
			payloadType = CommandPayload.CommandPlanetRename;
			break;
		}
		case "setProductionType": {
			const subjectOffset = builder.createString(c.subject);
			inner = CommandPlanetProduce.createCommandPlanetProduce(
				builder,
				BigInt(c.planetNumber),
				productionTypeToFBS(c.productionType),
				subjectOffset,
			);
			payloadType = CommandPayload.CommandPlanetProduce;
			break;
		}
		case "setCargoRoute": {
			inner = CommandPlanetRouteSet.createCommandPlanetRouteSet(
				builder,
				BigInt(c.sourcePlanetNumber),
				BigInt(c.destinationPlanetNumber),
				cargoLoadTypeToFBS(c.loadType),
			);
			payloadType = CommandPayload.CommandPlanetRouteSet;
			break;
		}
		case "removeCargoRoute": {
			inner = CommandPlanetRouteRemove.createCommandPlanetRouteRemove(
				builder,
				BigInt(c.sourcePlanetNumber),
				cargoLoadTypeToFBS(c.loadType),
			);
			payloadType = CommandPayload.CommandPlanetRouteRemove;
			break;
		}
		case "createShipClass": {
			const nameOffset = builder.createString(c.name);
			inner = CommandShipClassCreate.createCommandShipClassCreate(
				builder,
				nameOffset,
				c.drive,
				BigInt(c.armament),
				c.weapons,
				c.shields,
				c.cargo,
			);
			payloadType = CommandPayload.CommandShipClassCreate;
			break;
		}
		case "removeShipClass": {
			const nameOffset = builder.createString(c.name);
			inner = CommandShipClassRemove.createCommandShipClassRemove(
				builder,
				nameOffset,
			);
			payloadType = CommandPayload.CommandShipClassRemove;
			break;
		}
		case "createScience": {
			const nameOffset = builder.createString(c.name);
			inner = CommandScienceCreate.createCommandScienceCreate(
				builder,
				nameOffset,
				c.drive,
				c.weapons,
				c.shields,
				c.cargo,
			);
			payloadType = CommandPayload.CommandScienceCreate;
			break;
		}
		case "removeScience": {
			const nameOffset = builder.createString(c.name);
			inner = CommandScienceRemove.createCommandScienceRemove(
				builder,
				nameOffset,
			);
			payloadType = CommandPayload.CommandScienceRemove;
			break;
		}
		case "setDiplomaticStance": {
			const acceptorOffset = builder.createString(c.acceptor);
			inner = CommandRaceRelation.createCommandRaceRelation(
				builder,
				acceptorOffset,
				relationToFBS(c.relation),
			);
			payloadType = CommandPayload.CommandRaceRelation;
			break;
		}
		case "setVoteRecipient": {
			const acceptorOffset = builder.createString(c.acceptor);
			inner = CommandRaceVote.createCommandRaceVote(builder, acceptorOffset);
			payloadType = CommandPayload.CommandRaceVote;
			break;
		}
	}
	CommandItem.startCommandItem(builder);
	CommandItem.addCmdId(builder, cmdIdOffset);
	if (c.applied !== null) CommandItem.addCmdApplied(builder, c.applied);
	if (c.errorCode !== null) {
		CommandItem.addCmdErrorCode(builder, BigInt(c.errorCode));
	}
	CommandItem.addPayloadType(builder, payloadType);
	CommandItem.addPayload(builder, inner);
	return CommandItem.endCommandItem(builder);
}

function productionTypeToFBS(
	value: SetProductionTypeResultFixture["productionType"],
): PlanetProduction {
	switch (value) {
		case "MAT":
			return PlanetProduction.MAT;
		case "CAP":
			return PlanetProduction.CAP;
		case "DRIVE":
			return PlanetProduction.DRIVE;
		case "WEAPONS":
			return PlanetProduction.WEAPONS;
		case "SHIELDS":
			return PlanetProduction.SHIELDS;
		case "CARGO":
			return PlanetProduction.CARGO;
		case "SCIENCE":
			return PlanetProduction.SCIENCE;
		case "SHIP":
			return PlanetProduction.SHIP;
	}
}

function cargoLoadTypeToFBS(
	value: SetCargoRouteResultFixture["loadType"],
): PlanetRouteLoadType {
	switch (value) {
		case "COL":
			return PlanetRouteLoadType.COL;
		case "CAP":
			return PlanetRouteLoadType.CAP;
		case "MAT":
			return PlanetRouteLoadType.MAT;
		case "EMP":
			return PlanetRouteLoadType.EMP;
	}
}

function relationToFBS(
	value: SetDiplomaticStanceResultFixture["relation"],
): Relation {
	switch (value) {
		case "WAR":
			return Relation.WAR;
		case "PEACE":
			return Relation.PEACE;
	}
}
