<!--
Shared chrome for the post-login lobby pages and the profile screen.
Renders a left page-list sidebar (mirroring the project site's
VitePress layout) plus a top identity strip ("Player-xxxx" → opens
profile, logout). Children fill the right-hand column.

F8-04b extends the sidebar to a two-level hierarchy:
- top-level items: `games` (with submenu), `profile`, and DEV-only
  `synthetic test reports`;
- `games` submenu: `active-past` (hidden when the player has no games),
  `recruitment` (always), `invitations` (hidden when no pending invites),
  `private games` (paid-tier only; DEV overrides). The visibility rules
  live in `lib/lobby-nav.ts` so the bare-`lobby` resolver
  (`lobby-screen.svelte`) and this shell agree on the auto-expanded
  sub-page.

Desktop (>640px): the submenu stays expanded as long as the active
screen is one of the games sub-panels. Mobile (≤640px, existing
horizontal-strip breakpoint): the `games` item becomes a dropdown
labeled "games · {active-sub} ▾"; tapping toggles the popover, tapping
outside or pressing Escape closes it, and re-selecting the active
sub-item is a no-op (mirrors the F8-02 idiom from issue #45).

The identity strip reads directly from the session-wide `account`
store so navigating between sub-pages never re-renders an empty
placeholder.
-->
<script lang="ts">
	import { onMount, type Snippet } from "svelte";
	import { appScreen, isLobbySubScreen, type AppScreen } from "$lib/app-nav.svelte";
	import { i18n, type TranslationKey } from "$lib/i18n/index.svelte";
	import { session } from "$lib/session-store.svelte";
	import { account } from "$lib/account-store.svelte";
	import { lobbyData } from "$lib/lobby-data.svelte";
	import {
		firstVisibleGamesScreen,
		visibleGamesSubScreens,
		type GamesSubScreen,
		type LobbyNavState,
	} from "$lib/lobby-nav";

	interface Props {
		children: Snippet;
	}

	let { children }: Props = $props();

	const DEV_AFFORDANCES =
		import.meta.env.VITE_GALAXY_DEV_AFFORDANCES === "true";

	const SUB_LABELS: Record<GamesSubScreen, TranslationKey> = {
		"games-active-past": "lobby.nav.games.active_past",
		"games-recruitment": "lobby.nav.games.recruitment",
		"games-invitations": "lobby.nav.games.invitations",
		"games-private-games": "lobby.nav.games.private_games",
	};

	let navState = $derived.by<LobbyNavState>(() => ({
		hasMyGames: lobbyData.myGames.length > 0,
		hasInvitations: lobbyData.invitations.length > 0,
		isPaidOrDev:
			DEV_AFFORDANCES || account.current?.entitlement.isPaid === true,
	}));

	let visibleSubs = $derived(visibleGamesSubScreens(navState));
	let firstVisible = $derived(firstVisibleGamesScreen(navState));

	function gotoScreen(screen: AppScreen): void {
		if (appScreen.screen !== screen) {
			appScreen.go(screen);
		}
	}

	function gotoGamesParent(): void {
		gotoScreen(firstVisible);
	}

	let activeGamesSub = $derived.by<GamesSubScreen | null>(() => {
		const s = appScreen.screen;
		if (
			s === "games-active-past" ||
			s === "games-recruitment" ||
			s === "games-invitations" ||
			s === "games-private-games"
		) {
			return s;
		}
		return null;
	});

	let gamesActive = $derived.by(() => isLobbySubScreen(appScreen.screen));
	let profileActive = $derived.by(() => appScreen.screen === "profile");
	let syntheticActive = $derived.by(() => appScreen.screen === "synthetic-reports");

	let identityLabel = $derived.by(() => {
		const current = account.current;
		if (current !== null) {
			const trimmed = current.displayName.trim();
			if (trimmed.length > 0) return trimmed;
			if (current.userName.length > 0) return current.userName;
		}
		return i18n.t("lobby.account_loading");
	});

	function gotoProfile(): void {
		gotoScreen("profile");
	}

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

	// Mobile dropdown state for the games submenu. Closed on subitem
	// selection, click outside, Escape, and on tap of the toggle when
	// already open.
	let mobileMenuOpen = $state(false);
	let mobileMenuEl: HTMLElement | null = null;

	function toggleMobileMenu(): void {
		mobileMenuOpen = !mobileMenuOpen;
	}

	function selectMobileSub(screen: AppScreen): void {
		mobileMenuOpen = false;
		gotoScreen(screen);
	}

	function handleMobileKeydown(event: KeyboardEvent): void {
		if (event.key === "Escape" && mobileMenuOpen) {
			mobileMenuOpen = false;
		}
	}

	function handleDocumentPointerDown(event: PointerEvent): void {
		if (!mobileMenuOpen) return;
		if (mobileMenuEl !== null && event.target instanceof Node) {
			if (!mobileMenuEl.contains(event.target)) {
				mobileMenuOpen = false;
			}
		}
	}

	onMount(() => {
		document.addEventListener("pointerdown", handleDocumentPointerDown);
		document.addEventListener("keydown", handleMobileKeydown);
		return () => {
			document.removeEventListener("pointerdown", handleDocumentPointerDown);
			document.removeEventListener("keydown", handleMobileKeydown);
		};
	});

	// Persisted snapshot may restore the user onto `games-private-games`
	// after a tier downgrade or onto `synthetic-reports` in a prod
	// bundle that strips the DEV affordance. Resolve quietly to the
	// first visible games sub-page instead of letting the dispatcher
	// render an empty shell.
	$effect(() => {
		const screen = appScreen.screen;
		if (screen === "games-private-games") {
			if (!DEV_AFFORDANCES && account.current?.entitlement.isPaid !== true) {
				appScreen.go(firstVisible);
			}
		} else if (screen === "synthetic-reports") {
			if (!DEV_AFFORDANCES) {
				appScreen.go(firstVisible);
			}
		} else if (screen === "games-active-past") {
			// Hide-when-empty is asymmetric: we only kick the user out if
			// the lobby-data store has actually reported zero games (not
			// during the initial `loading=true` window). Otherwise a
			// fresh navigation would bounce off this screen before the
			// fan-out resolves.
			if (!lobbyData.loading && lobbyData.myGames.length === 0) {
				appScreen.go(firstVisible);
			}
		} else if (screen === "games-invitations") {
			// Same asymmetric rule as `games-active-past`: hide-when-empty
			// fires only after the fan-out resolves, so a fresh nav onto
			// invitations does not bounce while the list is still loading.
			// An invite redeemed / declined from this screen drops the
			// only pending entry → user is moved to the first visible
			// sub-page on the next tick.
			if (!lobbyData.loading && lobbyData.invitations.length === 0) {
				appScreen.go(firstVisible);
			}
		}
	});
</script>

<a class="skip-link" href="#main-content">{i18n.t("common.skip_to_content")}</a>
<div class="layout">
	<header class="topbar">
		<button
			type="button"
			class="identity"
			onclick={gotoProfile}
			data-testid="lobby-account-name"
		>
			{identityLabel}
		</button>
		<button type="button" class="logout" onclick={logout} data-testid="lobby-logout">
			{i18n.t("lobby.logout")}
		</button>
	</header>
	<div class="body">
		<nav class="sidebar" aria-label={i18n.t("lobby.nav.aria_label")}>
			<ul class="top-list">
				<li class="games-item">
					<button
						type="button"
						class="nav-link parent"
						class:active={gamesActive}
						aria-current={gamesActive ? "page" : undefined}
						onclick={gotoGamesParent}
						data-testid="lobby-nav-games"
					>
						{i18n.t("lobby.nav.games")}
					</button>

					<!-- Desktop submenu: always expanded when in `games-*` -->
					<ul
						class="submenu desktop-only"
						aria-label={i18n.t("lobby.nav.games.aria_label")}
					>
						{#each visibleSubs as sub (sub)}
							<li>
								<button
									type="button"
									class="nav-link sub"
									class:active={appScreen.screen === sub}
									aria-current={appScreen.screen === sub ? "page" : undefined}
									onclick={() => gotoScreen(sub)}
									data-testid="lobby-nav-{sub}"
								>
									{i18n.t(SUB_LABELS[sub])}
								</button>
							</li>
						{/each}
					</ul>

					<!-- Mobile dropdown: only the active sub is shown as a
					     button, tap toggles the popover with the visible
					     subs. Re-tap on the active sub is a no-op. -->
					<div
						class="mobile-dropdown mobile-only"
						bind:this={mobileMenuEl}
					>
						<button
							type="button"
							class="nav-link mobile-toggle"
							class:active={gamesActive}
							aria-haspopup="listbox"
							aria-expanded={mobileMenuOpen}
							onclick={toggleMobileMenu}
							data-testid="lobby-nav-games-mobile"
						>
							{i18n.t("lobby.nav.games.mobile_toggle", {
								label: i18n.t(
									SUB_LABELS[activeGamesSub ?? "games-recruitment"],
								),
							})}
							<span aria-hidden="true">▾</span>
						</button>
						{#if mobileMenuOpen}
							<ul
								class="mobile-popover"
								role="listbox"
								aria-label={i18n.t("lobby.nav.games.aria_label")}
							>
								{#each visibleSubs as sub (sub)}
									<li>
										<button
											type="button"
											class="popover-item"
											class:active={appScreen.screen === sub}
											role="option"
											aria-selected={appScreen.screen === sub}
											onclick={() => selectMobileSub(sub)}
											data-testid="lobby-nav-{sub}-mobile"
										>
											{i18n.t(SUB_LABELS[sub])}
										</button>
									</li>
								{/each}
							</ul>
						{/if}
					</div>
				</li>

				<li>
					<button
						type="button"
						class="nav-link"
						class:active={profileActive}
						aria-current={profileActive ? "page" : undefined}
						onclick={gotoProfile}
						data-testid="lobby-nav-profile"
					>
						{i18n.t("lobby.nav.profile")}
					</button>
				</li>

				{#if DEV_AFFORDANCES}
					<li>
						<button
							type="button"
							class="nav-link"
							class:active={syntheticActive}
							aria-current={syntheticActive ? "page" : undefined}
							onclick={() => gotoScreen("synthetic-reports")}
							data-testid="lobby-nav-synthetic-reports"
						>
							{i18n.t("lobby.nav.synthetic_reports")}
						</button>
					</li>
				{/if}
			</ul>
		</nav>
		<main id="main-content" tabindex="-1" class="content">
			{@render children()}
		</main>
	</div>
</div>

<style>
	.layout {
		display: flex;
		flex-direction: column;
		min-height: 100vh;
		font-family: var(--font-mono);
		color: var(--color-text);
	}

	.topbar {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: var(--space-3);
		padding: var(--space-3) var(--space-5);
		border-bottom: 1px solid var(--color-border-subtle);
		background: var(--color-surface);
	}

	.identity {
		font: inherit;
		font-size: var(--text-md);
		color: var(--color-text);
		background: transparent;
		border: none;
		padding: var(--space-1) var(--space-2);
		border-radius: var(--radius-sm);
		cursor: pointer;
		text-decoration: underline;
		text-underline-offset: 0.2em;
	}

	.identity:hover {
		background: var(--color-surface-hover);
	}

	.logout {
		font: inherit;
		font-size: var(--text-sm);
		padding: var(--space-1) var(--space-3);
		background: transparent;
		color: inherit;
		border: 1px solid var(--color-border);
		border-radius: var(--radius-sm);
		cursor: pointer;
	}

	.logout:hover {
		background: var(--color-surface-hover);
	}

	.body {
		display: flex;
		flex: 1 1 auto;
		min-height: 0;
	}

	.sidebar {
		flex: 0 0 14rem;
		border-right: 1px solid var(--color-border-subtle);
		padding: var(--space-4) var(--space-3);
		background: var(--color-surface);
	}

	.top-list,
	.submenu {
		list-style: none;
		margin: 0;
		padding: 0;
		display: flex;
		flex-direction: column;
		gap: var(--space-1);
	}

	.submenu {
		margin-left: var(--space-3);
		margin-top: var(--space-1);
	}

	.nav-link {
		display: block;
		width: 100%;
		text-align: left;
		font: inherit;
		font-size: var(--text-base);
		color: var(--color-text-muted);
		background: transparent;
		border: none;
		padding: var(--space-2) var(--space-3);
		border-radius: var(--radius-sm);
		cursor: pointer;
	}

	.nav-link.sub {
		font-size: var(--text-sm);
		padding: var(--space-1) var(--space-3);
	}

	.nav-link.parent.active {
		color: var(--color-text);
		font-weight: var(--weight-medium);
	}

	.nav-link:hover {
		background: var(--color-surface-hover);
		color: var(--color-text);
	}

	.nav-link.active {
		color: var(--color-accent);
		background: var(--color-accent-subtle);
		font-weight: var(--weight-medium);
	}

	.content {
		flex: 1 1 auto;
		padding: var(--space-5) var(--space-6);
		max-width: 48rem;
	}

	.mobile-dropdown {
		position: relative;
		display: none;
	}

	.mobile-toggle {
		display: flex;
		align-items: center;
		gap: var(--space-1);
		justify-content: space-between;
	}

	.mobile-popover {
		position: absolute;
		top: calc(100% + var(--space-1));
		left: 0;
		right: 0;
		z-index: 5;
		list-style: none;
		margin: 0;
		padding: var(--space-1);
		background: var(--color-surface-raised);
		border: 1px solid var(--color-border);
		border-radius: var(--radius-md);
		box-shadow: var(--shadow-sm, 0 4px 12px rgba(0, 0, 0, 0.15));
	}

	.popover-item {
		display: block;
		width: 100%;
		text-align: left;
		font: inherit;
		font-size: var(--text-sm);
		color: var(--color-text-muted);
		background: transparent;
		border: none;
		padding: var(--space-2) var(--space-3);
		border-radius: var(--radius-sm);
		cursor: pointer;
	}

	.popover-item:hover {
		background: var(--color-surface-hover);
		color: var(--color-text);
	}

	.popover-item.active {
		color: var(--color-accent);
		background: var(--color-accent-subtle);
		font-weight: var(--weight-medium);
	}

	.mobile-only {
		display: none;
	}

	@media (max-width: 640px) {
		.body {
			flex-direction: column;
		}
		.sidebar {
			flex: 0 0 auto;
			border-right: none;
			border-bottom: 1px solid var(--color-border-subtle);
			padding: var(--space-2) var(--space-3);
		}
		.top-list {
			flex-direction: row;
			gap: var(--space-2);
			overflow-x: auto;
		}
		.nav-link {
			white-space: nowrap;
			padding: var(--space-1) var(--space-3);
		}
		.games-item {
			position: relative;
		}
		.desktop-only {
			display: none;
		}
		.mobile-only,
		.mobile-dropdown {
			display: block;
		}
		.content {
			padding: var(--space-4);
			max-width: none;
		}
		/* The games-item's parent button is replaced by the mobile
		   dropdown toggle. */
		.nav-link.parent {
			display: none;
		}
	}
</style>
