<script lang="ts">
	import { Play, Pause, Forward, Backward, Undo, Trim } from "@gradio/icons";
	import { get_skip_rewind_amount } from "../shared/utils";
	import type { I18nFormatter } from "@gradio/utils";
	import WaveSurfer from "wavesurfer.js";
	import RegionsPlugin, {
		type Region
	} from "wavesurfer.js/dist/plugins/regions.js";
	import type { WaveformOptions } from "./types";
	import VolumeLevels from "./VolumeLevels.svelte";
	import VolumeControl from "./VolumeControl.svelte";

	export let waveform: WaveSurfer;
	export let audio_duration: number;
	export let i18n: I18nFormatter;
	export let playing: boolean;
	export let showRedo = false;
	export let interactive = false;
	export let handle_trim_audio: (start: number, end: number) => void;
	export let mode = "";
	export let container: HTMLDivElement;
	export let handle_reset_value: () => void;
	export let waveform_options: WaveformOptions = {};
	export let trim_region_settings: WaveformOptions = {};
	export let show_volume_slider = false;
	export let editable = true;

	export let trimDuration = 0;

	let playbackSpeeds = [0.5, 1, 1.5, 2];
	let playbackSpeed = playbackSpeeds[1];

	let trimRegion: RegionsPlugin;
	let activeRegion: Region | null = null;

	let leftRegionHandle: HTMLDivElement | null;
	let rightRegionHandle: HTMLDivElement | null;
	let activeHandle = "";

	let currentVolume = 1;

	$: trimRegion = waveform.registerPlugin(RegionsPlugin.create());

	$: trimRegion?.on("region-out", (region) => {
		region.play();
	});

	$: trimRegion?.on("region-updated", (region) => {
		trimDuration = region.end - region.start;
	});

	$: trimRegion?.on("region-clicked", (region, e) => {
		e.stopPropagation(); // prevent triggering a click on the waveform
		activeRegion = region;
		region.play();
	});

	const addTrimRegion = (): void => {
		activeRegion = trimRegion.addRegion({
			start: audio_duration / 4,
			end: audio_duration / 2,
			...trim_region_settings
		});

		trimDuration = activeRegion.end - activeRegion.start;
	};

	$: if (activeRegion) {
		const shadowRoot = container.children[0]!.shadowRoot!;

		rightRegionHandle = shadowRoot.querySelector('[data-resize="right"]');
		leftRegionHandle = shadowRoot.querySelector('[data-resize="left"]');

		if (leftRegionHandle && rightRegionHandle) {
			leftRegionHandle.setAttribute("role", "button");
			rightRegionHandle.setAttribute("role", "button");
			leftRegionHandle?.setAttribute("aria-label", "Drag to adjust start time");
			rightRegionHandle?.setAttribute("aria-label", "Drag to adjust end time");
			leftRegionHandle?.setAttribute("tabindex", "0");
			rightRegionHandle?.setAttribute("tabindex", "0");

			leftRegionHandle.addEventListener("focus", () => {
				if (trimRegion) activeHandle = "left";
			});

			rightRegionHandle.addEventListener("focus", () => {
				if (trimRegion) activeHandle = "right";
			});
		}
	}

	const trimAudio = (): void => {
		if (waveform && trimRegion) {
			if (activeRegion) {
				const start = activeRegion.start;
				const end = activeRegion.end;
				handle_trim_audio(start, end);
				mode = "";
				activeRegion = null;
			}
		}
	};

	const clearRegions = (): void => {
		trimRegion?.getRegions().forEach((region) => {
			region.remove();
		});
		trimRegion?.clearRegions();
	};

	const toggleTrimmingMode = (): void => {
		clearRegions();
		if (mode === "edit") {
			mode = "";
		} else {
			mode = "edit";
			addTrimRegion();
		}
	};

	const adjustRegionHandles = (handle: string, key: string): void => {
		let newStart;
		let newEnd;

		if (!activeRegion) return;
		if (handle === "left") {
			if (key === "ArrowLeft") {
				newStart = activeRegion.start - 0.05;
				newEnd = activeRegion.end;
			} else {
				newStart = activeRegion.start + 0.05;
				newEnd = activeRegion.end;
			}
		} else {
			if (key === "ArrowLeft") {
				newStart = activeRegion.start;
				newEnd = activeRegion.end - 0.05;
			} else {
				newStart = activeRegion.start;
				newEnd = activeRegion.end + 0.05;
			}
		}

		activeRegion.setOptions({
			start: newStart,
			end: newEnd
		});

		trimDuration = activeRegion.end - activeRegion.start;
	};

	$: trimRegion &&
		window.addEventListener("keydown", (e) => {
			if (e.key === "ArrowLeft") {
				adjustRegionHandles(activeHandle, "ArrowLeft");
			} else if (e.key === "ArrowRight") {
				adjustRegionHandles(activeHandle, "ArrowRight");
			}
		});
</script>

<div class="controls" data-testid="waveform-controls">
	<div class="control-wrapper">
		<button
			class="action icon volume"
			style:color={show_volume_slider
				? "var(--color-accent)"
				: "var(--neutral-400)"}
			aria-label="Adjust volume"
			on:click={() => (show_volume_slider = !show_volume_slider)}
		>
			<VolumeLevels {currentVolume} />
		</button>

		{#if show_volume_slider}
			<VolumeControl bind:currentVolume bind:show_volume_slider {waveform} />
		{/if}

		<button
			class:hidden={show_volume_slider}
			class="playback icon"
			aria-label={`Adjust playback speed to ${
				playbackSpeeds[
					(playbackSpeeds.indexOf(playbackSpeed) + 1) % playbackSpeeds.length
				]
			}x`}
			on:click={() => {
				playbackSpeed =
					playbackSpeeds[
						(playbackSpeeds.indexOf(playbackSpeed) + 1) % playbackSpeeds.length
					];

				waveform.setPlaybackRate(playbackSpeed);
			}}
		>
			<span>{playbackSpeed}x</span>
		</button>
	</div>

	<div class="play-pause-wrapper">
		<button
			class="rewind icon"
			aria-label={`Skip backwards by ${get_skip_rewind_amount(
				audio_duration,
				waveform_options.skip_length
			)} seconds`}
			on:click={() =>
				waveform.skip(
					get_skip_rewind_amount(audio_duration, waveform_options.skip_length) *
						-1
				)}
		>
			<Backward />
		</button>
		<button
			class="play-pause-button icon"
			on:click={() => waveform.playPause()}
			aria-label={playing ? i18n("audio.pause") : i18n("audio.play")}
		>
			{#if playing}
				<Pause />
			{:else}
				<Play />
			{/if}
		</button>
		<button
			class="skip icon"
			aria-label="Skip forward by {get_skip_rewind_amount(
				audio_duration,
				waveform_options.skip_length
			)} seconds"
			on:click={() =>
				waveform.skip(
					get_skip_rewind_amount(audio_duration, waveform_options.skip_length)
				)}
		>
			<Forward />
		</button>
	</div>

	<div class="settings-wrapper">
		{#if editable && interactive}
			{#if showRedo && mode === ""}
				<button
					class="action icon"
					aria-label="Reset audio"
					on:click={() => {
						handle_reset_value();
						clearRegions();
						mode = "";
					}}
				>
					<Undo />
				</button>
			{/if}

			{#if mode === ""}
				<button
					class="action icon"
					aria-label="Trim audio to selection"
					on:click={toggleTrimmingMode}
				>
					<Trim />
				</button>
			{:else}
				<button class="text-button" on:click={trimAudio}>Trim</button>
				<button class="text-button" on:click={toggleTrimmingMode}>Cancel</button
				>
			{/if}
		{/if}
	</div>
</div>

<style>
	.settings-wrapper {
		display: flex;
		justify-self: self-end;
		align-items: center;
	}
	.text-button {
		border: 1px solid var(--neutral-400);
		border-radius: var(--radius-sm);
		font-weight: 300;
		font-size: var(--size-3);
		text-align: center;
		color: var(--neutral-400);
		height: var(--size-5);
		font-weight: bold;
		padding: 0 5px;
		margin-left: 5px;
	}

	.text-button:hover,
	.text-button:focus {
		color: var(--color-accent);
		border-color: var(--color-accent);
	}

	.controls {
		display: grid;
		grid-template-columns: 1fr 1fr 1fr;
		margin-top: 5px;
		align-items: center;
		position: relative;
	}

	.hidden {
		display: none;
	}

	.control-wrapper {
		display: flex;
		justify-self: self-start;
		align-items: center;
		justify-content: space-between;
	}

	@media (max-width: 375px) {
		.controls {
			display: flex;
			flex-wrap: wrap;
		}

		.controls * {
			margin: var(--spacing-sm);
		}

		.controls .text-button {
			margin-left: 0;
		}
	}

	.action {
		width: var(--size-5);
		width: var(--size-5);
		color: var(--neutral-400);
		margin-left: var(--spacing-md);
	}
	.icon:hover,
	.icon:focus {
		color: var(--color-accent);
	}
	.play-pause-wrapper {
		display: flex;
		justify-self: center;
	}
	.playback {
		border: 1px solid var(--neutral-400);
		border-radius: var(--radius-sm);
		width: 5.5ch;
		font-weight: 300;
		font-size: var(--size-3);
		text-align: center;
		color: var(--neutral-400);
		height: var(--size-5);
		font-weight: bold;
	}

	.playback:hover,
	.playback:focus {
		color: var(--color-accent);
		border-color: var(--color-accent);
	}

	.rewind,
	.skip {
		margin: 0 10px;
		color: var(--neutral-400);
	}

	.play-pause-button {
		width: var(--size-8);
		width: var(--size-8);
		display: flex;
		align-items: center;
		justify-content: center;
		color: var(--neutral-400);
		fill: var(--neutral-400);
	}

	.volume {
		position: relative;
		display: flex;
		justify-content: center;
		margin-right: var(--spacing-xl);
	}
</style>
