<script lang="ts">
	import FrameSlider from './FrameSlider.svelte';
	import { type Video, type VideoConstructor } from '$/lib/Player/Video';
	import Keybinds from './Keybinds.svelte';
	import { onMount } from 'svelte';

	let frame = $state(0);
	let canvas = $state(null as HTMLCanvasElement | null);
	let audio = $state(null as HTMLAudioElement | null);
	let lastCanvas = null as typeof canvas;
	let video = $state(undefined as Video | undefined);
	let frameCount = $state(0);
	let playing = $state(false);
	let playbackStarted = $state(0);
	let playbackFrameOffset = 0;
	let renderPromise: Promise<void> | void = void 0;
	let renderId = 0;
	let audioSource = $state(null as null | string);
	let {
		Video: VideoImplementation
	}: {
		Video: VideoConstructor;
	} = $props();
	const newCanvas = async (canvas: HTMLCanvasElement, videoImplementation: VideoConstructor) => {
		if (video) video.cleanup();
		lastCanvas = canvas;
		video = new VideoImplementation(canvas);
		const audioSourcePromise = (async () => {
			if (video?.audioUrl?.[1]) {
				audioSource = await fetch(video.audioUrl[1])
					.then(async (v) => {
						if (v.status.toString().startsWith('2')) return URL.createObjectURL(await v.blob());
						else throw new Error('non-2xx audio');
					})
					.catch((e) => {
						console.warn('Failed to get audio', e);
						return video?.audioUrl?.[1] ?? null;
					});
			}
		})();
		video['_isInit'] = true;
		renderPromise = video.init();
		await renderPromise;
		video['_isInit'] = false;
		frameCount = video.length;
		await audioSourcePromise;
	};
	const renderPreviewFrame = async (video: Video, frame: number) => {
		const ourId = ++renderId;
		if (renderPromise) await renderPromise;
		if (renderId !== ourId) return;
		renderPromise =
			video.renderFrame({
				frames: frame,
				milliseconds: (frame / video.fps) * 1000,
				seconds: frame / video.fps
			}) ?? Promise.resolve();
		return renderPromise;
	};
	$effect(() => {
		if (canvas && canvas !== lastCanvas) newCanvas(canvas, VideoImplementation);
	});
	$effect(() => {
		if (video) renderPreviewFrame(video, frame);
	});
	let playbackLoopId = 0;
	const startPlaybackLoop = (id = ++playbackLoopId) => {
		if (video && id === playbackLoopId) {
			const ms = performance.now() - playbackStarted;
			let f = Math.floor((ms / 1000) * video.fps) + playbackFrameOffset;

			if (f > frameCount) {
				f = frameCount;
				playing = false;
			}
			frame = f;
			renderPreviewFrame(video, frame).then(() =>
				requestAnimationFrame(() => (playing ? startPlaybackLoop(id) : void 0))
			);
		}
	};
	$effect(() => {
		if (playing) {
			playbackStarted = performance.now();
			playbackFrameOffset = frame;
			startPlaybackLoop();
		}
	});
	let loadedFrameTimestamp = false;
	onMount(() => {
		const t = sessionStorage.getItem('timestamp');
		const tI = t ? parseInt(t, 36) : null;
		if (tI && !isNaN(tI)) {
			frame = tI;
			requestAnimationFrame(() => (frame = tI));
		}
		loadedFrameTimestamp = true;
	});
	// TODO: implement waitin a few seconds before saving
	$effect(() => {
		if (loadedFrameTimestamp)
			try {
				sessionStorage.setItem('timestamp', frame.toString(36));
			} catch (_) {}
	});
	$effect(() => {
		if (audio && video && !playing) {
			try {
				const f = frame;
				audio.currentTime = frame / video.fps;
				audio.play();
				(async () => {
					const targetTime = (frame + 1) / video.fps;
					while (audio.currentTime <= targetTime) {
						await new Promise((rs) => requestAnimationFrame(rs));
						if (playing || frame !== f) return;
					}
					audio.pause();
					audio.currentTime = frame / video.fps;
				})();
			} catch (error) {
				console.warn(error);
			}
		}
	});
	$effect(() => {
		if (audio) {
			if (playing) audio.play();
			else audio.pause();
		}
	});
</script>

<svelte:window
	onresize={() => {
		if (canvas && video) {
			(async () => {
				video['_isInit'] = true;
				renderPromise = await video.init();
				video['_isInit'] = false;
				renderPreviewFrame(video, frame);
			})();
		}
	}}
/>

<Keybinds bind:frame {frameCount} fps={video?.fps} bind:playing bind:playbackStarted />

<div class="p-2 w-screen h-screen relative flex flex-col">
	<div class="flex-1 relative">
		<div class="absolute top-0 left-0 w-full h-full flex items-center justify-center">
			<canvas class="pointer-events-none bg-black" bind:this={canvas}>
				Your browser doesn't support the canvas API.
			</canvas>
			{#if audioSource}
				<audio src={audioSource} bind:this={audio}></audio>
			{/if}
		</div>
	</div>
	<FrameSlider bind:frame {frameCount} bind:playing bind:playbackStarted />
</div>
