/**
 * AttachmentAudio — custom audio player for `audio/*` attachments.
 * Ported from the new UI's Fluxer-style layout but simplified for
 * our Convex pipeline: takes the already-decrypted blob URL from
 * `EncryptedAttachment` instead of resolving an MXC URL itself.
 *
 *   ┌──────────────────────────────────────────────────────────┐
 *   │ ┌───┐  filename-truncated…              .mp3             │
 *   │ │ ▶ │  ────────────────────────●───              0:12/3:04│
 *   │ └───┘                                                    │
 *   │ ◀)                                          1x        ↓  │
 *   └──────────────────────────────────────────────────────────┘
 */
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
	Download,
	Pause,
	Play,
	SpeakerHigh,
	SpeakerSlash,
	Star,
} from '@phosphor-icons/react';
import { useMutation, useQuery } from 'convex/react';
import { api } from '../../../../../convex/_generated/api';
import type { Id } from '../../../../../convex/_generated/dataModel';
import type { AttachmentMetadata } from './EncryptedAttachment';
import styles from './AttachmentAudio.module.css';

interface AttachmentAudioProps {
	src: string;
	filename: string;
	attachment?: AttachmentMetadata;
}

const PLAYBACK_SPEEDS = [1, 1.25, 1.5, 2, 0.5, 0.75];

function splitFilename(filename: string): { stem: string; ext: string } {
	const dot = filename.lastIndexOf('.');
	if (dot <= 0 || dot === filename.length - 1) {
		return { stem: filename, ext: '' };
	}
	return { stem: filename.slice(0, dot), ext: filename.slice(dot) };
}

function formatTime(seconds: number): string {
	if (!Number.isFinite(seconds) || seconds < 0) return '0:00';
	const total = Math.floor(seconds);
	const m = Math.floor(total / 60);
	const s = total % 60;
	return `${m}:${s.toString().padStart(2, '0')}`;
}

export function AttachmentAudio({ src, filename, attachment }: AttachmentAudioProps) {
	const audioRef = useRef<HTMLAudioElement>(null);
	const [isPlaying, setIsPlaying] = useState(false);
	const [currentTime, setCurrentTime] = useState(0);
	const [duration, setDuration] = useState(0);
	const [volume, setVolume] = useState(1);
	const [isMuted, setIsMuted] = useState(false);
	const [playbackRateIndex, setPlaybackRateIndex] = useState(0);

	const { stem, ext } = useMemo(() => splitFilename(filename), [filename]);

	// Saved-media wiring — mirrors the ImageLightbox star button so
	// audio attachments can be bookmarked into the Media tab.
	const myUserId =
		typeof localStorage !== 'undefined' ? localStorage.getItem('userId') : null;
	const savedList = useQuery(
		api.savedMedia.list,
		myUserId && attachment
			? { userId: myUserId as Id<'userProfiles'> }
			: 'skip',
	);
	const isSaved = !!(
		attachment && savedList?.some((m) => m.url === attachment.url)
	);
	const saveMutation = useMutation(api.savedMedia.save);
	const removeMutation = useMutation(api.savedMedia.remove);

	const handleToggleSaved = useCallback(async () => {
		if (!attachment || !myUserId) return;
		try {
			if (isSaved) {
				await removeMutation({
					userId: myUserId as Id<'userProfiles'>,
					url: attachment.url,
				});
			} else {
				await saveMutation({
					userId: myUserId as Id<'userProfiles'>,
					url: attachment.url,
					kind: attachment.mimeType.split('/')[0],
					filename: attachment.filename,
					mimeType: attachment.mimeType,
					width: attachment.width,
					height: attachment.height,
					size: attachment.size,
					encryptionKey: attachment.key,
					encryptionIv: attachment.iv,
				});
			}
		} catch (err) {
			console.warn('Failed to toggle saved audio:', err);
		}
	}, [attachment, isSaved, myUserId, removeMutation, saveMutation]);

	useEffect(() => {
		const el = audioRef.current;
		if (!el) return;
		const handleTime = () => setCurrentTime(el.currentTime);
		const handleDuration = () => setDuration(el.duration);
		const handlePlay = () => setIsPlaying(true);
		const handlePause = () => setIsPlaying(false);
		const handleEnded = () => setIsPlaying(false);
		const handleVolume = () => {
			setVolume(el.volume);
			setIsMuted(el.muted);
		};
		el.addEventListener('timeupdate', handleTime);
		el.addEventListener('loadedmetadata', handleDuration);
		el.addEventListener('durationchange', handleDuration);
		el.addEventListener('play', handlePlay);
		el.addEventListener('pause', handlePause);
		el.addEventListener('ended', handleEnded);
		el.addEventListener('volumechange', handleVolume);
		return () => {
			el.removeEventListener('timeupdate', handleTime);
			el.removeEventListener('loadedmetadata', handleDuration);
			el.removeEventListener('durationchange', handleDuration);
			el.removeEventListener('play', handlePlay);
			el.removeEventListener('pause', handlePause);
			el.removeEventListener('ended', handleEnded);
			el.removeEventListener('volumechange', handleVolume);
		};
	}, []);

	useEffect(() => {
		const el = audioRef.current;
		if (!el) return;
		el.playbackRate = PLAYBACK_SPEEDS[playbackRateIndex];
	}, [playbackRateIndex]);

	const handleTogglePlay = useCallback(() => {
		const el = audioRef.current;
		if (!el || !src) return;
		if (el.paused) {
			void el.play().catch(() => {});
		} else {
			el.pause();
		}
	}, [src]);

	const handleSeek = useCallback(
		(e: React.ChangeEvent<HTMLInputElement>) => {
			const el = audioRef.current;
			if (!el) return;
			const next = Number(e.target.value);
			el.currentTime = next;
			setCurrentTime(next);
		},
		[],
	);

	const handleToggleMute = useCallback(() => {
		const el = audioRef.current;
		if (!el) return;
		el.muted = !el.muted;
	}, []);

	const handleVolumeSlider = useCallback(
		(e: React.ChangeEvent<HTMLInputElement>) => {
			const el = audioRef.current;
			if (!el) return;
			const next = Number(e.target.value);
			el.volume = next;
			if (next > 0 && el.muted) el.muted = false;
			else if (next === 0 && !el.muted) el.muted = true;
		},
		[],
	);

	const handleCycleSpeed = useCallback(() => {
		setPlaybackRateIndex((i) => (i + 1) % PLAYBACK_SPEEDS.length);
	}, []);

	const handleDownload = useCallback(() => {
		if (!src) return;
		const a = document.createElement('a');
		a.href = src;
		a.download = filename;
		document.body.appendChild(a);
		a.click();
		document.body.removeChild(a);
	}, [src, filename]);

	const progressRatio =
		duration > 0 ? Math.max(0, Math.min(1, currentTime / duration)) : 0;
	const progressPercent = `${(progressRatio * 100).toFixed(2)}%`;
	const playbackSpeedLabel = `${PLAYBACK_SPEEDS[playbackRateIndex]}x`;

	return (
		<div className={styles.card}>
			<div className={styles.topRow}>
				<button
					type="button"
					className={styles.playButton}
					onClick={handleTogglePlay}
					disabled={!src}
					aria-label={isPlaying ? 'Pause' : 'Play'}
					title={isPlaying ? 'Pause' : 'Play'}
				>
					{isPlaying ? (
						<Pause size={18} weight="fill" />
					) : (
						<Play size={18} weight="fill" />
					)}
				</button>

				<div className={styles.filenameRow}>
					<span className={styles.filenameStem} title={filename}>
						{stem}
					</span>
					{ext && <span className={styles.filenameExt}>{ext}</span>}
				</div>
			</div>

			<div className={styles.progressRow}>
				<div
					className={styles.progressTrack}
					style={{ ['--progress' as string]: progressPercent }}
				>
					<div className={styles.progressFill} />
					<input
						type="range"
						className={styles.progressInput}
						min={0}
						max={duration || 0}
						step={0.01}
						value={currentTime}
						onChange={handleSeek}
						disabled={!duration}
						aria-label="Seek audio"
					/>
				</div>
				<span className={styles.timeLabel}>
					{formatTime(currentTime)} / {formatTime(duration)}
				</span>
			</div>

			<div className={styles.bottomRow}>
				<div className={styles.volumeWrap}>
					<div className={styles.volumePopover}>
						<div
							className={styles.volumeTrack}
							style={{
								['--volume' as string]: `${(isMuted ? 0 : volume) * 100}%`,
							}}
						>
							<div className={styles.volumeFill} />
							<input
								type="range"
								min={0}
								max={1}
								step={0.01}
								value={isMuted ? 0 : volume}
								onChange={handleVolumeSlider}
								className={styles.volumeInput}
								aria-label="Volume"
							/>
						</div>
					</div>

					<button
						type="button"
						className={styles.iconButton}
						onClick={handleToggleMute}
						aria-label={isMuted || volume === 0 ? 'Unmute' : 'Mute'}
						title={isMuted || volume === 0 ? 'Unmute' : 'Mute'}
					>
						{isMuted || volume === 0 ? (
							<SpeakerSlash size={18} weight="fill" />
						) : (
							<SpeakerHigh size={18} weight="fill" />
						)}
					</button>
				</div>

				<div className={styles.bottomRowRight}>
					<button
						type="button"
						className={styles.speedButton}
						onClick={handleCycleSpeed}
						aria-label={`Playback speed: ${playbackSpeedLabel}`}
						title="Playback speed"
					>
						{playbackSpeedLabel}
					</button>
					{attachment && (
						<button
							type="button"
							className={styles.iconButton}
							onClick={() => void handleToggleSaved()}
							aria-label={isSaved ? 'Unfavorite' : 'Favorite'}
							title={isSaved ? 'Unfavorite' : 'Favorite'}
							style={
								isSaved
									? { color: 'var(--brand-primary, #5865f2)' }
									: undefined
							}
						>
							<Star size={18} weight={isSaved ? 'fill' : 'regular'} />
						</button>
					)}
					<button
						type="button"
						className={styles.iconButton}
						onClick={handleDownload}
						disabled={!src}
						aria-label="Download"
						title="Download"
					>
						<Download size={18} weight="regular" />
					</button>
				</div>
			</div>

			<audio
				ref={audioRef}
				src={src || undefined}
				preload="metadata"
				className={styles.hiddenAudio}
			/>
		</div>
	);
}
