<script lang="ts">
	import { useSpacetimeDB, useTable, useReducer } from "spacetimedb/svelte";
	import { tables, reducers } from "$lib/st-bindings";
	import JASSUB from "jassub";
	import font1 from "$lib/assets/fonts/trebuc_0.ttf";

	const conn = useSpacetimeDB();

	const [videoStates] = useTable(tables.videoState);
	const videoState = $derived($videoStates.find((state) => state.id === 1n));

	const setUrlReducer = useReducer(reducers.setUrl);
	const setSubtitleUrlReducer = useReducer(reducers.setSubtitleUrl);
	const playReducer = useReducer(reducers.play);
	const pauseReducer = useReducer(reducers.pause);
	const seekReducer = useReducer(reducers.seek);

	let videoElement: HTMLVideoElement | undefined = $state();
	let newUrl = $state("");
	let newSubtitleUrl = $state("");

	let isSyncing = false;
	let syncTimeout: ReturnType<typeof setTimeout> | undefined;
	let jassubInstance: any | undefined;

	$effect(() => {
		console.log("effect-1");
		if (videoElement && videoState?.subtitleUrl && videoState.subtitleUrl.endsWith(".ass")) {
			jassubInstance = new JASSUB({
				video: videoElement,
				subUrl: videoState.subtitleUrl,
				fonts: [font1],
			});

			return () => {
				jassubInstance?.destroy();
				jassubInstance = undefined;
			};
		}
	});

	function syncAction(fn: () => void) {
		isSyncing = true;
		clearTimeout(syncTimeout);
		fn();
		syncTimeout = setTimeout(() => {
			isSyncing = false;
		}, 50);
	}

	$effect(() => {
		console.log("effect-2");
		if (!videoElement || !videoState) return;
		const el = videoElement;
		const state = videoState;

		if (el.src !== state.url) {
			el.src = state.url;
		}

		// Account for server to client clock drift slightly, but MVP assumes relatively synced clocks.
		const timeElapsedMicros = BigInt(Date.now()) * 1000n - state.lastUpdatedAt.microsSinceUnixEpoch;
		const expectedTime = state.isPlaying
			? state.timePosition + Number(timeElapsedMicros) / 1_000_000
			: state.timePosition;

		const diff = Math.abs(el.currentTime - expectedTime);

		if (!state.isPlaying) {
			if (el.currentTime < expectedTime && diff <= 2) {
				// Behind pause point. Keep playing to catch up. (timeupdate will pause it)
				// If locally paused, stay paused (we likely dispatched the pause event + buffer)
			} else {
				// Ahead or reached pause point. Pause and rewind if needed.
				syncAction(() => {
					el.pause();
					el.currentTime = expectedTime;
				});
			}
		} else {
			// Sync on play by unpausing slightly in future
			if (el.paused) {
				syncAction(() => {
					el.currentTime = expectedTime;
					el.play().catch(console.error);
				});
			} else if (diff > 2.0) {
				// Allow up to 2s drift while playing
				syncAction(() => {
					el.currentTime = expectedTime;
				});
			}
		}
	});

	function handlePlay() {
		if (isSyncing) return;
		// Send play slightly in future to adjust for network latency
		playReducer({ timePosition: (videoElement?.currentTime ?? 0) + 0.15 });
	}

	function handlePause() {
		if (isSyncing) return;
		// Send pause slightly in future, so remote clients coast into it
		pauseReducer({ timePosition: (videoElement?.currentTime ?? 0) + 0.15 });
	}

	function handleSeeked() {
		if (isSyncing) return;
		seekReducer({ timePosition: videoElement?.currentTime ?? 0 });
	}

	function handleTimeUpdate() {
		if (!videoElement || !videoState) return;
		const el = videoElement;

		if (!videoState.isPlaying) {
			const expectedTime = videoState.timePosition;
			if (!el.paused && el.currentTime >= expectedTime) {
				syncAction(() => {
					el.pause();
					if (el.currentTime - expectedTime > 0.01) {
						el.currentTime = expectedTime;
					}
				});
			}
		}
	}

	function handleSetUrl(e: SubmitEvent) {
		e.preventDefault();
		if (!newUrl.trim() || !$conn.isActive) return;
		setUrlReducer({ url: newUrl });
		newUrl = "";
	}

	function handleSetSubtitle(e: SubmitEvent) {
		e.preventDefault();
		if (!newSubtitleUrl.trim() || !$conn.isActive) return;
		setSubtitleUrlReducer({ url: newSubtitleUrl });
		newSubtitleUrl = "";
	}
</script>

<div class="p-4">
	<h1>Space Stream</h1>

	<div class="mb-2">
		Status:
		<strong style="color: {$conn.isActive ? 'green' : 'red'}">
			{$conn.isActive ? "Connected" : "Disconnected"}
		</strong>
	</div>

	<form onsubmit={handleSetUrl} class="mb-2">
		<input
			type="url"
			placeholder="Enter new video URL"
			bind:value={newUrl}
			class="mr-2 w-96 p-2"
			disabled={!$conn.isActive}
		/>
		<button type="submit" class="p-2" disabled={!$conn.isActive}>Set URL</button>
	</form>

	<form onsubmit={handleSetSubtitle} class="mb-2">
		<input
			type="url"
			placeholder="Enter subtitle track URL (.vtt/.ass)"
			bind:value={newSubtitleUrl}
			class="mr-2 w-96 p-2"
		/>
		<button type="submit" class="p-2">Set Subtitles</button>
	</form>

	<div>
		<!-- svelte-ignore a11y_media_has_caption -->
		<video
			bind:this={videoElement}
			muted
			controls
			crossorigin="anonymous"
			onplay={handlePlay}
			onpause={handlePause}
			onseeked={handleSeeked}
			ontimeupdate={handleTimeUpdate}
			class="w-full max-w-2xl bg-black"
		>
			{#if videoState?.subtitleUrl && !videoState.subtitleUrl.endsWith(".ass")}
				<track src={videoState.subtitleUrl} kind="subtitles" srclang="en" label="English" default />
			{/if}
			Your browser does not support the video tag.
		</video>
	</div>
</div>
