<script lang="ts">
	import { goto } from "$app/navigation";
	import { onMount } from "svelte";

	import { createEdgeGatewayClient } from "../../api/connect";
	import { GalaxyClient } from "../../api/galaxy-client";
	import {
		LobbyError,
		listMyApplications,
		listMyGames,
		listMyInvites,
		listPublicGames,
		redeemInvite,
		declineInvite,
		submitApplication,
		type ApplicationSummary,
		type GameSummary,
		type InviteSummary,
	} from "../../api/lobby";
	import { ByteBuffer } from "flatbuffers";
	import { AccountResponse } from "../../proto/galaxy/fbs/user";
	import { GATEWAY_BASE_URL, GATEWAY_RESPONSE_PUBLIC_KEY } from "$lib/env";
	import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
	import { loadCore } from "../../platform/core/index";
	import { session } from "$lib/session-store.svelte";
	import { Builder } from "flatbuffers";
	import { GetMyAccountRequest } from "../../proto/galaxy/fbs/user";

	let displayName: string | null = $state(null);
	let configError: string | null = $state(null);
	let listsLoading = $state(true);
	let lobbyError: string | null = $state(null);

	let myGames: GameSummary[] = $state([]);
	let invitations: InviteSummary[] = $state([]);
	let applications: ApplicationSummary[] = $state([]);
	let publicGames: GameSummary[] = $state([]);

	let openApplicationFor: string | null = $state(null);
	let raceNameInput = $state("");
	let raceNameError: string | null = $state(null);
	let submittingApplication = $state(false);

	let inviteActionInFlight: string | null = $state(null);

	let client: GalaxyClient | null = null;

	async function logout(): Promise<void> {
		await session.signOut("user");
	}

	async function sha256(payload: Uint8Array): Promise<Uint8Array> {
		const digest = await crypto.subtle.digest("SHA-256", payload as BufferSource);
		return new Uint8Array(digest);
	}

	function describeLobbyError(err: unknown): string {
		if (err instanceof LobbyError) {
			const key = `lobby.error.${err.code}` as TranslationKey;
			const translated = i18n.t(key);
			if (translated !== key) {
				return translated;
			}
			return i18n.t("lobby.error.unknown", { message: err.message });
		}
		return err instanceof Error ? err.message : "request failed";
	}

	async function refreshAll(): Promise<void> {
		if (client === null) return;
		listsLoading = true;
		lobbyError = null;
		try {
			const [games, invites, apps, publicPage] = await Promise.all([
				listMyGames(client),
				listMyInvites(client),
				listMyApplications(client),
				listPublicGames(client),
			]);
			myGames = games;
			invitations = invites.filter((invite) => invite.status === "pending");
			applications = apps;
			publicGames = publicPage.items;
		} catch (err) {
			lobbyError = describeLobbyError(err);
		} finally {
			listsLoading = false;
		}
	}

	function applicationStatusLabel(status: string): string {
		const key = `lobby.application.status.${status}` as TranslationKey;
		const translated = i18n.t(key);
		if (translated === key) {
			return i18n.t("lobby.application.status.unknown", { status });
		}
		return translated;
	}

	function openApplicationForm(gameId: string): void {
		openApplicationFor = gameId;
		raceNameInput = "";
		raceNameError = null;
	}

	function cancelApplicationForm(): void {
		openApplicationFor = null;
		raceNameInput = "";
		raceNameError = null;
	}

	async function submitApplicationFor(gameId: string): Promise<void> {
		if (client === null) return;
		const trimmed = raceNameInput.trim();
		if (trimmed === "") {
			raceNameError = i18n.t("lobby.application.race_name_required");
			return;
		}
		submittingApplication = true;
		raceNameError = null;
		try {
			const result = await submitApplication(client, gameId, trimmed);
			applications = [result, ...applications];
			openApplicationFor = null;
			raceNameInput = "";
		} catch (err) {
			raceNameError = describeLobbyError(err);
		} finally {
			submittingApplication = false;
		}
	}

	async function acceptInvite(invite: InviteSummary): Promise<void> {
		if (client === null) return;
		inviteActionInFlight = invite.inviteId;
		try {
			await redeemInvite(client, invite.gameId, invite.inviteId);
			invitations = invitations.filter((i) => i.inviteId !== invite.inviteId);
			myGames = await listMyGames(client);
		} catch (err) {
			lobbyError = describeLobbyError(err);
		} finally {
			inviteActionInFlight = null;
		}
	}

	async function rejectInvite(invite: InviteSummary): Promise<void> {
		if (client === null) return;
		inviteActionInFlight = invite.inviteId;
		try {
			await declineInvite(client, invite.gameId, invite.inviteId);
			invitations = invitations.filter((i) => i.inviteId !== invite.inviteId);
		} catch (err) {
			lobbyError = describeLobbyError(err);
		} finally {
			inviteActionInFlight = null;
		}
	}

	async function loadGreeting(c: GalaxyClient): Promise<void> {
		const builder = new Builder(32);
		GetMyAccountRequest.startGetMyAccountRequest(builder);
		builder.finish(GetMyAccountRequest.endGetMyAccountRequest(builder));
		const result = await c.executeCommand("user.account.get", builder.asUint8Array());
		if (result.resultCode !== "ok") {
			return;
		}
		const response = AccountResponse.getRootAsAccountResponse(
			new ByteBuffer(result.payloadBytes),
		);
		const account = response.account();
		if (account === null) {
			return;
		}
		const display = account.displayName();
		const userName = account.userName();
		displayName = display && display.length > 0 ? display : userName;
	}

	function gotoCreate(): void {
		goto("/lobby/create");
	}

	function gotoGame(gameId: string): void {
		goto(`/games/${gameId}/map`);
	}

	onMount(async () => {
		if (
			session.keypair === null ||
			session.deviceSessionId === null ||
			GATEWAY_RESPONSE_PUBLIC_KEY.length === 0
		) {
			listsLoading = false;
			if (GATEWAY_RESPONSE_PUBLIC_KEY.length === 0) {
				configError = "VITE_GATEWAY_RESPONSE_PUBLIC_KEY is not configured";
			}
			return;
		}
		const keypair = session.keypair;
		try {
			const core = await loadCore();
			client = new GalaxyClient({
				core,
				edge: createEdgeGatewayClient(GATEWAY_BASE_URL),
				signer: (canonical) => keypair.sign(canonical),
				sha256,
				deviceSessionId: session.deviceSessionId,
				gatewayResponsePublicKey: GATEWAY_RESPONSE_PUBLIC_KEY,
			});
			loadGreeting(client).catch(() => {});
			await refreshAll();
		} catch (err) {
			lobbyError = describeLobbyError(err);
			listsLoading = false;
		}
	});
</script>

<main>
	<header>
		<h1>{i18n.t("lobby.title")}</h1>
		<p>
			{i18n.t("lobby.device_session_id_label")}:
			<code data-testid="device-session-id">{session.deviceSessionId ?? ""}</code>
		</p>
		{#if displayName !== null}
			<p data-testid="account-greeting">
				{i18n.t("lobby.greeting", { name: displayName })}
			</p>
		{/if}
		<button onclick={logout} data-testid="lobby-logout">
			{i18n.t("lobby.logout")}
		</button>
	</header>

	{#if configError !== null}
		<p role="alert" data-testid="account-error">{configError}</p>
	{:else if lobbyError !== null}
		<p role="alert" data-testid="lobby-error">{lobbyError}</p>
	{/if}

	<section data-testid="lobby-create-section">
		<button onclick={gotoCreate} data-testid="lobby-create-button">
			{i18n.t("lobby.create_button")}
		</button>
	</section>

	<section data-testid="lobby-my-games-section">
		<h2>{i18n.t("lobby.section.my_games")}</h2>
		{#if listsLoading}
			<p>{i18n.t("lobby.list_loading")}</p>
		{:else if myGames.length === 0}
			<p data-testid="lobby-my-games-empty">{i18n.t("lobby.my_games.empty")}</p>
		{:else}
			<ul class="card-list">
				{#each myGames as game (game.gameId)}
					<li>
						<button
							class="card"
							onclick={() => gotoGame(game.gameId)}
							data-testid="lobby-my-game-card"
						>
							<strong>{game.gameName}</strong>
							<span class="meta">{game.status}</span>
							<span class="meta">{game.minPlayers}–{game.maxPlayers} players</span>
						</button>
					</li>
				{/each}
			</ul>
		{/if}
	</section>

	<section data-testid="lobby-invitations-section">
		<h2>{i18n.t("lobby.section.invitations")}</h2>
		{#if listsLoading}
			<p>{i18n.t("lobby.list_loading")}</p>
		{:else if invitations.length === 0}
			<p data-testid="lobby-invitations-empty">{i18n.t("lobby.invitations.empty")}</p>
		{:else}
			<ul class="card-list">
				{#each invitations as invite (invite.inviteId)}
					<li class="card">
						<strong>{invite.raceName}</strong>
						<span class="meta">{invite.gameId}</span>
						<div class="actions">
							<button
								onclick={() => acceptInvite(invite)}
								disabled={inviteActionInFlight === invite.inviteId}
								data-testid="lobby-invite-accept"
							>
								{i18n.t("lobby.invitation.accept")}
							</button>
							<button
								onclick={() => rejectInvite(invite)}
								disabled={inviteActionInFlight === invite.inviteId}
								data-testid="lobby-invite-decline"
							>
								{i18n.t("lobby.invitation.decline")}
							</button>
						</div>
					</li>
				{/each}
			</ul>
		{/if}
	</section>

	<section data-testid="lobby-applications-section">
		<h2>{i18n.t("lobby.section.applications")}</h2>
		{#if listsLoading}
			<p>{i18n.t("lobby.list_loading")}</p>
		{:else if applications.length === 0}
			<p data-testid="lobby-applications-empty">{i18n.t("lobby.applications.empty")}</p>
		{:else}
			<ul class="card-list">
				{#each applications as app (app.applicationId)}
					<li class="card" data-testid="lobby-application-card">
						<strong>{app.raceName}</strong>
						<span class="meta">{app.gameId}</span>
						<span class="status" data-status={app.status}>
							{applicationStatusLabel(app.status)}
						</span>
					</li>
				{/each}
			</ul>
		{/if}
	</section>

	<section data-testid="lobby-public-games-section">
		<h2>{i18n.t("lobby.section.public_games")}</h2>
		{#if listsLoading}
			<p>{i18n.t("lobby.list_loading")}</p>
		{:else if publicGames.length === 0}
			<p data-testid="lobby-public-games-empty">{i18n.t("lobby.public_games.empty")}</p>
		{:else}
			<ul class="card-list">
				{#each publicGames as game (game.gameId)}
					<li class="card">
						<strong>{game.gameName}</strong>
						<span class="meta">{game.status}</span>
						<span class="meta">{game.minPlayers}–{game.maxPlayers} players</span>
						{#if openApplicationFor === game.gameId}
							<form
								onsubmit={(event) => {
									event.preventDefault();
									submitApplicationFor(game.gameId);
								}}
								data-testid="lobby-application-form"
							>
								<label>
									{i18n.t("lobby.application.race_name_label")}
									<input
										type="text"
										bind:value={raceNameInput}
										data-testid="lobby-application-race-name"
										autocomplete="off"
									/>
								</label>
								{#if raceNameError !== null}
									<p role="alert" data-testid="lobby-application-error">
										{raceNameError}
									</p>
								{/if}
								<div class="actions">
									<button
										type="submit"
										disabled={submittingApplication}
										data-testid="lobby-application-submit"
									>
										{i18n.t("lobby.application.submit")}
									</button>
									<button
										type="button"
										onclick={cancelApplicationForm}
										data-testid="lobby-application-cancel"
									>
										{i18n.t("lobby.application.cancel")}
									</button>
								</div>
							</form>
						{:else}
							<button
								onclick={() => openApplicationForm(game.gameId)}
								data-testid="lobby-public-game-apply"
							>
								{i18n.t("lobby.application.submit_for", { name: game.gameName })}
							</button>
						{/if}
					</li>
				{/each}
			</ul>
		{/if}
	</section>
</main>

<style>
	main {
		padding: 1.5rem 1rem;
		max-width: 32rem;
		margin: 0 auto;
		font-family: system-ui, sans-serif;
	}

	header {
		display: flex;
		flex-direction: column;
		gap: 0.5rem;
		margin-bottom: 1.5rem;
	}

	header h1 {
		margin: 0;
	}

	header button {
		align-self: flex-start;
	}

	section {
		margin-bottom: 2rem;
	}

	section h2 {
		font-size: 1.1rem;
		margin: 0 0 0.75rem;
	}

	.card-list {
		list-style: none;
		padding: 0;
		margin: 0;
		display: flex;
		flex-direction: column;
		gap: 0.5rem;
	}

	.card {
		display: flex;
		flex-direction: column;
		gap: 0.25rem;
		padding: 0.75rem;
		border: 1px solid #ccc;
		border-radius: 0.4rem;
		background: #fafafa;
		text-align: left;
		font: inherit;
		cursor: pointer;
		width: 100%;
	}

	li.card {
		cursor: default;
	}

	.meta {
		color: #555;
		font-size: 0.9rem;
	}

	.status {
		align-self: flex-start;
		padding: 0.1rem 0.5rem;
		border-radius: 999px;
		background: #e7e7e7;
		font-size: 0.8rem;
	}

	.actions {
		display: flex;
		gap: 0.5rem;
		margin-top: 0.25rem;
	}

	form {
		display: flex;
		flex-direction: column;
		gap: 0.5rem;
		margin-top: 0.5rem;
	}

	label {
		display: flex;
		flex-direction: column;
		gap: 0.25rem;
	}

	input[type="text"] {
		font-size: 1rem;
		padding: 0.4rem 0.5rem;
	}
</style>
