<!--
	Vidstack-based video player. Wraps the native `<video>` element with a
	polished, PhotoPrism-style chrome (gradient bottom bar, large play
	overlay, hover-revealed scrubber). Element registration is dynamically
	imported so it never runs during SvelteKit's static prerender.

	Imports are side-effecting:
	  - `vidstack/player` registers <media-player> / <media-provider>
	  - `vidstack/player/layouts/default` registers <media-video-layout>
	  - the two CSS imports ship the dark theme and video-layout styles
-->
<script lang="ts">
	import { onDestroy, onMount } from 'svelte';
	import 'vidstack/player/styles/default/theme.css';
	import 'vidstack/player/styles/default/layouts/video.css';

	interface Props {
		src: string;
		poster?: string;
		title?: string;
	}
	let { src, poster, title }: Props = $props();

	let ready = $state(false);
	// Whichever branch is currently mounted (fallback <video> or vidstack
	// <media-player>) binds here. On destroy we reach through this ref to
	// abort the in-flight range request — see onDestroy below.
	let host = $state<HTMLElement | undefined>();

	onMount(async () => {
		// Bundle these into the client chunk only — the modules side-effect
		// `customElements.define()` calls, which would crash under SSR.
		await import('vidstack/player');
		await import('vidstack/player/ui');
		await import('vidstack/player/layouts/default');
		ready = true;
	});

	// Hard-abort the underlying <video> on unmount. Detaching the element
	// alone doesn't reliably release the HTTP range request — the connection
	// can linger as "Pending" long enough to starve the grid's thumbnail
	// queue. Explicitly pausing, clearing src, and calling load() tells the
	// network layer to drop the stream now.
	onDestroy(() => {
		if (!host) return;
		const v =
			host.tagName.toLowerCase() === 'video'
				? (host as HTMLVideoElement)
				: host.querySelector('video');
		if (!v) return;
		try {
			v.pause();
			v.removeAttribute('src');
			v.load();
		} catch {
			// Element may already be detached / GC'd; ignore.
		}
	});
</script>

{#if ready}
	<!--
		Note: `src` is *not* set as an attribute on <media-player>. Our backend
		URL ends in `/avc` (not `.mp4`), so Vidstack's URL-suffix sniff fails
		and it falls back to a HEAD probe — which can pick the wrong loader
		and produce decode errors. Declaring the source via a nested
		<source type="video/mp4"> tells Vidstack to use the native MP4 loader
		directly, no probe needed.
	-->
	<media-player
		bind:this={host}
		class="vds-player max-h-full max-w-full rounded-md shadow-2xl"
		title={title ?? ''}
		{poster}
		autoplay
		muted
		playsinline
		load="eager"
		posterLoad="eager"
		streamType="on-demand"
		viewType="video"
	>
		<media-provider>
			<source {src} type="video/mp4" />
		</media-provider>
		<media-video-layout></media-video-layout>
	</media-player>
{:else}
	<!-- Fallback while the Vidstack module is loading. Keeps the poster
		 visible so the transition into the full chrome isn't jarring. -->
	<!-- svelte-ignore a11y_media_has_caption -->
	<video
		bind:this={host}
		{src}
		{poster}
		controls
		autoplay
		muted
		playsinline
		preload="auto"
		class="max-h-full max-w-full rounded-md object-contain shadow-2xl"
	></video>
{/if}

<style>
	:global(.vds-player) {
		/* Let the player size to the video's intrinsic aspect ratio,
		   capped by the host pane. Don't set width/height to 100% — that
		   was stretching widescreen video into the square preview pane.
		   The default vidstack layout reads the loaded media's aspect
		   and sizes the box accordingly; we just clamp the upper bound
		   so it can't escape its host pane. */
		max-width: 100%;
		max-height: 100%;
		--media-brand: #3b82f6;
		--media-focus-ring-color: #3b82f6;
	}
	:global(.vds-player video),
	:global(.vds-player media-provider) {
		max-width: 100%;
		max-height: 100%;
		object-fit: contain;
	}
</style>
