<script lang="ts">
	import { createEventDispatcher, onMount, onDestroy } from "svelte";
	import { Play, Pause, Maximize, Undo } from "@gradio/icons";
	import Video from "./Video.svelte";
	import VideoControls from "./VideoControls.svelte";
	import VolumeControl from "./VolumeControl.svelte";
	import VolumeLevels from "../../audio/shared/VolumeLevels.svelte";
	import type { FileData, Client } from "@gradio/client";
	import { prepare_files } from "@gradio/client";
	import { format_time } from "@gradio/utils";
	import type { I18nFormatter } from "@gradio/utils";

	export let root = "";
	export let src: string;
	export let subtitle: string | null = null;
	export let mirror: boolean;
	export let autoplay: boolean;
	export let loop: boolean;
	export let label = "test";
	export let interactive = false;
	export let handle_change: (video: FileData) => void = () => {};
	export let handle_reset_value: () => void = () => {};
	export let upload: Client["upload"];
	export let is_stream: boolean | undefined;
	export let i18n: I18nFormatter;
	export let show_download_button = false;
	export let value: FileData | null = null;
	export let handle_clear: () => void = () => {};
	export let has_change_history = false;
	export let playback_position = 0;

	const dispatch = createEventDispatcher<{
		play: undefined;
		pause: undefined;
		stop: undefined;
		end: undefined;
		clear: undefined;
	}>();

	let time = 0;
	let duration: number;
	let paused = true;
	let video: HTMLVideoElement;
	let processingVideo = false;
	let show_volume_slider = false;
	let current_volume = 1;
	let is_fullscreen = false;

	function handleMove(e: TouchEvent | MouseEvent): void {
		if (!duration) return;

		if (e.type === "click") {
			handle_click(e as MouseEvent);
			return;
		}

		if (e.type !== "touchmove" && !((e as MouseEvent).buttons & 1)) return;

		const clientX =
			e.type === "touchmove"
				? (e as TouchEvent).touches[0].clientX
				: (e as MouseEvent).clientX;
		const { left, right } = (
			e.currentTarget as HTMLProgressElement
		).getBoundingClientRect();
		time = (duration * (clientX - left)) / (right - left);
	}

	async function play_pause(): Promise<void> {
		if (document.fullscreenElement != video) {
			const isPlaying =
				video.currentTime > 0 &&
				!video.paused &&
				!video.ended &&
				video.readyState > video.HAVE_CURRENT_DATA;

			if (!isPlaying) {
				await video.play();
			} else video.pause();
		}
	}

	function handle_click(e: MouseEvent): void {
		const { left, right } = (
			e.currentTarget as HTMLProgressElement
		).getBoundingClientRect();
		time = (duration * (e.clientX - left)) / (right - left);
	}

	function handle_end(): void {
		dispatch("stop");
		dispatch("end");
	}

	const handle_trim_video = async (videoBlob: Blob): Promise<void> => {
		let _video_blob = new File([videoBlob], "video.mp4");
		const val = await prepare_files([_video_blob]);
		let value = ((await upload(val, root))?.filter(Boolean) as FileData[])[0];

		handle_change(value);
	};

	function open_full_screen(): void {
		if (!is_fullscreen) {
			video.requestFullscreen();
		} else {
			document.exitFullscreen();
		}
	}

	function handleFullscreenChange(): void {
		is_fullscreen = document.fullscreenElement === video;
		if (video) {
			video.controls = is_fullscreen;
		}
	}

	let last_synced_volume = 1;
	let previous_video: HTMLVideoElement | undefined;
	// Tolerance for floating-point comparison of volume values
	const VOLUME_EPSILON = 0.001;

	function handleVolumeChange(): void {
		if (video && Math.abs(video.volume - last_synced_volume) > VOLUME_EPSILON) {
			current_volume = video.volume;
			last_synced_volume = video.volume;
		}
	}

	onMount(() => {
		document.addEventListener("fullscreenchange", handleFullscreenChange);
		return () => {
			document.removeEventListener("fullscreenchange", handleFullscreenChange);
		};
	});

	onDestroy(() => {
		if (video) {
			video.removeEventListener("volumechange", handleVolumeChange);
		}
	});

	$: if (video && video !== previous_video) {
		if (previous_video) {
			previous_video.removeEventListener("volumechange", handleVolumeChange);
		}
		video.addEventListener("volumechange", handleVolumeChange);
		previous_video = video;
	}

	$: time = time || 0;
	$: duration = duration || 0;
	$: playback_position = time;
	$: if (playback_position !== time && video) {
		video.currentTime = playback_position;
	}
	$: if (video && !is_fullscreen) {
		if (Math.abs(video.volume - current_volume) > VOLUME_EPSILON) {
			video.volume = current_volume;
			last_synced_volume = current_volume;
		}
		video.controls = false;
	}
	$: if (video && is_fullscreen) {
		last_synced_volume = video.volume;
	}
</script>

<div class="wrap">
	<div class="mirror-wrap" class:mirror>
		<Video
			{src}
			preload="auto"
			{autoplay}
			{loop}
			{is_stream}
			controls={is_fullscreen}
			on:click={play_pause}
			on:play
			on:pause
			on:error
			on:ended={handle_end}
			bind:currentTime={time}
			bind:duration
			bind:paused
			bind:node={video}
			data-testid={`${label}-player`}
			{processingVideo}
			on:loadstart
			on:loadeddata
			on:loadedmetadata
		>
			<track kind="captions" src={subtitle} default />
		</Video>
	</div>

	<div class="controls">
		<div class="inner">
			<span
				role="button"
				tabindex="0"
				class="icon"
				aria-label="play-pause-replay-button"
				on:click={play_pause}
				on:keydown={play_pause}
			>
				{#if time === duration}
					<Undo />
				{:else if paused}
					<Play />
				{:else}
					<Pause />
				{/if}
			</span>

			<span class="time">{format_time(time)} / {format_time(duration)}</span>

			<!-- TODO: implement accessible video timeline for 4.0 -->
			<!-- svelte-ignore a11y-click-events-have-key-events -->
			<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
			<progress
				value={time / duration || 0}
				on:mousemove={handleMove}
				on:touchmove|preventDefault={handleMove}
				on:click|stopPropagation|preventDefault={handle_click}
			/>

			<div class="volume-control-wrapper">
				<button
					class="icon volume-button"
					style:color={show_volume_slider ? "var(--color-accent)" : "white"}
					aria-label="Adjust volume"
					on:click={() => (show_volume_slider = !show_volume_slider)}
				>
					<VolumeLevels currentVolume={current_volume} />
				</button>

				{#if show_volume_slider}
					<VolumeControl bind:current_volume bind:show_volume_slider />
				{/if}
			</div>

			{#if !show_volume_slider}
				<div
					role="button"
					tabindex="0"
					class="icon"
					aria-label="full-screen"
					on:click={open_full_screen}
					on:keypress={open_full_screen}
				>
					<Maximize />
				</div>
			{/if}
		</div>
	</div>
</div>
{#if interactive}
	<VideoControls
		videoElement={video}
		showRedo
		{handle_trim_video}
		{handle_reset_value}
		bind:processingVideo
		{value}
		{i18n}
		{show_download_button}
		{handle_clear}
		{has_change_history}
	/>
{/if}

<style lang="postcss">
	span {
		text-shadow: 0 0 8px rgba(0, 0, 0, 0.5);
	}

	progress {
		margin-right: var(--size-3);
		border-radius: var(--radius-sm);
		width: var(--size-full);
		height: var(--size-2);
	}

	progress::-webkit-progress-bar {
		border-radius: 2px;
		background-color: rgba(255, 255, 255, 0.2);
		overflow: hidden;
	}

	progress::-webkit-progress-value {
		background-color: rgba(255, 255, 255, 0.9);
	}

	.mirror {
		transform: scaleX(-1);
	}

	.mirror-wrap {
		position: relative;
		height: 100%;
		width: 100%;
	}

	.controls {
		position: absolute;
		bottom: 0;
		opacity: 0;
		transition: 500ms;
		margin: var(--size-2);
		border-radius: var(--radius-md);
		background: var(--color-grey-800);
		padding: var(--size-2) var(--size-1);
		width: calc(100% - 0.375rem * 2);
		width: calc(100% - var(--size-2) * 2);
		z-index: 10;
	}
	.wrap:hover .controls {
		opacity: 1;
	}
	:global(:fullscreen) .controls {
		display: none;
	}

	.inner {
		display: flex;
		justify-content: space-between;
		align-items: center;
		padding-right: var(--size-2);
		padding-left: var(--size-2);
		width: var(--size-full);
		height: var(--size-full);
	}

	.icon {
		display: flex;
		justify-content: center;
		cursor: pointer;
		width: var(--size-6);
		color: white;
	}

	.volume-control-wrapper {
		position: relative;
		display: flex;
		align-items: center;
		margin-right: var(--spacing-md);
	}

	.volume-button {
		display: flex;
		justify-content: center;
		align-items: center;
		cursor: pointer;
		width: var(--size-6);
		color: white;
		border: none;
		background: none;
		padding: 0;
	}

	.time {
		flex-shrink: 0;
		margin-right: var(--size-3);
		margin-left: var(--size-3);
		color: white;
		font-size: var(--text-sm);
		font-family: var(--font-mono);
	}
	.wrap {
		position: relative;
		background-color: var(--background-fill-secondary);
		height: var(--size-full);
		width: var(--size-full);
		border-radius: var(--radius-xl);
	}
	.wrap :global(video) {
		height: var(--size-full);
		width: var(--size-full);
	}
</style>
