<script lang="ts">
	import type { BufferedRange, PreviewChunk } from '$lib/types';
	import GridTile from './grid-tile.svelte';
	import ShieldIcon from 'phosphor-svelte/lib/ShieldIcon';
	import SwordIcon from 'phosphor-svelte/lib/SwordIcon';
	import { playerColor } from '$lib/utils/player-colors';

	interface Props {
		previews: PreviewChunk[];
		currentPlayer: number;
		availablePlayers: Set<number>;
		playerDurations: Map<number, number>;
		virtualTime: number;
		paused?: boolean;
		muted?: boolean;
		playbackRate?: number;
		onPausedChange?: (paused: boolean) => void;
		onSelect: (player: number) => void;
		onTimeUpdate?: (t: number) => void;
		onLeaderEnded?: () => void;
		onBufferedChange?: (ranges: BufferedRange[]) => void;
		onReady?: (player: number) => void;
	}

	let {
		previews,
		currentPlayer,
		availablePlayers,
		playerDurations,
		virtualTime,
		paused = false,
		muted = false,
		playbackRate = 1,
		onSelect,
		onTimeUpdate,
		onLeaderEnded,
		onBufferedChange,
		onReady
	}: Props = $props();

	type PerPlayer = {
		player: number;
		side: PreviewChunk['player_side'];
		weapon: string;
		chunks: { src: string; duration: number }[];
		duration: number;
	};

	const perPlayer = $derived.by<PerPlayer[]>(() => {
		const previewMap = new Map<number, PreviewChunk[]>();
		for (const preview of previews) {
			if (!previewMap.has(preview.player)) previewMap.set(preview.player, []);
			previewMap.get(preview.player)!.push(preview);
		}

		const players: PerPlayer[] = [];
		for (const [player, chunks] of previewMap.entries()) {
			chunks.sort((a, b) => a.chunk_index - b.chunk_index);
			const last = chunks[chunks.length - 1];
			players.push({
				player,
				side: last.player_side,
				weapon: last.primary_weapon,
				chunks: chunks.map((chunk) => ({
					src: chunk.preview_video.src,
					duration: chunk.duration_s
				})),
				duration: playerDurations.get(player) ?? 0
			});
		}

		players.sort((a, b) => a.player - b.player);
		return players;
	});

	const ctPlayers = $derived(perPlayer.filter((player) => player.side === 'CT'));
	const tPlayers = $derived(perPlayer.filter((player) => player.side === 'T'));

	function onLeaderTime(t: number) {
		onTimeUpdate?.(t);
	}

	// Intersect every tile's buffered ranges: the timeline span that's safe
	// to play across all perspectives at once.
	const tileBuffers = new Map<number, BufferedRange[]>();

	function intersectAll(): BufferedRange[] {
		const roundEnd = Math.max(0, ...Array.from(playerDurations.values()));
		if (roundEnd <= 0) return [];

		// For a player who died early (stream shorter than the round), pad the
		// buffer list with a synthetic [dur, roundEnd] range. There is no stream
		// there for them to buffer, so they shouldn't constrain the unified
		// indicator past their own death.
		const lists: BufferedRange[][] = [];
		for (const [player, ranges] of tileBuffers) {
			const dur = playerDurations.get(player);
			const padded =
				dur != null && Number.isFinite(dur) && dur < roundEnd
					? [...ranges, { start: dur, end: roundEnd }]
					: ranges;
			if (padded.length > 0) lists.push(padded);
		}
		if (!lists.length) return [];

		let acc = lists[0];
		for (let i = 1; i < lists.length; i++) {
			const next: BufferedRange[] = [];
			for (const a of acc) {
				for (const b of lists[i]) {
					const start = Math.max(a.start, b.start);
					const end = Math.min(a.end, b.end);
					if (end > start) next.push({ start, end });
				}
			}
			if (!next.length) return [];
			acc = next;
		}

		return acc;
	}

	function handleTileBuffered(player: number, ranges: BufferedRange[]) {
		tileBuffers.set(player, ranges);
		onBufferedChange?.(intersectAll());
	}
</script>

<div class="max-h-[80vh] overflow-y-auto rounded-lg border bg-card shadow-sm">
	<div class="space-y-4 p-3">
		{#if ctPlayers.length}
			<div>
				<div
					class="mb-2 flex items-center gap-1.5 text-[11px] font-semibold tracking-wide text-sky-700 uppercase dark:text-sky-400"
				>
					<ShieldIcon size={12} weight="duotone" /> Counter-Terrorists
				</div>
				<div class="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-2">
					{#each ctPlayers as player (player.player)}
						<div class="flex min-w-0 flex-col gap-1">
							<GridTile
								chunks={player.chunks}
								masterTime={virtualTime}
								masterPaused={paused}
								isLeader={player.player === currentPlayer}
								isCurrent={player.player === currentPlayer}
								isMuted={muted || player.player !== currentPlayer}
								{playbackRate}
								available={availablePlayers.has(player.player)}
								duration={player.duration}
								player={player.player}
								side={player.side}
								onSelect={() => onSelect(player.player)}
								{onLeaderTime}
								onLeaderEnded={() => onLeaderEnded?.()}
								onBufferedChange={handleTileBuffered}
								{onReady}
							/>
							<div class="flex items-center gap-1.5 px-0.5 text-xs">
								<span
									class="inline-block size-2 shrink-0 rounded-full"
									style={`background: ${playerColor(player.player)};`}
								></span>
								<span class="truncate font-medium">Player {player.player}</span>
							</div>
						</div>
					{/each}
				</div>
			</div>
		{/if}

		{#if tPlayers.length}
			<div>
				<div
					class="mb-2 flex items-center gap-1.5 text-[11px] font-semibold tracking-wide text-amber-700 uppercase dark:text-amber-400"
				>
					<SwordIcon size={12} weight="duotone" /> Terrorists
				</div>
				<div class="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-2">
					{#each tPlayers as player (player.player)}
						<div class="flex min-w-0 flex-col gap-1">
							<GridTile
								chunks={player.chunks}
								masterTime={virtualTime}
								masterPaused={paused}
								isLeader={player.player === currentPlayer}
								isCurrent={player.player === currentPlayer}
								isMuted={muted || player.player !== currentPlayer}
								{playbackRate}
								available={availablePlayers.has(player.player)}
								duration={player.duration}
								player={player.player}
								side={player.side}
								onSelect={() => onSelect(player.player)}
								{onLeaderTime}
								onLeaderEnded={() => onLeaderEnded?.()}
								onBufferedChange={handleTileBuffered}
								{onReady}
							/>
							<div class="flex items-center gap-1.5 px-0.5 text-xs">
								<span
									class="inline-block size-2 shrink-0 rounded-full"
									style={`background: ${playerColor(player.player)};`}
								></span>
								<span class="truncate font-medium">Player {player.player}</span>
							</div>
						</div>
					{/each}
				</div>
			</div>
		{/if}
	</div>
</div>
