<script lang="ts">
	import { goto } from '$app/navigation';
	import { toErrorMessage } from '$lib/api/errors';
	import type { PlayerOptionDTO, PlayerVersusDTO } from '$lib/api/schema-helpers';
	import { getVersus, listVersusPlayers } from '$lib/api/statistics';
	import { session } from '$lib/stores/session.svelte';
	import {
		alertError,
		alertWarning,
		card,
		cardHeading,
		ghost
	} from '$lib/ui/classes';
	import PlayerPicker from '$lib/ui/PlayerPicker.svelte';

	/*
	 * One question, all events: how often did these two actually meet, and in which
	 * games. That is a different shape from the Rankings page — no event scope, no
	 * leaderboard — which is why it is its own page rather than a fifth tab there.
	 *
	 * The scope is deliberately "everything ever imported": a pairing's history is
	 * the point, and slicing it by season would just be the Rankings page again.
	 *
	 * A set records its bracket, and a bracket's game is nullable, so meetings the
	 * database cannot attribute to a game are excluded by the API and reported
	 * separately as `unknownGameSets`. They are shown, never folded into a total.
	 */

	let players = $state<PlayerOptionDTO[]>([]);
	let playerAId = $state<number | null>(null);
	let playerBId = $state<number | null>(null);

	let versus = $state<PlayerVersusDTO | null>(null);
	let loadingPlayers = $state(false);
	let loading = $state(false);
	let error = $state<string | null>(null);

	let inFlight: AbortController | null = null;

	const games = $derived(versus?.games ?? []);
	const sets = $derived(versus?.sets ?? 0);
	const decided = $derived(versus?.decidedSets ?? 0);
	const winsA = $derived(versus?.winsA ?? 0);
	const winsB = $derived(versus?.winsB ?? 0);
	const unknown = $derived(versus?.unknownGameSets ?? 0);
	/** Undecided meetings: recorded, but with equal scores, so nobody won them. */
	const undecided = $derived(Math.max(0, sets - decided));

	let started = false;
	$effect(() => {
		if (!session.isLoggedIn) {
			goto('/login', { replaceState: true });
			return;
		}
		if (!started) {
			started = true;
			void refreshPlayers();
		}
	});

	/*
	 * Reads only the two ids, so writing `versus` / `loading` below cannot re-trigger
	 * it. One request per complete pairing, and the previous one is aborted — picking
	 * a third player mid-flight must not race an older answer into view.
	 */
	$effect(() => {
		const a = playerAId;
		const b = playerBId;

		if (a === null || b === null || a === b) {
			inFlight?.abort();
			inFlight = null;
			versus = null;
			loading = false;
			return;
		}

		void compare(a, b);
	});

	async function refreshPlayers() {
		loadingPlayers = true;
		error = null;
		try {
			players = await listVersusPlayers();
		} catch (cause) {
			error = toErrorMessage(cause, 'Could not load the player list.');
		} finally {
			loadingPlayers = false;
		}
	}

	async function compare(a: number, b: number) {
		inFlight?.abort();
		const controller = new AbortController();
		inFlight = controller;

		loading = true;
		error = null;

		try {
			const result = await getVersus(a, b, { signal: controller.signal });
			if (controller.signal.aborted) return;
			versus = result;
		} catch (cause) {
			if (cause instanceof DOMException && cause.name === 'AbortError') return;
			versus = null;
			error = toErrorMessage(cause, 'Could not load this pairing.');
		} finally {
			if (inFlight === controller) {
				inFlight = null;
				loading = false;
			}
		}
	}

	function swap() {
		[playerAId, playerBId] = [playerBId, playerAId];
	}

	function clear() {
		playerAId = null;
		playerBId = null;
	}

	/** Percentages get one decimal only under 100, so the column stays narrow. */
	function percent(value: number): string {
		return value >= 99.95 ? '100%' : `${value.toFixed(1)}%`;
	}

	/** First player's share of the decided meetings; 50 when nothing is decided. */
	function share(a: number, b: number): number {
		const total = a + b;
		return total === 0 ? 50 : (a / total) * 100;
	}
</script>

<svelte:head>
	<title>Players Statistiques · LaDOSE</title>
</svelte:head>

<main id="main" class="mx-auto max-w-5xl px-4 py-10">
	<header class="mb-8">
		<h1 class="text-3xl font-semibold tracking-tight">Players Statistiques</h1>
		<p class="mt-1 text-sm text-muted">
			Pick two players and see how often they met, game by game, across every event ever
			imported. For leaderboards and attendance, see
			<a href="/statistiques/rankings" class="underline decoration-line-strong hover:text-ink">
				Rankings Statistiques
			</a>.
		</p>
	</header>

	{#if error}
		<p role="alert" class="{alertError} mb-4">{error}</p>
	{/if}

	<section class={card}>
		<div class="flex items-baseline justify-between gap-3">
			<h2 class={cardHeading}>
				Pairing
				<span class="ml-1 font-normal text-muted normal-case">
					({players.length} players with recorded sets)
				</span>
			</h2>
			<button class={ghost} onclick={refreshPlayers} disabled={loadingPlayers}>
				{loadingPlayers ? 'Loading…' : 'Refresh'}
			</button>
		</div>

		<div class="mt-4 grid gap-5 sm:grid-cols-2">
			<PlayerPicker
				heading="Player A"
				{players}
				bind:selectedId={playerAId}
				excludeId={playerBId}
				disabled={loadingPlayers}
			/>
			<PlayerPicker
				heading="Player B"
				{players}
				bind:selectedId={playerBId}
				excludeId={playerAId}
				disabled={loadingPlayers}
			/>
		</div>

		<div class="mt-4 flex items-center justify-between gap-3 border-t border-line pt-4">
			<span class="text-xs text-muted">
				{#if loading}
					Reading their sets…
				{:else if loadingPlayers}
					Only players with at least one set in a bracket with a known game are listed.
				{:else}
					The count beside a name is their total sets, whoever the opponent was.
				{/if}
			</span>
			<div class="flex gap-2">
				<button class={ghost} onclick={swap} disabled={playerAId === null && playerBId === null}>
					Swap
				</button>
				<button class={ghost} onclick={clear} disabled={playerAId === null && playerBId === null}>
					Clear
				</button>
			</div>
		</div>
	</section>

	{#if versus && (playerAId !== null) && (playerBId !== null)}
		<!--
			Scoreline first: the whole page answers one question, so the total sits above
			the per-game split rather than under it.
		-->
		<section class="{card} mt-6">
			<div class="flex flex-wrap items-baseline justify-between gap-3">
				<h2 class="text-lg font-semibold tracking-tight">
					{versus.playerA}
					<span class="text-subtle">vs</span>
					{versus.playerB}
				</h2>
				<p class="text-3xl font-semibold tracking-tight tabular-nums">
					<span class={winsA >= winsB ? '' : 'text-muted'}>{winsA}</span>
					<span class="text-subtle">–</span>
					<span class={winsB > winsA ? '' : 'text-muted'}>{winsB}</span>
				</p>
			</div>

			{#if sets === 0}
				<p class="mt-3 text-sm text-subtle">
					No recorded meeting in a bracket with a known game.
					{#if unknown > 0}
						They did meet {unknown} time{unknown === 1 ? '' : 's'}, but in
						{unknown === 1 ? 'a bracket' : 'brackets'} with no game attached.
					{/if}
				</p>
			{:else}
				<!--
					A share meter, not a two-series bar: one measure (player A's share of the
					decided meetings) painted in the accent over an inset track. The score
					above carries the same numbers for anyone who cannot see it.
				-->
				<div
					class="mt-4 h-2 overflow-hidden rounded-full bg-inset"
					role="img"
					aria-label="{versus.playerA} won {winsA} of {decided} decided meetings against {versus.playerB}"
				>
					<div class="h-full rounded-full bg-accent" style="width: {share(winsA, winsB)}%"></div>
				</div>

				<dl class="mt-5 grid grid-cols-2 gap-3 sm:grid-cols-4">
					{#each [['Meetings', String(sets)], ['Games played', String(games.length)], ['Decided', String(decided)], [`${versus.playerA ?? 'A'} win rate`, percent(share(winsA, winsB))]] as const as [caption, value] (caption)}
						<div class="rounded-xl border border-line bg-inset p-3">
							<dt class="truncate text-xs tracking-wide text-muted uppercase">{caption}</dt>
							<dd class="mt-1 text-2xl font-semibold tracking-tight tabular-nums">{value}</dd>
						</div>
					{/each}
				</dl>
			{/if}
		</section>

		{#if unknown > 0 && sets > 0}
			<p class="{alertWarning} mt-4">
				{unknown} further meeting{unknown === 1 ? '' : 's'} excluded: the bracket has no game
				attached, so {unknown === 1 ? 'it' : 'they'} cannot be filed under one. Set the game on
				those tournaments to see {unknown === 1 ? 'it' : 'them'} here.
			</p>
		{/if}

		{#if games.length}
			<section class="{card} mt-6">
				<h2 class={cardHeading}>Meetings per game</h2>
				<p class="mt-1 text-xs text-muted">
					Most-played first. Games counts the individual games inside the decided sets — a
					DQ is stored as a negative score and counts as zero.
				</p>

				<div class="mt-4 overflow-x-auto">
					<table class="w-full min-w-max text-sm">
						<thead class="text-left text-xs tracking-wide text-muted uppercase">
							<tr class="border-b border-line">
								<th class="py-2 pr-4 font-medium">Game</th>
								<th class="px-2 py-2 text-right font-medium">Meetings</th>
								<th class="px-2 py-2 text-right font-medium">Decided</th>
								<th class="px-2 py-2 text-right font-medium">Record</th>
								<th class="px-2 py-2 text-right font-medium">Games</th>
								<th class="w-40 py-2 pl-2 font-medium">{versus.playerA} share</th>
							</tr>
						</thead>
						<tbody class="tabular-nums">
							{#each games as row (row.gameId)}
								{@const rowWinsA = row.winsA ?? 0}
								{@const rowWinsB = row.winsB ?? 0}
								<tr class="border-b border-line/60 last:border-0">
									<td class="py-2 pr-4">
										<span class="font-medium">{row.game}</span>
										{#if row.gameLongName && row.gameLongName !== row.game}
											<span class="ml-1 text-xs text-subtle">{row.gameLongName}</span>
										{/if}
									</td>
									<td class="px-2 py-2 text-right font-semibold">{row.sets}</td>
									<td class="px-2 py-2 text-right {row.decidedSets === row.sets ? 'text-subtle' : ''}">
										{row.decidedSets}
									</td>
									<td class="px-2 py-2 text-right font-semibold">{rowWinsA}–{rowWinsB}</td>
									<td class="px-2 py-2 text-right text-subtle">
										{row.gamesWonA}–{row.gamesWonB}
									</td>
									<td class="py-2 pl-2">
										<div class="flex items-center gap-2">
											<div
												class="h-1.5 grow overflow-hidden rounded-full bg-inset"
												role="img"
												aria-label="{versus.playerA} won {rowWinsA} of {rowWinsA + rowWinsB} decided meetings in {row.game}"
											>
												<div
													class="h-full rounded-full bg-accent"
													style="width: {share(rowWinsA, rowWinsB)}%"
												></div>
											</div>
											<span class="w-12 shrink-0 text-right text-xs text-muted">
												{rowWinsA + rowWinsB === 0 ? '—' : percent(share(rowWinsA, rowWinsB))}
											</span>
										</div>
									</td>
								</tr>
							{/each}
						</tbody>
					</table>
				</div>

				{#if undecided > 0}
					<p class="mt-4 text-xs text-muted">
						{undecided} of these {sets} meetings {undecided === 1 ? 'has' : 'have'} equal scores
						— counted as a meeting, but won by nobody, so
						{undecided === 1 ? 'it is' : 'they are'} absent from the records above.
					</p>
				{/if}
			</section>
		{/if}
	{:else if !loading}
		<p class="mt-6 text-sm text-subtle">
			{#if playerAId === null && playerBId === null}
				Pick a player on each side to see their history.
			{:else}
				Pick a second player to compare.
			{/if}
		</p>
	{/if}
</main>
