"use client"

import type { IWaveformTrackProps } from "./WaveformTrack.d"
import type { JSX } from "react"

import { useMediaRemote, useMediaState } from "@vidstack/react"
import {
	FunctionComponent,
	useCallback,
	useEffect,
	useRef,
	useState,
} from "react"
import WaveSurfer from "wavesurfer.js"

import styles from "./WaveformTrack.module.scss"

function resolveWaveformColors(): { wave: string; progress: string } {
	const root = getComputedStyle(document.documentElement)
	return {
		wave:
			root.getPropertyValue("--waveform-wave").trim() ||
			"hsl(297, 70%, 44%)",
		progress:
			root.getPropertyValue("--waveform-progress").trim() ||
			"hsl(293, 100%, 34%)",
	}
}

export const WaveformTrack: FunctionComponent<
	IWaveformTrackProps
> = ({ audioUrl, pixelsPerSecond, height, duration }): JSX.Element => {
	const currentTime = useMediaState("currentTime")
	const remote = useMediaRemote()
	const containerRef = useRef<HTMLDivElement>(null)
	const wsRef = useRef<WaveSurfer | null>(null)
	const isSeekingRef = useRef(false)
	const isReadyRef = useRef(false)
	const initialPpsRef = useRef(pixelsPerSecond)
	const [theme, setTheme] = useState(
		() => document.documentElement.getAttribute("data-theme") ?? "light",
	)

	const handleClick = useCallback(
		(event: React.MouseEvent<HTMLDivElement>) => {
			const rect = event.currentTarget.getBoundingClientRect()
			const x = event.clientX - rect.left
			const time = Math.max(0, Math.min(x / pixelsPerSecond, duration))

			isSeekingRef.current = true
			remote.seek(time)

			setTimeout(() => {
				isSeekingRef.current = false
			}, 100)
		},
		[duration, pixelsPerSecond, remote],
	)

	useEffect(() => {
		if (!containerRef.current) return

		isReadyRef.current = false
		initialPpsRef.current = pixelsPerSecond

		const colors = resolveWaveformColors()

		const ws = WaveSurfer.create({
			container: containerRef.current,
			url: audioUrl,
			duration,
			height,
			waveColor: colors.wave,
			progressColor: colors.progress,
			cursorWidth: 0,
			barWidth: 2,
			barGap: 1,
			barRadius: 2,
			normalize: true,
			interact: false,
			minPxPerSec: pixelsPerSecond,
			hideScrollbar: true,
			fillParent: false,
			autoCenter: false,
			autoScroll: false,
			dragToSeek: false,
			mediaControls: false,
			backend: "MediaElement",
		})

		ws.on("ready", () => {
			isReadyRef.current = true
		})

		// Keep wavesurfer silent; video player is the source of truth.
		ws.setVolume(0)
		wsRef.current = ws

		return () => {
			ws.destroy()
			wsRef.current = null
			isReadyRef.current = false
		}
	}, [audioUrl, duration, height, pixelsPerSecond])

	// Observe theme changes on <html data-theme>
	useEffect(() => {
		const observer = new MutationObserver(() => {
			setTheme(
				document.documentElement.getAttribute("data-theme") ?? "light",
			)
		})
		observer.observe(document.documentElement, {
			attributes: true,
			attributeFilter: ["data-theme"],
		})
		return () => observer.disconnect()
	}, [])

	// Update waveform colors when theme changes
	useEffect(() => {
		const ws = wsRef.current
		if (!ws || !isReadyRef.current) return

		const colors = resolveWaveformColors()
		ws.setOptions({
			waveColor: colors.wave,
			progressColor: colors.progress,
		})
	}, [theme])

	useEffect(() => {
		const ws = wsRef.current
		if (!ws || !isReadyRef.current) return

		if (pixelsPerSecond === initialPpsRef.current) return

		try {
			ws.zoom(pixelsPerSecond)
		} catch {
			// wavesurfer may throw if audio is not fully decoded yet
		}
	}, [pixelsPerSecond])

	useEffect(() => {
		const ws = wsRef.current
		if (!ws || !isReadyRef.current || isSeekingRef.current) return

		if (ws.getDuration() <= 0) return

		if (Math.abs(ws.getCurrentTime() - currentTime) <= 0.08) return

		try {
			ws.setTime(currentTime)
		} catch {
			// ignore if not ready
		}
	}, [currentTime])

	return (
		<div
			className={styles.root}
			data-testid="WaveformTrack"
			ref={containerRef}
			onClick={handleClick}
			style={{ height }}
		/>
	)
}
