<!--
Recruitment panel for the lobby `games` section. Surfaces every public
game open for enrollment plus the caller's own pending/approved
applications for games whose enrollment has since closed. Cards merge
the public-game summary with the caller's application (if any) and
display the status as a chip in the top-right.

Inline race-name form behaviour:
- visible when the caller has no application for that game;
- visible when the caller's last application was rejected (re-apply
  flow, owner-confirmed in F8-04b);
- hidden when the application is pending or approved.

Cards for stale applications (enrollment closed, no public-game row):
- pending/approved → rendered as a standalone "applied" card so the
  caller can see their waitlist or accepted slot;
- rejected/unknown → not rendered (no actionable info).
-->
<script lang="ts">
	import { onMount } from "svelte";
	import { account } from "$lib/account-store.svelte";
	import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
	import {
		lobbyData,
		describeLobbyError,
	} from "$lib/lobby-data.svelte";
	import LobbyShell from "./lobby-shell.svelte";
	import {
		submitApplication,
		type ApplicationSummary,
		type GameSummary,
	} from "../../api/lobby";

	let openApplicationFor = $state<string | null>(null);
	let raceNameInput = $state("");
	let raceNameError = $state<string | null>(null);
	let submitting = $state(false);

	onMount(() => {
		lobbyData.ensure().then((client) => {
			if (client !== null) {
				account.ensure(client).catch(() => {});
			}
		});
	});

	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;
	}

	// Determine whether the inline race-name form should be visible for
	// a given (game, application) pair. Visible when there's no
	// application, or when the latest application was rejected so the
	// caller can try again. Hidden for pending / approved / unknown.
	function showApplicationForm(application: ApplicationSummary | null): boolean {
		if (application === null) return true;
		return application.status === "rejected";
	}

	interface RecruitmentCard {
		game: GameSummary;
		application: ApplicationSummary | null;
	}

	let appsByGameId = $derived.by<Map<string, ApplicationSummary>>(() => {
		const m = new Map<string, ApplicationSummary>();
		for (const app of lobbyData.applications) {
			m.set(app.gameId, app);
		}
		return m;
	});

	let recruitmentCards = $derived.by<ReadonlyArray<RecruitmentCard>>(() => {
		const me = account.current?.userId ?? "";
		return lobbyData.publicGames
			.filter((g) => g.ownerUserId !== me)
			.map((g) => ({ game: g, application: appsByGameId.get(g.gameId) ?? null }));
	});

	// Standalone cards for stale applications (game no longer in the
	// public-games list but the application is pending/approved).
	// Rejected / unknown stale applications are intentionally hidden.
	let standaloneApplications = $derived.by<ReadonlyArray<ApplicationSummary>>(() => {
		const seen = new Set(lobbyData.publicGames.map((g) => g.gameId));
		return lobbyData.applications.filter(
			(app) =>
				!seen.has(app.gameId) &&
				(app.status === "pending" || app.status === "approved"),
		);
	});

	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> {
		const client = lobbyData.client;
		if (client === null) return;
		const trimmed = raceNameInput.trim();
		if (trimmed === "") {
			raceNameError = i18n.t("lobby.application.race_name_required");
			return;
		}
		submitting = true;
		raceNameError = null;
		try {
			const result = await submitApplication(client, gameId, trimmed);
			lobbyData.prependApplication(result);
			openApplicationFor = null;
			raceNameInput = "";
		} catch (err) {
			raceNameError = describeLobbyError(err);
		} finally {
			submitting = false;
		}
	}
</script>

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

	<section data-testid="lobby-games-recruitment-section">
		<h2>{i18n.t("lobby.section.recruitment")}</h2>
		{#if lobbyData.loading}
			<p role="status">{i18n.t("lobby.list_loading")}</p>
		{:else if recruitmentCards.length === 0 && standaloneApplications.length === 0}
			<p data-testid="lobby-recruitment-empty">
				{i18n.t("lobby.recruitment.empty")}
			</p>
		{:else}
			<ul class="card-list">
				{#each recruitmentCards as card (card.game.gameId)}
					<li class="card" data-testid="lobby-recruitment-card">
						<div class="card-header">
							<strong>{card.game.gameName}</strong>
							{#if card.application !== null}
								<span
									class="status-chip"
									data-status={card.application.status}
									data-testid="lobby-application-status-chip"
								>
									{applicationStatusLabel(card.application.status)}
								</span>
							{/if}
						</div>
						<span class="meta">{card.game.status}</span>
						<span class="meta">{card.game.minPlayers}–{card.game.maxPlayers} players</span>

						{#if showApplicationForm(card.application)}
							{#if openApplicationFor === card.game.gameId}
								<form
									onsubmit={(event) => {
										event.preventDefault();
										submitApplicationFor(card.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={submitting}
											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(card.game.gameId)}
									data-testid="lobby-public-game-apply"
								>
									{i18n.t("lobby.application.submit_for", {
										name: card.game.gameName,
									})}
								</button>
							{/if}
						{:else if card.application?.status === "pending"}
							<p class="meta">{i18n.t("lobby.recruitment.applied_pending")}</p>
						{:else if card.application?.status === "approved"}
							<p class="meta">{i18n.t("lobby.recruitment.applied_approved")}</p>
						{/if}
					</li>
				{/each}

				{#each standaloneApplications as app (app.applicationId)}
					<li class="card" data-testid="lobby-stale-application-card">
						<div class="card-header">
							<strong>{app.raceName}</strong>
							<span
								class="status-chip"
								data-status={app.status}
								data-testid="lobby-application-status-chip"
							>
								{applicationStatusLabel(app.status)}
							</span>
						</div>
						<span class="meta">{app.gameId}</span>
						{#if app.status === "pending"}
							<p class="meta">{i18n.t("lobby.recruitment.applied_pending")}</p>
						{:else}
							<p class="meta">{i18n.t("lobby.recruitment.applied_approved")}</p>
						{/if}
					</li>
				{/each}
			</ul>
		{/if}
	</section>
</LobbyShell>

<style>
	section { margin-bottom: var(--space-6); }
	section h2 { font-size: var(--text-lg); margin: 0 0 var(--space-3); }
	.card-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--space-2); }
	.card {
		display: flex;
		flex-direction: column;
		gap: var(--space-1);
		padding: var(--space-3);
		border: 1px solid var(--color-border);
		border-radius: var(--radius-md);
		background: var(--color-surface-raised);
		text-align: left;
		font: inherit;
		width: 100%;
	}
	.card-header {
		display: flex;
		align-items: flex-start;
		justify-content: space-between;
		gap: var(--space-2);
	}
	.status-chip {
		align-self: flex-start;
		padding: 0.1rem var(--space-2);
		border-radius: var(--radius-pill);
		background: var(--color-surface);
		font-size: var(--text-xs);
		color: var(--color-text-muted);
		text-transform: lowercase;
	}
	.status-chip[data-status="approved"] {
		color: var(--color-accent);
	}
	.status-chip[data-status="rejected"] {
		color: var(--color-danger, #c33);
	}
	.meta { color: var(--color-text-muted); font-size: var(--text-sm); }
	.actions { display: flex; gap: var(--space-2); margin-top: var(--space-1); }
	form { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-2); }
	label { display: flex; flex-direction: column; gap: var(--space-1); }
	input[type="text"] { font: inherit; font-size: var(--text-md); padding: var(--space-1) var(--space-2); }
</style>
