<script lang="ts">
	import { run } from 'svelte/legacy';

	import { onDestroy, onMount } from 'svelte';
	import { browser } from '$app/environment';
	import debug from 'debug';
	import { i18n } from '$lib/i18n';
	const dbg = debug('app:camera');

	let video: HTMLVideoElement = $state();
	let devices: MediaDeviceInfo[] = $state([]);
	let currentDevice: string | undefined = $state();

	let requestResolution: [number, number] | 'auto' = $state('auto');
	let requestFramerate: number | 'auto' = $state('auto');
	let deviceInfo: {
		resolution?: string;
		frameRate?: number;
	} = $state({});
	let snapshot: string | undefined = $state();
	let flipped = $state(false);

	run(() => {
		dbg('devices %O', devices);
	});
	run(() => {
		dbg('currentDevice %s', currentDevice);
	});

	onMount(() => {
		refreshDevices();
		video.addEventListener('playing', () => {
			if (browser && video?.srcObject instanceof MediaStream) {
				deviceInfo = {
					resolution: `${video.videoWidth}x${video.videoHeight}`,
					frameRate: video?.srcObject?.getVideoTracks()[0]?.getSettings().frameRate
				};
			}
		});
	});

	onDestroy(() => {
		if (browser && video?.srcObject instanceof MediaStream) {
			video.srcObject.getTracks().forEach((t) => t.stop());
		}
	});

	async function refreshDevices() {
		devices = (await navigator.mediaDevices.enumerateDevices()).filter(
			(d) => d.kind === 'videoinput'
		);
		if (!currentDevice) {
			currentDevice = devices[0]?.deviceId;
		}
	}

	run(() => {
		if (currentDevice) {
			navigator.mediaDevices
				.getUserMedia({
					video: {
						deviceId: currentDevice,
						width: requestResolution === 'auto' ? undefined : requestResolution[0],
						height: requestResolution === 'auto' ? undefined : requestResolution[1],
						frameRate: requestFramerate === 'auto' ? undefined : requestFramerate
					}
				})
				.then((stream) => {
					video.srcObject = stream;
					refreshDevices();
				});
		}
	});

	async function takeSnapshot() {
		const canvas = document.createElement('canvas');
		canvas.width = video.videoWidth;
		canvas.height = video.videoHeight;
		const ctx = canvas.getContext('2d');
		if (!ctx) return;
		if (flipped) {
			ctx.scale(-1, 1);
			ctx.translate(-canvas.width, 0);
		}
		ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
		snapshot = canvas.toDataURL('image/png');
	}
</script>

<h2><i class="ti ti-camera"></i> {$i18n.t('Camera test')}</h2>

<div class="controls">
	<label>
		{$i18n.t('Device')}
		<select bind:value={currentDevice} disabled={!devices.length}>
			{#each devices as device}
				<option value={device.deviceId}>{device.label || '???'}</option>
			{:else}
				<option>{$i18n.t('No camera found')}</option>
			{/each}
		</select>
	</label>
	<button onclick={refreshDevices}>
		<i class="ti ti-refresh"></i>
		{$i18n.t('Refresh')}
	</button>
	<div class="separator"></div>
	<label>
		{$i18n.t('Resolution')}
		<select bind:value={requestResolution}>
			<option value="auto">Auto</option>
			<option value={[4096, 2160]}>4096x2160</option>
			<option value={[3840, 2160]}>3840x2160</option>
			<option value={[1920, 1080]}>1920x1080</option>
			<option value={[1280, 720]}>1280x720</option>
			<option value={[640, 480]}>640x480</option>
			<option value={[320, 240]}>320x240</option>
		</select>
	</label>
	<label>
		{$i18n.t('Frame rate')}
		<select bind:value={requestFramerate}>
			<option value="auto">Auto</option>
			<option value={120}>120 fps</option>
			<option value={60}>60 fps</option>
			<option value={30}>30 fps</option>
			<option value={15}>15 fps</option>
			<option value={10}>10 fps</option>
			<option value={5}>5 fps</option>
		</select>
	</label>
</div>

<div class="display" class:snapshot={Boolean(snapshot)}>
	<!-- svelte-ignore a11y_media_has_caption -->
	<video class:flipped bind:this={video} autoplay class:unloaded={!currentDevice}></video>
	{#if snapshot}
		<!-- svelte-ignore a11y_missing_attribute -->
		<!--suppress HtmlRequiredAltAttribute -->
		<img src={snapshot} />
		<button onclick={() => (snapshot = undefined)}><i class="ti ti-x"></i></button>
	{/if}
</div>

<footer>
	{#if !currentDevice}
		<span class="subdued">{$i18n.t('No camera selected')}</span>
	{:else}
		<ul>
			{#key currentDevice}
				<li>
					{$i18n.t('Resolution')}: <strong>{deviceInfo.resolution || '???'}</strong>
				</li>
				<li>
					{$i18n.t('Frame rate')}: <strong>{deviceInfo.frameRate || '???'}</strong>
				</li>
			{/key}
		</ul>
		<div class="controls">
			<button onclick={takeSnapshot}>
				<i class="ti ti-camera"></i>
				{$i18n.t('Take picture')}
			</button>
			<button onclick={() => (flipped = !flipped)}>
				<i class="ti ti-flip-vertical"></i>
				{#if flipped}
					{$i18n.t('Unflip image')}
				{:else}
					{$i18n.t('Flip image')}
				{/if}
			</button>
		</div>
	{/if}
</footer>

<style>
	.controls {
		display: flex;
		align-items: end;
		justify-content: stretch;
		gap: 1em;

		& label:first-child {
			flex-grow: 1;
			min-width: 0;
		}
	}

	select {
		background: black;
		color: white;
		padding: 0.25em 0.5em;
		border-radius: 0.25em;
		border: 1px solid white;
	}

	label {
		display: flex;
		flex-direction: column;
		gap: 0.2em;

		font-size: 0.8em;
		& select {
			font-size: initial;
		}
	}

	.display {
		position: relative;
		display: flex;
		flex-direction: column;
		min-height: 0;
		flex-grow: 1;
		justify-content: center;

		& img {
			object-fit: contain;
		}

		& button {
			position: absolute;
			top: 1em;
			right: 1em;
		}

		&.snapshot {
			& video {
				display: none;
			}
		}
	}

	video {
		min-width: 0;
		min-height: 0;
		max-width: 100%;
		max-height: 100%;

		margin: 1em 0;

		&.unloaded {
			background: repeating-linear-gradient(45deg, gray, gray 20px, darkgray 20px, darkgray 40px);
			flex-grow: 1;
		}

		&.flipped {
			transform: scaleX(-1);
		}
	}

	footer {
		display: flex;
		justify-content: space-between;
		align-items: center;
	}

	ul {
		list-style: none;
		margin: 0;
		padding: 0;
		display: inline-flex;
		gap: 1rem;
	}

	.subdued {
		opacity: 0.8;
	}
</style>
