<script lang="ts">
	import { onDestroy } from 'svelte';
	import type { VideoConstructor } from '../Player/Video';
	import { FFmpeg, type LogEvent } from '@ffmpeg/ffmpeg';
	import { fetchFile, toBlobURL } from '@ffmpeg/util';

	let {
		Video: VideoImplementation
	}: {
		Video: VideoConstructor;
	} = $props();
	let canvas = $state(null as null | HTMLCanvasElement);

	let frame = $state(0);
	let frameCount = $state(0);
	let active = false;

	let lastAnimationFrame = 0;
	let startedAt = 0;
	let message = $state('Waiting');
	let videoUrl = $state(null as null | string);

	const ffmpegBaseURL = 'https://unpkg.com/@ffmpeg/core-mt@0.12.6/dist/esm';

	const start = async (format = 'mp4') => {
		active = true;
		startedAt = performance.now();

		const ffmpeg = new FFmpeg();
		message = 'Loading ffmpeg-core.js';
		ffmpeg.on('log', ({ message: msg }: LogEvent) => {
			message = msg;
			console.log(message);
		});
		await ffmpeg.load({
			coreURL: await toBlobURL(`${ffmpegBaseURL}/ffmpeg-core.js`, 'text/javascript'),
			wasmURL: await toBlobURL(`${ffmpegBaseURL}/ffmpeg-core.wasm`, 'application/wasm'),
			workerURL: await toBlobURL(`${ffmpegBaseURL}/ffmpeg-core.worker.js`, 'text/javascript')
		});

		message = 'Making directory';
		await ffmpeg.createDir('frames');
		message = 'Preparing Canvas';
		const c = canvas!;
		const video = new VideoImplementation(c);
		video['_isInit'] = true;
		await video.init({
			isPreview: false
		});
		video['_isInit'] = false;
		frameCount = video.length;
		message = 'Rendering first few frames...';
		for (frame = 0; frame <= frameCount; frame++) {
			if (!active) return;
			await video.renderFrame({
				frames: frame,
				milliseconds: (frame / video.fps) * 1000,
				seconds: frame / video.fps
			});
			// TODO: see if we can pipe this into an active ffmpeg instead of writing a file then running a command after
			const blob = await new Promise<Blob>((rs, rj) =>
				c.toBlob((blob) => (blob ? rs(blob) : rj(new Error('No blob gotten back'))), 'image/png')
			);
			const u = URL.createObjectURL(blob);
			const file = await fetchFile(u);
			URL.revokeObjectURL(u);
			await ffmpeg.writeFile('frames/f' + frame.toString().padStart(10, '0') + '.png', file);
			if (performance.now() > lastAnimationFrame + 33) {
				await new Promise((rs) => requestAnimationFrame(rs));
				lastAnimationFrame = performance.now();
				message = `Rendering to pngs - ${frame}/${frameCount} frames | running for ${(
					Math.floor(lastAnimationFrame - startedAt) / 1000
				).toFixed(3)} seconds`;
			}
		}

		message = 'Start transcoding';
		await ffmpeg.exec([
			'-framerate',
			video.fps.toString(),
			'-pattern_type',
			'sequence',
			'-start_number',
			'0',
			'-pattern_type',
			'glob',
			'-i',
			'frames/f*.png',
			// 'frames/f%04d.png',
			'middle.' + format
		]);
		message = 'Disposing pngs';
		for (const i of await ffmpeg.listDir('frames'))
			if (!i.isDir) await ffmpeg.deleteFile('frames/' + i.name);
		await ffmpeg.deleteDir('frames/');
		if (video.audioUrl) {
			message = 'Fetching Audio';
			await ffmpeg.writeFile('audio.' + video.audioUrl[0], await fetchFile(video.audioUrl[1]));
			message = 'Merging Audio';
			await ffmpeg.exec([
				'-i',
				'middle.' + format,
				'-r',
				video.fps.toString(),
				'-i',
				'audio.' + video.audioUrl[0],
				'-c:v',
				'copy',
				'-map',
				'0:v:0',
				'-map',
				'1:a:0',
				'-frames:v',
				video.length.toString(),
				'output.' + format
			]);
			message = 'Removing videoless file';
			await ffmpeg.deleteFile('middle.' + format);
		} else await ffmpeg.rename('middle.' + format, 'output.' + format);
		message = 'Reading File';
		const data = await ffmpeg.readFile('output.' + format);
		console.log('done');
		videoUrl = URL.createObjectURL(
			// @ts-ignore bufferlike is good enuf
			new Blob([(data as Uint8Array).buffer], { type: 'video/' + format })
		);
		message = 'Disposing ffmpeg state';
		await ffmpeg.deleteFile('output.' + format);
		// TODO: save dialog with preview
		const el = document.createElement('a');
		el.download = `output.${format}`;
		el.href = videoUrl;
		el.textContent = '-';
		document.body.appendChild(el);
		el.click();
		setTimeout(() => {
			el.remove();
		}, 10);
	};
	$effect(() => {
		if (canvas && VideoImplementation) start();
	});
	onDestroy(() => (active = false));
</script>

<svelte:head>
	<title>{message} - Videotool Renderer</title>
</svelte:head>

<div class="flex flex-col h-screen w-screen">
	{#if videoUrl}
		<!-- svelte-ignore a11y_media_has_caption -->
		<video src={videoUrl} controls class="max-w-screen max-h-screen flex-1"></video>
	{:else}
		<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>
			</div>
		</div>
	{/if}
	<p class="p-4">
		{message}
	</p>
</div>
