// adapted from: https://codepen.io/uixamp / https://arisetyo.github.io

function human_readable_time(duration) {
	return new Date(duration * 1000).toISOString().slice(duration > 3600 ? 11 : 14, 19);
}

const DISPLAY_WINDOW = 1;
const BAR_COUNT = 48;
const BAR_GAP = 2;
const BAR_CAP_HEIGHT = 8;
const PROGRESS_TICKS = 1000;
const MS_BETWEEN_PROGRESS_UPDATES = 200;

new MutationObserver((mutations) => {
	const uninitialized_audio_elements = document.querySelectorAll(
		".audio-container:not([data-audio-player])",
	);
	for (const player of uninitialized_audio_elements) {
		const CURRENT_TIMES = player.querySelectorAll(
			".audio-controls-container .time-container .current",
		);
		const DURATIONS = player.querySelectorAll(
			".audio-controls-container .time-container .duration",
		);

		let progress_being_changed_by_user = false;

		const AUDIO = player.querySelector("audio");
		AUDIO.controls = false;
		AUDIO.addEventListener("ended", () => {
			delete player.dataset.playing;
		});
		AUDIO.addEventListener("loadedmetadata", () => {
			for (const duration of DURATIONS) {
				duration.innerHTML = human_readable_time(AUDIO.duration);
			}
		});
		AUDIO.addEventListener("timeupdate", () => {
			if (!progress_being_changed_by_user) {
				PROGRESS.value = parseInt((AUDIO.currentTime / AUDIO.duration) * PROGRESS_TICKS);
			}

			for (const current of CURRENT_TIMES) {
				current.innerHTML = human_readable_time(AUDIO.currentTime);
			}
		});
		AUDIO.addEventListener("volumechange", () => {
			if (!volume_being_changed_by_user) {
				VOLUME.value = parseInt(AUDIO.volume * 100);
			}

			for (const volume_display of VOLUME_DISPLAYS) {
				volume_display.innerHTML = parseInt(AUDIO.volume * 100);
			}
		});

		player.addEventListener("keydown", (event) => {
			const key = event.which ?? event.keyCode;
			switch (key) {
				case 32: // space
					event.preventDefault();
					if (player.dataset.playing) {
						AUDIO.pause();
						delete player.dataset.playing;
					} else {
						AUDIO.play();
						player.dataset.playing = true;
					}
					break;
				case 37:
					event.preventDefault();
					AUDIO.currentTime -= 5;
					break;
				case 39:
					event.preventDefault();
					AUDIO.currentTime += 5;
					break;
				default:
					break;
			}
		});

		const VOLUME = player.querySelector('.audio-controls-container input[name="volume"]');
		const VOLUME_DISPLAYS = player.querySelectorAll(
			".audio-controls-container .volume-display",
		);

		let volume_being_changed_by_user = false;

		VOLUME.addEventListener("pointerdown", (event) => {
			volume_being_changed_by_user = true;
		});
		VOLUME.addEventListener("pointerup", () => {
			volume_being_changed_by_user = false;
		});

		function on_volume_changed() {
			if (volume_being_changed_by_user) {
				AUDIO.volume = Math.min(1.0, Math.max(VOLUME.value / 100, 0));
			}

			for (const volume_display of VOLUME_DISPLAYS) {
				volume_display.innerHTML = parseInt(AUDIO.volume * 100);
			}
		}
		VOLUME.addEventListener("change", on_volume_changed);
		VOLUME.addEventListener("input", on_volume_changed);

		VOLUME.value = Math.min(100, Math.max(AUDIO.volume * 100, 0));

		const PROGRESS = player.querySelector('.audio-controls-container input[name="progress"]');
		PROGRESS.min = 0;
		PROGRESS.max = PROGRESS_TICKS;

		PROGRESS.addEventListener("pointerdown", (event) => {
			progress_being_changed_by_user = true;
		});
		PROGRESS.addEventListener("pointerup", () => {
			progress_being_changed_by_user = false;
		});

		function on_progress_changed() {
			if (progress_being_changed_by_user) {
				AUDIO.currentTime = (PROGRESS.value / PROGRESS_TICKS) * AUDIO.duration;
			}
		}

		PROGRESS.addEventListener("change", on_progress_changed);
		PROGRESS.addEventListener("input", on_progress_changed);

		let CANVAS;
		let ACTX;

		let ANALYSER;
		let DATA;
		let SOURCE;

		let CTX;

		let VIZ_RGB;

		function init() {
			CANVAS = CANVAS ?? player.querySelector("canvas");
			CANVAS.height = 255;
			CANVAS.width = BAR_COUNT * (16 + BAR_GAP);

			VIZ_RGB = window
				.getComputedStyle(CANVAS)
				.color.slice(11, -1)
				.split(/[ ,]/)
				.map((v) => parseInt(Math.min(v * 255, 255)));

			ACTX = ACTX ?? new AudioContext();

			if (!ANALYSER) {
				ANALYSER = ACTX.createAnalyser();
			}

			DATA = DATA ?? new Uint8Array(ANALYSER.frequencyBinCount);

			if (!SOURCE) {
				SOURCE = ACTX.createMediaElementSource(AUDIO);
				SOURCE.connect(ANALYSER);
				SOURCE.connect(ACTX.destination);
			}

			CTX = CTX ?? CANVAS.getContext("2d");

			for (const current of CURRENT_TIMES) {
				current.innerHTML = human_readable_time(AUDIO.currentTime);
			}
		}

		let previous_max_values;
		function draw() {
			previous_max_values =
				previous_max_values ?? new Array(ANALYSER.frequencyBinCount).fill(0);

			if (!player.dataset.playing) {
				if (previous_max_values.all((v) => v === 0)) return;
			}

			ANALYSER.getByteFrequencyData(DATA);

			const gradient = CTX.createLinearGradient(0, 0, 0, CANVAS.height);
			gradient.addColorStop(
				0.4,
				`rgba( ${parseInt(VIZ_RGB[0] / 4)}, ${parseInt(VIZ_RGB[1] / 4)}, ${parseInt(VIZ_RGB[2] / 4)}, 0)`,
			);
			gradient.addColorStop(1, `rgba( ${VIZ_RGB[0]}, ${VIZ_RGB[1]}, ${VIZ_RGB[2]}, 0.25)`);

			const bar_width = parseInt(CANVAS.width / BAR_COUNT);
			const stride = parseInt((DATA.length * DISPLAY_WINDOW) / BAR_COUNT);

			CTX.clearRect(0, 0, CANVAS.width, CANVAS.height);

			for (let i = 0; i < BAR_COUNT; ++i) {
				const value = DATA[i * stride];

				const cap_value = value > previous_max_values[i] ? value : previous_max_values[i];
				const cap_color_value = 0.33 + cap_value / 255;
				CTX.fillStyle = `rgba(${VIZ_RGB[0]}, ${VIZ_RGB[1]}, ${VIZ_RGB[2]}, 0.8)`;
				CTX.fillRect(
					i * (bar_width + BAR_GAP),
					CANVAS.height - cap_value,
					bar_width,
					BAR_CAP_HEIGHT,
				);

				previous_max_values[i] =
					value > previous_max_values[i]
						? value
						: previous_max_values[i] - BAR_CAP_HEIGHT / 2;

				CTX.fillStyle = gradient;
				CTX.fillRect(
					i * (bar_width + BAR_GAP),
					CANVAS.height - value + BAR_CAP_HEIGHT + BAR_CAP_HEIGHT,
					bar_width,
					value,
				);
			}

			requestAnimationFrame(draw);
		}

		player.querySelector(".audio-control.skip-back")?.addEventListener("click", (event) => {
			init();
			AUDIO.currentTime = 0;
		});

		player
			.querySelector(".audio-control.play-pause-toggle")
			.addEventListener("click", (event) => {
				init();

				const is_playing = player.dataset.playing;
				if (is_playing) {
					AUDIO.pause();
					delete player.dataset.playing;
					// CTX.clearRect(0, 0, canvasEl.width, canvasEl.height);
					return;
				}

				AUDIO.play();
				player.dataset.playing = true;
				draw();
			});

		player.querySelector(".audio-control.skip-forward")?.addEventListener("click", (event) => {
			init();

			AUDIO.currentTime = AUDIO.duration;
			AUDIO.pause();
			delete player.dataset.playing;
		});

		player.dataset.audioPlayer = true;
	}
}).observe(document, {
	subtree: true,
	childList: true,
});
