import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import Input, { Textarea } from '@/components/elements/Input';
import { Dialog } from '@/components/elements/dialog';
import { Button } from '@/components/elements/button/index';
import { colors } from '../colors';
import { themeColors } from '../colorExtractor';
import {
    AudioPlayerContainer,
    VisualizerCanvas,
    AudioContent,
    BeatNotesLayer,
    ArtworkStack,
    VinylDisk,
    AudioArtwork,
    AudioIconWrapper,
    WaveformCanvas,
    VolumeSlider,
    RateSlider,
} from './styles';
import { AudioPlayerProps, ParsedAudioMetadata } from './types';
import {
    formatTime,
    readStoredVolume,
    writeStoredVolume,
    applyMediaSettings,
    globalAudioRegistry,
    syncAllAudio,
    syncSeekAll,
    syncSkipAll,
    toArrayBuffer,
} from './utils';
import {
    parseId3Metadata,
    buildTextFrame,
    buildApicFrame,
    buildLyricsFrame,
    stripId3Tag,
    buildId3Tag,
    createArtworkUrl,
    loadMetadataFromBlob,
} from './id3';
import { createWaveformState, drawVisualizer, drawTimeline, emitBeatNote, type WaveformPalette, type WaveformState } from './waveform';

const MAX_METADATA_EDIT_SIZE = 25 * 1024 * 1024;
const ARTWORK_SAMPLE_SIZE = 40;
const TIMED_LYRIC_PATTERN = /^\[(\d{2}):([0-5]\d):([0-5]\d)(?:[.,](\d{1,3}))?\]\s*(.+)$/;
const LYRICS_RENDER_STORAGE_KEY = 'betterfiles.audio.lyrics.render';

type LyricEffect = 'shake' | 'zoom' | 'pulse' | 'flash';

const LYRIC_EFFECTS: LyricEffect[] = ['shake', 'zoom', 'pulse', 'flash'];

type TimedLyric = {
    time: number;
    text: string;
    effect?: LyricEffect;
};

const readLyricsRenderEnabled = () => {
    if (typeof window === 'undefined') return true;
    try {
        return window.localStorage.getItem(LYRICS_RENDER_STORAGE_KEY) !== '0';
    } catch {
        return true;
    }
};

const normalizeLyricsText = (value: string) => value.replace(/\r\n?/g, '\n').trim();

const normalizeLyricEffect = (value?: string): LyricEffect | undefined => {
    const normalized = value?.trim().toLowerCase();
    return LYRIC_EFFECTS.find((effect) => effect === normalized);
};

const extractLyricEffect = (value: string): { text: string; effect?: LyricEffect } => {
    let text = value.trim();
    let effect: LyricEffect | undefined;

    const prefixed = text.match(/^\[(shake|zoom|pulse|flash)\]\s*/i);
    if (prefixed) {
        effect = normalizeLyricEffect(prefixed[1]);
        text = text.slice(prefixed[0].length).trim();
    }

    const braced = text.match(/\s+\{(shake|zoom|pulse|flash)\}\s*$/i);
    if (braced) {
        effect = normalizeLyricEffect(braced[1]) || effect;
        text = text.slice(0, braced.index).trim();
    }

    const piped = text.match(/\s+\|\s*(?:effect\s*=\s*)?(shake|zoom|pulse|flash)\s*$/i);
    if (piped) {
        effect = normalizeLyricEffect(piped[1]) || effect;
        text = text.slice(0, piped.index).trim();
    }

    return { text, effect };
};

const parseTimedLyricLine = (line: string): TimedLyric | null => {
    const match = line.match(TIMED_LYRIC_PATTERN);
    if (!match) return null;

    const parsed = extractLyricEffect(match[5]);
    if (!parsed.text) return null;

    const milliseconds = match[4] ? Number(match[4].padEnd(3, '0')) / 1000 : 0;

    return {
        time: Number(match[1]) * 3600 + Number(match[2]) * 60 + Number(match[3]) + milliseconds,
        text: parsed.text,
        effect: parsed.effect,
    };
};

const parseTimedLyrics = (value?: string): TimedLyric[] => {
    if (!value) return [];

    return normalizeLyricsText(value)
        .split('\n')
        .map(parseTimedLyricLine)
        .filter((line): line is TimedLyric => !!line && line.text.length > 0)
        .sort((a, b) => a.time - b.time);
};

const getLyricsValidationError = (value: string): string | null => {
    const normalized = normalizeLyricsText(value);
    if (!normalized) return null;
    if (normalized.length > 64 * 1024) {
        return 'Lyrics metadata is limited to 64 KB.';
    }

    const invalid = normalized.split('\n').find((line) => line.trim() && !parseTimedLyricLine(line));
    if (invalid) {
        return `Invalid lyric line: ${invalid}. Use [hh:mm:ss] Lyric, [hh:mm:ss.mmm] Lyric, or [hh:mm:ss.mmm][shake] Lyric`;
    }

    return null;
};

const getLyricEffectFrames = (effect: LyricEffect): Keyframe[] => {
    switch (effect) {
        case 'shake':
            return [
                { transform: 'translate3d(0, 0, 0)' },
                { transform: 'translate3d(-8px, 0, 0)' },
                { transform: 'translate3d(7px, 0, 0)' },
                { transform: 'translate3d(-5px, 0, 0)' },
                { transform: 'translate3d(3px, 0, 0)' },
                { transform: 'translate3d(0, 0, 0)' },
            ];
        case 'zoom':
            return [
                { transform: 'scale(1)' },
                { transform: 'scale(1.028)' },
                { transform: 'scale(1)' },
            ];
        case 'pulse':
            return [
                { transform: 'scale(1)', filter: 'brightness(1)' },
                { transform: 'scale(1.012)', filter: 'brightness(1.18)' },
                { transform: 'scale(1)', filter: 'brightness(1)' },
            ];
        case 'flash':
            return [
                { filter: 'brightness(1)' },
                { filter: 'brightness(1.35)' },
                { filter: 'brightness(1)' },
            ];
    }
};

const VolumeIcon: React.FC<{ muted: boolean; volume: number; className?: string }> = ({ muted, volume, className = 'w-4 h-4' }) => {
    const silent = muted || volume <= 0;
    const low = !silent && volume < 0.5;

    return (
        <svg className={className} fill='none' stroke='currentColor' strokeWidth={2} viewBox='0 0 24 24' aria-hidden='true'>
            <path strokeLinecap='round' strokeLinejoin='round' d='M5 9v6h4l5 4V5L9 9H5z' />
            {silent ? (
                <>
                    <path strokeLinecap='round' strokeLinejoin='round' d='M18 9l4 4' />
                    <path strokeLinecap='round' strokeLinejoin='round' d='M22 9l-4 4' />
                </>
            ) : (
                <>
                    <path strokeLinecap='round' strokeLinejoin='round' d='M17 10.5a3 3 0 010 3' />
                    {!low && <path strokeLinecap='round' strokeLinejoin='round' d='M19.5 7.5a7 7 0 010 9' />}
                </>
            )}
        </svg>
    );
};
const MIN_DARK_ACCENT_POPULATION = 0.035;

type RgbColor = [number, number, number];

const clampChannel = (value: number) => Math.max(0, Math.min(255, Math.round(value)));
const toRgb = (color: RgbColor) => `rgb(${clampChannel(color[0])}, ${clampChannel(color[1])}, ${clampChannel(color[2])})`;

const getLuminance = (color: RgbColor) => {
    const [r, g, b] = color.map((channel) => {
        const value = channel / 255;
        return value <= 0.03928 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);
    });

    return r * 0.2126 + g * 0.7152 + b * 0.0722;
};

const getSaturation = (color: RgbColor) => {
    const r = color[0] / 255;
    const g = color[1] / 255;
    const b = color[2] / 255;
    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);
    if (max === min) return 0;
    const lightness = (max + min) / 2;
    return (max - min) / (1 - Math.abs(2 * lightness - 1));
};

const mixRgb = (color: RgbColor, target: RgbColor, amount: number): RgbColor => [
    color[0] + (target[0] - color[0]) * amount,
    color[1] + (target[1] - color[1]) * amount,
    color[2] + (target[2] - color[2]) * amount,
];

const scaleRgb = (color: RgbColor, amount: number): RgbColor => [
    clampChannel(color[0] * amount),
    clampChannel(color[1] * amount),
    clampChannel(color[2] * amount),
];

const clampAccentVisibility = (color: RgbColor): RgbColor => {
    let next = color;
    for (let i = 0; i < 8 && getLuminance(next) < 0.13; i++) {
        next = mixRgb(next, [255, 255, 255], 0.08);
    }
    for (let i = 0; i < 8 && getLuminance(next) > 0.42; i++) {
        next = mixRgb(next, [0, 0, 0], 0.08);
    }
    return next;
};

const pickArtworkWaveformPalette = (imageData: ImageData): WaveformPalette | null => {
    const buckets = new Map<string, { count: number; sum: RgbColor }>();
    let visiblePixels = 0;

    for (let i = 0; i < imageData.data.length; i += 4) {
        const alpha = imageData.data[i + 3];
        if (alpha < 180) continue;

        visiblePixels++;
        const color: RgbColor = [imageData.data[i], imageData.data[i + 1], imageData.data[i + 2]];
        const luminance = getLuminance(color);
        const saturation = getSaturation(color);

        if (luminance < 0.08 || luminance > 0.48 || saturation < 0.22) continue;

        const keyColor = color.map((channel) => Math.round(channel / 24) * 24) as RgbColor;
        const key = keyColor.join(',');
        const bucket = buckets.get(key) || { count: 0, sum: [0, 0, 0] as RgbColor };
        bucket.count++;
        bucket.sum = [
            bucket.sum[0] + color[0],
            bucket.sum[1] + color[1],
            bucket.sum[2] + color[2],
        ];
        buckets.set(key, bucket);
    }

    if (!visiblePixels) return null;

    let selectedColor: RgbColor | null = null;
    let selectedCount = 0;
    let selectedLuminance = 1;
    buckets.forEach((bucket) => {
        const population = bucket.count / visiblePixels;
        if (population < MIN_DARK_ACCENT_POPULATION) return;

        const color: RgbColor = [
            bucket.sum[0] / bucket.count,
            bucket.sum[1] / bucket.count,
            bucket.sum[2] / bucket.count,
        ];
        const luminance = getLuminance(color);
        if (!selectedColor || luminance < selectedLuminance || (Math.abs(luminance - selectedLuminance) < 0.035 && bucket.count > selectedCount)) {
            selectedColor = color;
            selectedCount = bucket.count;
            selectedLuminance = luminance;
        }
    });

    if (!selectedColor) return null;

    const base = clampAccentVisibility(selectedColor);
    return {
        dark: toRgb(scaleRgb(base, 0.58)),
        default: toRgb(base),
        light: toRgb(mixRgb(base, [255, 255, 255], 0.34)),
    };
};

const AudioPlayer: React.FC<AudioPlayerProps> = ({
    src,
    blobUrl,
    blob,
    blobPartial,
    isStreaming,
    resumeState,
    filePath,
    uuid,
    fileName,
    onRequestFullDownload,
    onError,
    onTimeUpdate,
    onPlayingChange,
    readOnly = false,
}) => {
    const audioRef = useRef<HTMLAudioElement>(null);
    const canvasRef = useRef<HTMLCanvasElement>(null);
    const timelineCanvasRef = useRef<HTMLCanvasElement>(null);
    const iconRef = useRef<HTMLElement | null>(null);
    const audioStageRef = useRef<HTMLDivElement>(null);
    const audioContentRef = useRef<HTMLDivElement>(null);
    const lyricBackdropRef = useRef<HTMLDivElement>(null);
    const playerIdRef = useRef<string>(`audio-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
    const waveformStateRef = useRef<WaveformState>(createWaveformState());
    const notesLayerRef = useRef<HTMLDivElement>(null);
    const onPlayingChangeRef = useRef<typeof onPlayingChange>();
    const animationRef = useRef<number>();
    const analyserRef = useRef<AnalyserNode>();
    const audioContextRef = useRef<AudioContext | null>(null);
    const sourceRef = useRef<MediaElementAudioSourceNode>();
    const pendingResumeRef = useRef<{ time: number; wasPlaying: boolean } | null>(null);
    const lastSrcRef = useRef<string | null>(null);
    const editCoverPreviewRef = useRef<string | null>(null);
    const coverInputRef = useRef<HTMLInputElement>(null);
    const artworkUrlRef = useRef<string | null>(null);
    const isScrubbingRef = useRef(false);
    const scrubRafRef = useRef<number | null>(null);
    const lastTimelineDrawRef = useRef(0);
    const lastBeatEffectRef = useRef(0);

    const [playing, setPlaying] = useState(false);
    const [currentTime, setCurrentTime] = useState(0);
    const [duration, setDuration] = useState(0);
    const [buffered, setBuffered] = useState(0);
    const [volume, setVolume] = useState(() => readStoredVolume());
    const [muted, setMuted] = useState(false);
    const [playbackRate, setPlaybackRate] = useState(1);
    const [syncCount, setSyncCount] = useState(0);
    const [metadata, setMetadata] = useState<ParsedAudioMetadata | null>(null);
    const [artworkUrl, setArtworkUrl] = useState<string | null>(null);
    const [showEditModal, setShowEditModal] = useState(false);
    const [showLyricsModal, setShowLyricsModal] = useState(false);
    const [editTitle, setEditTitle] = useState('');
    const [editArtist, setEditArtist] = useState('');
    const [editAlbum, setEditAlbum] = useState('');
    const [lyricsDraft, setLyricsDraft] = useState('');
    const [editCoverFile, setEditCoverFile] = useState<File | null>(null);
    const [editCoverPreview, setEditCoverPreview] = useState<string | null>(null);
    const [coverMode, setCoverMode] = useState<'none' | 'existing' | 'new'>('none');
    const [editError, setEditError] = useState<string | null>(null);
    const [lyricsError, setLyricsError] = useState<string | null>(null);
    const [editSaving, setEditSaving] = useState(false);
    const [lyricsSaving, setLyricsSaving] = useState(false);
    const [editProgress, setEditProgress] = useState(0);
    const [lyricsProgress, setLyricsProgress] = useState(0);
    const [lyricsRenderEnabled, setLyricsRenderEnabled] = useState(readLyricsRenderEnabled);
    const [seekPreview, setSeekPreview] = useState<number | null>(null);
    const [artworkColors, setArtworkColors] = useState<{ tl: string; tr: string; bl: string; br: string } | null>(null);
    const [waveformPalette, setWaveformPalette] = useState<WaveformPalette | null>(null);
    const [ambientRotation, setAmbientRotation] = useState(0);

    const lastVolumeRef = useRef(readStoredVolume());
    const lastMutedRef = useRef(false);
    const lastRateRef = useRef(1);
    const rotationRef = useRef(0);

    const isMp3 = (fileName || '').toLowerCase().endsWith('.mp3');
    const canEditMetadata = !readOnly && !!blob && !blobPartial && isMp3 && blob.size <= MAX_METADATA_EDIT_SIZE;
    const needsFullDownload = !canEditMetadata && (isStreaming || blobPartial || !blob);

    useEffect(() => {
        if (typeof window === 'undefined') return;
        try {
            window.localStorage.setItem(LYRICS_RENDER_STORAGE_KEY, lyricsRenderEnabled ? '1' : '0');
        } catch {
            // Ignore storage failures; the toggle still works for this render.
        }
    }, [lyricsRenderEnabled]);

    useEffect(() => {
        const playerId = playerIdRef.current;
        globalAudioRegistry.set(playerId, {
            play: () => audioRef.current?.play(),
            pause: () => audioRef.current?.pause(),
            seek: (time: number) => { if (audioRef.current) audioRef.current.currentTime = time; },
            skip: (delta: number) => { if (audioRef.current) audioRef.current.currentTime += delta; },
            isPlaying: () => playing,
            getCurrentTime: () => audioRef.current?.currentTime || 0,
        });
        setSyncCount(globalAudioRegistry.size);

        const interval = setInterval(() => setSyncCount(globalAudioRegistry.size), 500);

        return () => {
            globalAudioRegistry.delete(playerId);
            clearInterval(interval);
        };
    }, [playing]);

    useEffect(() => {
        onPlayingChangeRef.current = onPlayingChange;
    }, [onPlayingChange]);

    useEffect(() => {
        let active = true;
        const controller = new AbortController();
        
        const resetArtwork = () => {
            if (artworkUrlRef.current) {
                URL.revokeObjectURL(artworkUrlRef.current);
                artworkUrlRef.current = null;
            }
        };
        
        const setParsedArtwork = (parsed: ParsedAudioMetadata | null) => {
            resetArtwork();
            const url = createArtworkUrl(parsed?.picture);
            if (url) {
                artworkUrlRef.current = url;
                setArtworkUrl(url);
            } else {
                setArtworkUrl(null);
            }
        };

        const loadMetadata = async () => {
            if (blob && !blobPartial) {
                const parsed = await loadMetadataFromBlob(blob);
                if (!active) return;
                setMetadata(parsed);
                setParsedArtwork(parsed);
                return;
            }
            if (isStreaming && src) {
                // Stream URLs are one-time Wings tokens. Do not fetch them for ID3 metadata,
                // otherwise the metadata request consumes the token before the audio element.
                setMetadata(null);
                resetArtwork();
                setArtworkUrl(null);
                return;
            }
            setMetadata(null);
            resetArtwork();
            setArtworkUrl(null);
        };

        loadMetadata();
        return () => {
            active = false;
            controller.abort();
            resetArtwork();
        };
    }, [blob, blobPartial, fileName, isStreaming, src]);

    useEffect(() => {
        if (!artworkUrl) {
            setArtworkColors(null);
            setWaveformPalette(null);
            return;
        }

        let active = true;
        const img = new Image();
        img.crossOrigin = 'anonymous';
        img.src = artworkUrl;

        const canvas = document.createElement('canvas');
        canvas.width = ARTWORK_SAMPLE_SIZE;
        canvas.height = ARTWORK_SAMPLE_SIZE;
        const ctx = canvas.getContext('2d', { willReadFrequently: true });
        if (!ctx) {
            setArtworkColors(null);
            setWaveformPalette(null);
            return;
        }

        const sample = () => {
            if (!active) return;
            try {
                ctx.drawImage(img, 0, 0, ARTWORK_SAMPLE_SIZE, ARTWORK_SAMPLE_SIZE);
                const getRgb = (x: number, y: number) => {
                    const data = ctx.getImageData(x, y, 1, 1).data;
                    return [data[0], data[1], data[2]];
                };
                const maxPixel = ARTWORK_SAMPLE_SIZE - 1;
                const corners = {
                    tl: getRgb(0, 0),
                    tr: getRgb(maxPixel, 0),
                    bl: getRgb(0, maxPixel),
                    br: getRgb(maxPixel, maxPixel),
                };
                const toRgb = (arr: number[]) => `rgb(${Math.round(arr[0])}, ${Math.round(arr[1])}, ${Math.round(arr[2])})`;
                setArtworkColors({
                    tl: toRgb(corners.tl),
                    tr: toRgb(corners.tr),
                    bl: toRgb(corners.bl),
                    br: toRgb(corners.br),
                });
                setWaveformPalette(pickArtworkWaveformPalette(ctx.getImageData(0, 0, ARTWORK_SAMPLE_SIZE, ARTWORK_SAMPLE_SIZE)));
            } catch {
                setArtworkColors(null);
                setWaveformPalette(null);
            }
        };

        if (img.complete) sample();
        else {
            img.onload = sample;
            img.onerror = () => {
                if (active) {
                    setArtworkColors(null);
                    setWaveformPalette(null);
                }
            };
        }

        return () => {
            active = false;
        };
    }, [artworkUrl]);

    const applySettings = (audio: HTMLAudioElement) => {
        applyMediaSettings(audio, lastVolumeRef.current, lastMutedRef.current, lastRateRef.current);
    };

    const handleBeat = useCallback((strength: number) => {
        const now = performance.now();
        if (now - lastBeatEffectRef.current < 180) return;
        lastBeatEffectRef.current = now;

        const layer = notesLayerRef.current;
        if (layer) {
            emitBeatNote(layer, strength, waveformPalette?.default || colors.primary.default);
        }
        if (artworkColors) {
            const step = 6 + Math.min(12, Math.max(0, strength * 10));
            rotationRef.current = (rotationRef.current + step) % 360;
            setAmbientRotation(rotationRef.current);
        }
    }, [artworkColors, waveformPalette]);

    const handleBeatZoomChange = useCallback((zoom: number) => {
        const beatAmount = Math.max(0, zoom - 1);
        if (audioStageRef.current) {
            audioStageRef.current.style.transform = beatAmount > 0.01 ? `scale(${1 + beatAmount * 0.08})` : 'scale(1)';
        }
    }, []);

    const runVisualizer = useCallback(() => {
        if (!canvasRef.current || !analyserRef.current) return;

        if (animationRef.current) {
            cancelAnimationFrame(animationRef.current);
            animationRef.current = undefined;
        }
        
        const draw = () => {
            if (!canvasRef.current || !analyserRef.current) {
                if (animationRef.current) {
                    cancelAnimationFrame(animationRef.current);
                    animationRef.current = undefined;
                }
                return;
            }
            
            animationRef.current = requestAnimationFrame(draw);
            
            drawVisualizer({
                canvas: canvasRef.current,
                analyser: analyserRef.current,
                state: waveformStateRef.current,
                intensity: audioRef.current?.muted ? 0 : audioRef.current?.volume ?? 1,
                palette: waveformPalette || undefined,
                onBeat: handleBeat,
                onBeatZoomChange: handleBeatZoomChange,
            });

            const now = performance.now();
            if (
                timelineCanvasRef.current &&
                timelineCanvasRef.current.clientWidth > 0 &&
                timelineCanvasRef.current.clientHeight > 0 &&
                analyserRef.current &&
                now - lastTimelineDrawRef.current > 120
            ) {
                lastTimelineDrawRef.current = now;
                const audio = audioRef.current;
                const rawDuration = audio?.duration;
                const timelineDuration = Number.isFinite(rawDuration) && (rawDuration as number) > 0
                    ? (rawDuration as number)
                    : duration;
                    
                drawTimeline({
                    canvas: timelineCanvasRef.current,
                    analyser: analyserRef.current,
                    currentTime: audio?.currentTime || 0,
                    duration: timelineDuration,
                    buffered: audio?.buffered || null,
                });
            }
        };
        draw();
    }, [duration, handleBeat, handleBeatZoomChange, waveformPalette]);

    useEffect(() => {
        const audio = audioRef.current;
        if (!audio) return;

        const AudioContext = window.AudioContext || (window as any).webkitAudioContext;
        if (AudioContext && !sourceRef.current) {
            const audioCtx = new AudioContext();
            const analyser = audioCtx.createAnalyser();
            analyser.fftSize = 512;
            analyser.smoothingTimeConstant = 0.45;
            analyser.minDecibels = -85;
            analyser.maxDecibels = -8;

            try {
                const source = audioCtx.createMediaElementSource(audio);
                source.connect(analyser);
                analyser.connect(audioCtx.destination);
                audioContextRef.current = audioCtx;
                sourceRef.current = source;
                analyserRef.current = analyser;
            } catch (e) {
                console.error("Web Audio API setup failed:", e);
            }
        }

        const updateTime = () => setCurrentTime(audio.currentTime);
        const updateDuration = () => {
            setDuration(audio.duration);
            applySettings(audio);
            if (pendingResumeRef.current) {
                const resume = pendingResumeRef.current;
                const target = Math.min(resume.time, Math.max(0, audio.duration - 0.1));
                if (!Number.isNaN(target)) {
                    audio.currentTime = target;
                }
                if (resume.wasPlaying) {
                    const promise = audio.play();
                    if (promise && typeof promise.catch === 'function') {
                        promise.catch(() => {});
                    }
                }
                pendingResumeRef.current = null;
            }
        };
        const updateBuffered = () => {
            if (audio.buffered.length > 0 && audio.duration > 0) {
                const end = audio.buffered.end(audio.buffered.length - 1);
                setBuffered(end);
            }
        };
        const onPlay = () => {
            setPlaying(true);
            if (audioContextRef.current?.state === 'suspended') {
                audioContextRef.current.resume().catch(() => {});
            }
            runVisualizer();
            onPlayingChangeRef.current?.(true);
        };
        const onPause = () => {
            setPlaying(false);
            if (animationRef.current) {
                cancelAnimationFrame(animationRef.current);
                animationRef.current = undefined;
            }
            if (audioStageRef.current) {
                audioStageRef.current.style.transform = 'scale(1)';
            }
            if (canvasRef.current) {
                canvasRef.current.style.transform = 'scale(1)';
            }
            onPlayingChangeRef.current?.(false);
        };
        const onVolumeChange = () => {
            setVolume(audio.volume);
            setMuted(audio.muted);
        };
        const onRateChange = () => {
            setPlaybackRate(audio.playbackRate);
        };

        audio.addEventListener('timeupdate', updateTime);
        audio.addEventListener('progress', updateBuffered);
        audio.addEventListener('loadedmetadata', updateDuration);
        audio.addEventListener('durationchange', updateDuration);
        audio.addEventListener('seeked', updateTime);
        audio.addEventListener('play', onPlay);
        audio.addEventListener('pause', onPause);
        audio.addEventListener('volumechange', onVolumeChange);
        audio.addEventListener('ratechange', onRateChange);

        if (!audio.paused) {
            setPlaying(true);
            runVisualizer();
        }

        return () => {
            audio.removeEventListener('timeupdate', updateTime);
            audio.removeEventListener('progress', updateBuffered);
            audio.removeEventListener('loadedmetadata', updateDuration);
            audio.removeEventListener('durationchange', updateDuration);
            audio.removeEventListener('seeked', updateTime);
            audio.removeEventListener('play', onPlay);
            audio.removeEventListener('pause', onPause);
            audio.removeEventListener('volumechange', onVolumeChange);
            audio.removeEventListener('ratechange', onRateChange);
            if (animationRef.current) {
                cancelAnimationFrame(animationRef.current);
                animationRef.current = undefined;
            }
        };
    }, [src, runVisualizer]);

    useEffect(() => {
        const audio = audioRef.current;
        if (!audio || !src) return;
        const previousSrc = lastSrcRef.current;
        if (previousSrc && previousSrc !== src && isStreaming && resumeState) {
            pendingResumeRef.current = { time: resumeState.time, wasPlaying: resumeState.wasPlaying };
        }
        if (previousSrc && previousSrc !== src) {
            waveformStateRef.current = createWaveformState();
            setBuffered(0);
            if (audioStageRef.current) audioStageRef.current.style.transform = 'scale(1)';
            if (canvasRef.current) canvasRef.current.style.transform = 'scale(1)';
            try {
                audio.load();
            } catch {}
        }
        applySettings(audio);
        lastSrcRef.current = src;
    }, [isStreaming, resumeState, src]);

    useEffect(() => {
        lastVolumeRef.current = volume;
        writeStoredVolume(volume);
    }, [volume]);

    useEffect(() => {
        lastMutedRef.current = muted;
    }, [muted]);

    useEffect(() => {
        lastRateRef.current = playbackRate;
    }, [playbackRate]);

    useEffect(() => {
        onTimeUpdate?.(currentTime);
    }, [currentTime, onTimeUpdate]);

    useEffect(() => {
        if (playing) {
            runVisualizer();
        }
    }, [playing, runVisualizer]);

    const togglePlay = useCallback(() => {
        if (audioRef.current) {
            if (playing) audioRef.current.pause();
            else audioRef.current.play();
        }
    }, [playing]);

    const getSafeDuration = () => {
        const audio = audioRef.current;
        if (!audio) return duration;
        const next = audio.duration;
        return Number.isFinite(next) && next > 0 ? next : duration;
    };

    const seekToClientX = (clientX: number, element: HTMLElement) => {
        const audio = audioRef.current;
        if (!audio) return;
        const rect = element.getBoundingClientRect();
        const percent = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
        const nextDuration = getSafeDuration();
        if (!nextDuration || !Number.isFinite(nextDuration)) return;
        const nextTime = percent * nextDuration;
        audio.currentTime = nextTime;
        setCurrentTime(nextTime);
        setSeekPreview(nextTime);
    };

    const handleSeek = (e: React.MouseEvent<HTMLDivElement>) => {
        seekToClientX(e.clientX, e.currentTarget);
    };

    const skipTime = useCallback((delta: number) => {
        const audio = audioRef.current;
        if (!audio) return;
        const nextDuration = getSafeDuration();
        const nextTime = Math.max(0, Math.min(nextDuration || audio.currentTime + delta, audio.currentTime + delta));
        audio.currentTime = nextTime;
        setCurrentTime(nextTime);
    }, [duration]);

    const handleScrubPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
        if (e.button !== 0) return;
        isScrubbingRef.current = true;
        (e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId);
        seekToClientX(e.clientX, e.currentTarget);
    };

    const handleScrubPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
        if (!isScrubbingRef.current) return;
        if (scrubRafRef.current) cancelAnimationFrame(scrubRafRef.current);
        const target = e.currentTarget;
        const x = e.clientX;
        scrubRafRef.current = requestAnimationFrame(() => {
            seekToClientX(x, target);
        });
    };

    const handleScrubPointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
        if (!isScrubbingRef.current) return;
        isScrubbingRef.current = false;
        (e.currentTarget as HTMLDivElement).releasePointerCapture(e.pointerId);
        if (scrubRafRef.current) {
            cancelAnimationFrame(scrubRafRef.current);
            scrubRafRef.current = null;
        }
        setSeekPreview(null);
    };

    const handleVolumeChange = useCallback((nextVolume: number) => {
        if (!audioRef.current) return;
        const clamped = Math.min(1, Math.max(0, nextVolume));
        audioRef.current.volume = clamped;
        audioRef.current.muted = clamped === 0;
    }, []);

    useEffect(() => {
        const handleKeyDown = (event: KeyboardEvent) => {
            const target = event.target as HTMLElement | null;
            const tagName = target?.tagName?.toLowerCase();
            if (target?.isContentEditable || tagName === 'input' || tagName === 'textarea' || tagName === 'select') return;
            if (showEditModal || showLyricsModal) return;

            switch (event.key.toLowerCase()) {
                case ' ':
                case 'k':
                    event.preventDefault();
                    togglePlay();
                    break;
                case 'arrowleft':
                case 'j':
                    event.preventDefault();
                    skipTime(event.shiftKey ? -10 : -5);
                    break;
                case 'arrowright':
                case 'l':
                    event.preventDefault();
                    skipTime(event.shiftKey ? 10 : 5);
                    break;
                case 'arrowup':
                    event.preventDefault();
                    handleVolumeChange((audioRef.current?.volume ?? volume) + 0.05);
                    break;
                case 'arrowdown':
                    event.preventDefault();
                    handleVolumeChange((audioRef.current?.volume ?? volume) - 0.05);
                    break;
                case 'm':
                    event.preventDefault();
                    if (audioRef.current) audioRef.current.muted = !audioRef.current.muted;
                    break;
            }
        };

        window.addEventListener('keydown', handleKeyDown);
        return () => window.removeEventListener('keydown', handleKeyDown);
    }, [handleVolumeChange, showEditModal, showLyricsModal, skipTime, togglePlay, volume]);

    const handleRateChange = (nextRate: number) => {
        if (!audioRef.current) return;
        audioRef.current.playbackRate = nextRate;
        (audioRef.current as any).preservesPitch = false;
        (audioRef.current as any).mozPreservesPitch = false;
        (audioRef.current as any).webkitPreservesPitch = false;
    };

    const resetEditPreview = () => {
        if (editCoverPreviewRef.current) {
            URL.revokeObjectURL(editCoverPreviewRef.current);
            editCoverPreviewRef.current = null;
        }
    };

    const openEditModal = () => {
        if (readOnly) return;
        resetEditPreview();
        setEditTitle(metadata?.title || '');
        setEditArtist(metadata?.artist || '');
        setEditAlbum(metadata?.album || '');
        setEditCoverFile(null);
        setCoverMode(metadata?.picture ? 'existing' : 'none');
        setEditCoverPreview(metadata?.picture ? artworkUrl : null);
        setEditError(null);
        setEditProgress(0);
        setShowEditModal(true);
        if (!canEditMetadata && (isStreaming || blobPartial || !blob)) {
            onRequestFullDownload?.();
        }
    };

    const openLyricsModal = () => {
        if (readOnly) return;
        setLyricsDraft(metadata?.lyrics || '');
        setLyricsError(isMp3 ? null : 'Lyrics can only be embedded into MP3 files.');
        setLyricsProgress(0);
        setShowLyricsModal(true);
        if (!canEditMetadata && (isStreaming || blobPartial || !blob)) {
            onRequestFullDownload?.();
        }
    };

    useEffect(() => {
        const maybeOpenFromEvent = (path?: string) => {
            if (!path || path !== filePath) return;
            openEditModal();
            if (typeof window !== 'undefined') {
                (window as any).__bfmEditAudioMetadataPath = null;
            }
        };

        const pending = typeof window !== 'undefined' ? (window as any).__bfmEditAudioMetadataPath : null;
        if (pending) {
            maybeOpenFromEvent(pending);
        }

        const handleEvent = (event: Event) => {
            const detail = (event as CustomEvent).detail as { path?: string } | undefined;
            maybeOpenFromEvent(detail?.path);
        };

        window.addEventListener('bfm:edit-audio-metadata', handleEvent as EventListener);
        return () => {
            window.removeEventListener('bfm:edit-audio-metadata', handleEvent as EventListener);
        };
    }, [filePath, openEditModal]);

    const closeEditModal = () => {
        resetEditPreview();
        setEditCoverFile(null);
        setEditCoverPreview(null);
        setEditError(null);
        setShowEditModal(false);
    };

    const closeLyricsModal = () => {
        setLyricsError(null);
        setShowLyricsModal(false);
    };

    const handleCoverSelect = (file?: File) => {
        if (!file) return;
        resetEditPreview();
        const url = URL.createObjectURL(file);
        editCoverPreviewRef.current = url;
        setEditCoverFile(file);
        setEditCoverPreview(url);
        setCoverMode('new');
    };

    const handleRemoveCover = () => {
        resetEditPreview();
        setEditCoverFile(null);
        setEditCoverPreview(null);
        setCoverMode('none');
    };

    const writeMetadata = async (
        nextMetadata: ParsedAudioMetadata,
        onProgress: (progress: number) => void,
    ) => {
        if (readOnly) {
            throw new Error('This live session is view only.');
        }
        if (!blob || !filePath || !uuid) {
            throw new Error('Full audio file is not available yet.');
        }
        if (!canEditMetadata) {
            throw new Error('Metadata edits are only available for MP3 files under 25 MB.');
        }

        const audioBuffer = await blob.arrayBuffer();
        const audioBytes = stripId3Tag(new Uint8Array(audioBuffer));
        const frames: Uint8Array[] = [];
        const titleFrame = buildTextFrame('TIT2', nextMetadata.title || '');
        const artistFrame = buildTextFrame('TPE1', nextMetadata.artist || '');
        const albumFrame = buildTextFrame('TALB', nextMetadata.album || '');
        const lyricsFrame = buildLyricsFrame(nextMetadata.lyrics || '');

        if (titleFrame) frames.push(titleFrame);
        if (artistFrame) frames.push(artistFrame);
        if (albumFrame) frames.push(albumFrame);
        if (lyricsFrame) frames.push(lyricsFrame);
        if (nextMetadata.picture?.data?.length) {
            frames.push(buildApicFrame(nextMetadata.picture.mime, nextMetadata.picture.data));
        }

        let outputBytes = audioBytes;
        if (frames.length > 0) {
            const tagBytes = buildId3Tag(frames);
            const total = tagBytes.length + audioBytes.length;
            outputBytes = new Uint8Array(total);
            outputBytes.set(tagBytes, 0);
            outputBytes.set(audioBytes, tagBytes.length);
        }

        if (outputBytes.length > MAX_METADATA_EDIT_SIZE) {
            throw new Error('Edited file exceeds the 25 MB limit.');
        }

        const outputBlob = new Blob([toArrayBuffer(outputBytes)], { type: blob.type || 'audio/mpeg' });
        const getFileUploadUrl = (await import('@/api/server/files/getFileUploadUrl')).default;
        const axios = (await import('axios')).default;
        const uploadUrl = await getFileUploadUrl(uuid);
        const directory = filePath.substring(0, filePath.lastIndexOf('/')) || '/';
        const targetName = filePath.split('/').pop() || fileName || 'audio.mp3';
        const formData = new FormData();
        formData.append('files', new File([outputBlob], targetName, { type: outputBlob.type || 'audio/mpeg' }));

        await axios.post(uploadUrl, formData, {
            headers: { 'Content-Type': 'multipart/form-data' },
            params: { directory },
            onUploadProgress: (event: any) => {
                if (event?.total) {
                    onProgress((event.loaded / event.total) * 100);
                }
            },
        });

        setMetadata(nextMetadata);
        if (artworkUrlRef.current) {
            URL.revokeObjectURL(artworkUrlRef.current);
            artworkUrlRef.current = null;
        }
        const newArtworkUrl = createArtworkUrl(nextMetadata.picture);
        if (newArtworkUrl) {
            artworkUrlRef.current = newArtworkUrl;
            setArtworkUrl(newArtworkUrl);
        } else {
            setArtworkUrl(null);
        }
    };

    const handleSaveMetadata = async () => {
        if (readOnly) return;
        setEditSaving(true);
        setEditError(null);
        setEditProgress(0);
        try {
            let picture: ParsedAudioMetadata['picture'] | undefined;
            if (coverMode === 'new' && editCoverFile) {
                const coverBuffer = await editCoverFile.arrayBuffer();
                picture = {
                    mime: editCoverFile.type || 'image/jpeg',
                    data: new Uint8Array(coverBuffer),
                };
            } else if (coverMode === 'existing' && metadata?.picture?.data) {
                picture = metadata.picture;
            }

            await writeMetadata({
                title: editTitle.trim() || undefined,
                artist: editArtist.trim() || undefined,
                album: editAlbum.trim() || undefined,
                lyrics: metadata?.lyrics,
                picture,
            }, setEditProgress);

            closeEditModal();
        } catch (error: any) {
            setEditError(error?.message || 'Failed to update metadata.');
        } finally {
            setEditSaving(false);
        }
    };

    const handleSaveLyrics = async () => {
        if (readOnly) return;
        const normalizedLyrics = normalizeLyricsText(lyricsDraft);
        const validationError = getLyricsValidationError(normalizedLyrics);
        if (validationError) {
            setLyricsError(validationError);
            return;
        }

        setLyricsSaving(true);
        setLyricsError(null);
        setLyricsProgress(0);
        try {
            await writeMetadata({
                title: metadata?.title,
                artist: metadata?.artist,
                album: metadata?.album,
                lyrics: normalizedLyrics || undefined,
                picture: metadata?.picture,
            }, setLyricsProgress);

            closeLyricsModal();
        } catch (error: any) {
            setLyricsError(error?.message || 'Failed to attach lyrics.');
        } finally {
            setLyricsSaving(false);
        }
    };

    const safeAudioDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
    const progressPercent = safeAudioDuration ? Math.min(100, Math.max(0, (currentTime / safeAudioDuration) * 100)) : 0;
    const bufferedPercent = safeAudioDuration ? Math.min(100, Math.max(0, (buffered / safeAudioDuration) * 100)) : 0;
    const displayTitle = metadata?.title || fileName?.replace(/\.[^/.]+$/, '') || 'Audio file';
    const timedLyrics = useMemo(() => parseTimedLyrics(metadata?.lyrics), [metadata?.lyrics]);
    const currentLyricIndex = useMemo(() => {
        if (!timedLyrics.length) return -1;

        let index = -1;
        for (let i = 0; i < timedLyrics.length; i += 1) {
            if (timedLyrics[i].time <= currentTime + 0.15) index = i;
            else break;
        }

        return index;
    }, [currentTime, timedLyrics]);
    const currentLyric = currentLyricIndex >= 0 ? timedLyrics[currentLyricIndex] : null;
    const displayLyric = currentLyric || timedLyrics[0] || null;
    const previousLyric = currentLyricIndex > 0 ? timedLyrics[currentLyricIndex - 1] : null;
    const nextLyricIndex = currentLyricIndex >= 0 ? currentLyricIndex + 1 : 1;
    const nextLyric = nextLyricIndex < timedLyrics.length ? timedLyrics[nextLyricIndex] : null;
    const shouldRenderLyrics = lyricsRenderEnabled && timedLyrics.length > 0;

    useEffect(() => {
        if (!currentLyric?.effect || currentLyricIndex < 0) return;

        const target = audioContentRef.current;
        if (!target || typeof target.animate !== 'function') return;

        target.getAnimations().forEach((animation) => animation.cancel());

        target.animate(getLyricEffectFrames(currentLyric.effect), {
            duration: currentLyric.effect === 'flash' ? 420 : 560,
            easing: 'cubic-bezier(0.2, 0, 0, 1)',
        });
    }, [currentLyric?.effect, currentLyricIndex]);

    useEffect(() => {
        if (!shouldRenderLyrics || !displayLyric) return;

        const target = lyricBackdropRef.current;
        if (!target || typeof target.animate !== 'function') return;

        target.animate([
            { opacity: 0, transform: 'translate3d(0, 34px, 0) scale(0.98)', filter: 'blur(10px)' },
            { opacity: 0.72, transform: 'translate3d(0, 9px, 0) scale(0.995)', filter: 'blur(2px)', offset: 0.64 },
            { opacity: 1, transform: 'translate3d(0, 0, 0) scale(1)', filter: 'blur(0)' },
        ], {
            duration: 920,
            easing: 'cubic-bezier(0.22, 1, 0.36, 1)',
        });
    }, [currentLyricIndex, displayLyric, shouldRenderLyrics]);

    return (
        <AudioPlayerContainer>
            {artworkColors && (
                <div
                    style={{
                        position: 'absolute',
                        left: '50%',
                        top: '50%',
                        width: '160vmax',
                        height: '160vmax',
                        marginLeft: '-80vmax',
                        marginTop: '-80vmax',
                        zIndex: 0,
                        pointerEvents: 'none',
                        transform: `rotate(${ambientRotation}deg)`,
                        transformOrigin: 'center',
                        transition: 'transform 260ms ease',
                        opacity: 0.85,
                        background: `
                            radial-gradient(circle at 0% 0%, ${artworkColors.tl} 0%, transparent 70%),
                            radial-gradient(circle at 100% 0%, ${artworkColors.tr} 0%, transparent 70%),
                            radial-gradient(circle at 0% 100%, ${artworkColors.bl} 0%, transparent 70%),
                            radial-gradient(circle at 100% 100%, ${artworkColors.br} 0%, transparent 70%)
                        `,
                    }}
                />
            )}
            <VisualizerCanvas ref={canvasRef} width={800} height={400} />
            <audio ref={audioRef} src={src} onError={onError} preload="auto" crossOrigin="anonymous" />

            <AudioContent ref={audioContentRef}>
                <BeatNotesLayer ref={notesLayerRef} />
                {shouldRenderLyrics && (
                    <div className='pointer-events-none absolute inset-x-4 top-8 bottom-36 z-[1] flex items-center justify-center overflow-hidden md:bottom-40'>
                        <div
                            ref={lyricBackdropRef}
                            key={currentLyricIndex}
                            className='flex max-w-5xl transform-gpu flex-col items-center text-center will-change-transform'
                        >
                            {previousLyric && (
                                <p className='mb-3 max-w-full truncate text-lg font-semibold text-neutral-100/20 md:text-2xl'>
                                    {previousLyric.text}
                                </p>
                            )}
                            <p className='max-w-full break-words text-4xl font-bold leading-tight text-neutral-100/80 drop-shadow-2xl md:text-6xl'>
                                {displayLyric?.text}
                            </p>
                            {nextLyric && (
                                <p className='mt-4 max-w-full truncate text-base font-medium text-neutral-100/30 md:text-xl'>
                                    {nextLyric.text}
                                </p>
                            )}
                        </div>
                    </div>
                )}

                <div ref={audioStageRef} className='relative z-10 flex flex-1 min-h-0 w-full origin-center transform-gpu items-center justify-center px-2 py-6 transition-transform duration-75 will-change-transform md:py-10'>
                    <div className='flex min-w-0 flex-col items-center gap-5 text-center md:flex-row md:text-left'>
                        <ArtworkStack>
                            <VinylDisk playing={playing} />
                            {artworkUrl ? (
                                <AudioArtwork
                                    ref={iconRef as React.RefObject<HTMLImageElement>}
                                    src={artworkUrl}
                                    alt={metadata?.album || metadata?.title || fileName}
                                />
                            ) : (
                                <AudioIconWrapper ref={iconRef as React.RefObject<HTMLDivElement>} playing={playing}>
                                    <svg className='w-14 h-14 text-white' fill='none' stroke='currentColor' viewBox='0 0 24 24'>
                                        <path strokeLinecap='round' strokeLinejoin='round' strokeWidth={1.6} d='M9 18V7l10-2v11M9 18c0 1.105-1.12 2-2.5 2S4 19.105 4 18s1.12-2 2.5-2 2.5.895 2.5 2zm10-2c0 1.105-1.12 2-2.5 2S14 17.105 14 16s1.12-2 2.5-2 2.5.895 2.5 2zM9 11l10-2' />
                                    </svg>
                                </AudioIconWrapper>
                            )}
                        </ArtworkStack>

                        <div className='min-w-0 max-w-md'>
                            <p className='truncate text-xl font-semibold text-neutral-100'>{displayTitle}</p>
                            {metadata?.artist && <p className='mt-1 truncate text-sm text-neutral-400'>{metadata.artist}</p>}
                            {metadata?.album && <p className='mt-1 truncate text-xs text-neutral-500'>{metadata.album}</p>}
                            {shouldRenderLyrics && currentLyric?.effect && (
                                <span className='mt-3 inline-flex rounded border border-neutral-600 bg-neutral-800/70 px-2 py-1 text-[10px] font-semibold uppercase text-neutral-400'>
                                    {currentLyric.effect}
                                </span>
                            )}
                        </div>
                    </div>
                </div>

                <WaveformCanvas ref={timelineCanvasRef} style={{ display: 'none' }} />
                <div
                    className='relative z-20 w-full rounded border p-3 shadow-2xl md:p-4'
                    style={{
                        backgroundColor: themeColors.page.secondary,
                        borderColor: themeColors.page.secondaryHover,
                        color: themeColors.page.primary,
                    }}
                >
                    <div
                        className='relative h-2 w-full cursor-pointer rounded-full'
                        style={{ backgroundColor: themeColors.page.secondaryActive }}
                        onClick={handleSeek}
                        onPointerDown={handleScrubPointerDown}
                        onPointerMove={handleScrubPointerMove}
                        onPointerUp={handleScrubPointerUp}
                        onPointerCancel={handleScrubPointerUp}
                        onMouseMove={(e) => {
                            if (!safeAudioDuration) return;
                            const rect = e.currentTarget.getBoundingClientRect();
                            const percent = Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width));
                            setSeekPreview(percent * safeAudioDuration);
                        }}
                        onMouseLeave={() => {
                            if (!isScrubbingRef.current) setSeekPreview(null);
                        }}
                    >
                        <div
                            className='absolute inset-y-0 left-0 rounded-full'
                            style={{ width: `${bufferedPercent}%`, backgroundColor: themeColors.page.secondarySelected }}
                        />
                        <div
                            className='absolute inset-y-0 left-0 rounded-full'
                            style={{
                                width: `${progressPercent}%`,
                                backgroundColor: colors.primary.default,
                            }}
                        />
                        <div
                            className='absolute top-1/2 -translate-y-1/2 w-3.5 h-3.5 rounded-full shadow-md'
                            style={{
                                left: `${progressPercent}%`,
                                marginLeft: -7,
                                backgroundColor: colors.primary.default,
                            }}
                        />
                        {seekPreview !== null && (
                            <span
                                className='absolute -top-7 rounded px-2 py-1 text-[10px] shadow'
                                style={{
                                    left: `${safeAudioDuration ? (seekPreview / safeAudioDuration) * 100 : 0}%`,
                                    transform: 'translateX(-50%)',
                                    backgroundColor: themeColors.page.background,
                                    color: themeColors.page.primary,
                                }}
                            >
                                {formatTime(seekPreview)}
                            </span>
                        )}
                    </div>
                    <div className='mt-2 flex items-center justify-between text-xs' style={{ color: themeColors.page.primaryHover }}>
                        <span className='tabular-nums'>{formatTime(currentTime)}</span>
                        <span className='tabular-nums'>{formatTime(duration)}</span>
                    </div>

                    <div className='mt-3 flex flex-wrap items-center justify-center gap-3 md:gap-5'>
                        <div className='flex items-center justify-center gap-5'>
                            <Button.Text
                                type='button'
                                onClick={() => skipTime(-10)}
                                title='Back 10 seconds'
                                size={Button.Sizes.Small}
                                shape={Button.Shapes.IconSquare}
                            >
                                <svg className='w-5 h-5' fill='none' stroke='currentColor' strokeWidth={2} viewBox='0 0 24 24'>
                                    <path strokeLinecap='round' strokeLinejoin='round' d='M11 19V5l-7 7 7 7zm9 0V5l-7 7 7 7z' />
                                </svg>
                            </Button.Text>

                            <Button
                                type='button'
                                onClick={togglePlay}
                                title={playing ? 'Pause' : 'Play'}
                                variant={Button.Variants.Primary}
                                size={Button.Sizes.Large}
                            >
                                {playing ? (
                                    <svg className='w-7 h-7' fill='none' stroke='currentColor' strokeWidth={2.5} viewBox='0 0 24 24'>
                                        <path strokeLinecap='round' strokeLinejoin='round' d='M7 5v14M17 5v14' />
                                    </svg>
                                ) : (
                                    <svg className='w-7 h-7 ml-0.5' fill='none' stroke='currentColor' strokeWidth={2.5} viewBox='0 0 24 24'>
                                        <path strokeLinecap='round' strokeLinejoin='round' d='M8 5l11 7-11 7V5z' />
                                    </svg>
                                )}
                            </Button>

                            <Button.Text
                                type='button'
                                onClick={() => skipTime(10)}
                                title='Forward 10 seconds'
                                size={Button.Sizes.Small}
                                shape={Button.Shapes.IconSquare}
                            >
                                <svg className='w-5 h-5' fill='none' stroke='currentColor' strokeWidth={2} viewBox='0 0 24 24'>
                                    <path strokeLinecap='round' strokeLinejoin='round' d='M13 19V5l7 7-7 7zM4 19V5l7 7-7 7z' />
                                </svg>
                            </Button.Text>
                        </div>

                        <div className='hidden h-8 w-px md:block' style={{ backgroundColor: themeColors.page.secondaryHover }} />

                        <div className='flex flex-wrap items-center justify-center gap-3'>
                            <div className='flex items-center gap-2'>
                                <span className='hidden text-xs sm:inline' style={{ color: themeColors.page.primaryHover }}>Volume</span>
                                <Button.Text
                                    type='button'
                                    onClick={() => { if (audioRef.current) audioRef.current.muted = !muted; }}
                                    title={muted || volume === 0 ? 'Unmute' : 'Mute'}
                                    size={Button.Sizes.Small}
                                    shape={Button.Shapes.IconSquare}
                                >
                                    <VolumeIcon muted={muted} volume={volume} />
                                </Button.Text>
                                <VolumeSlider
                                    type='range'
                                    min='0'
                                    max='1'
                                    step='0.05'
                                    value={muted ? 0 : volume}
                                    onChange={(e) => handleVolumeChange(parseFloat(e.target.value))}
                                />
                            </div>

                            <div className='flex items-center gap-2'>
                                <span className='text-xs' style={{ color: themeColors.page.primaryHover }}>Speed</span>
                                <span className='w-9 text-xs tabular-nums' style={{ color: themeColors.page.primaryHover }}>{Math.round(playbackRate * 100)}%</span>
                                <RateSlider
                                    type='range'
                                    min='0.5'
                                    max='1.5'
                                    step='0.01'
                                    value={playbackRate}
                                    onChange={(e) => handleRateChange(parseFloat(e.target.value))}
                                />
                            </div>

                            <Button.Text
                                type='button'
                                size={Button.Sizes.Small}
                                onClick={openLyricsModal}
                                disabled={readOnly}
                                title={metadata?.lyrics ? 'Edit synced lyrics' : 'Add synced lyrics'}
                            >
                                {metadata?.lyrics ? 'Edit Lyrics' : 'Add Lyrics'}
                            </Button.Text>
                            {timedLyrics.length > 0 && (
                                <Button.Text
                                    type='button'
                                    size={Button.Sizes.Small}
                                    onClick={() => setLyricsRenderEnabled((enabled) => !enabled)}
                                    title={lyricsRenderEnabled ? 'Stop rendering lyrics' : 'Show synced lyrics'}
                                >
                                    {lyricsRenderEnabled ? 'Hide Lyrics' : 'Show Lyrics'}
                                </Button.Text>
                            )}
                        </div>
                    </div>

                    {syncCount > 1 && (
                        <div className='mt-3 flex items-center justify-center gap-2 border-t border-neutral-600 pt-3'>
                            <Button.Text
                                type='button'
                                onClick={() => syncSkipAll(-10)}
                                size={Button.Sizes.Small}
                            >
                                -10s
                            </Button.Text>
                            <Button.Text
                                type='button'
                                onClick={() => syncSeekAll(0)}
                                size={Button.Sizes.Small}
                                shape={Button.Shapes.IconSquare}
                            >
                                <svg className='w-4 h-4' fill='currentColor' viewBox='0 0 24 24'><path d='M6 6h2v12H6zm3.5 6l8.5 6V6z' /></svg>
                            </Button.Text>
                            <Button.Text type='button' onClick={() => syncAllAudio('toggle')} size={Button.Sizes.Small}>
                                Sync ({syncCount})
                            </Button.Text>
                            <Button.Text type='button' onClick={() => syncSkipAll(10)} size={Button.Sizes.Small}>
                                +10s
                            </Button.Text>
                        </div>
                    )}
                </div>

                <Dialog open={showEditModal} onClose={closeEditModal} title="Edit Audio Metadata">
                    <div className="space-y-4">
                        <p className="text-xs text-neutral-300">
                            Metadata is updated locally and the full file is re-uploaded. Limit: 25 MB.
                        </p>
                        {needsFullDownload && (
                            <p className="text-xs text-yellow-300">
                                Downloading full file to enable metadata edits...
                            </p>
                        )}
                        {editError && (
                            <div
                                className="text-sm text-red-400 bg-gray-700 border border-red-500 p-3"
                                style={{ borderRadius: themeColors.borderRadius.component }}
                            >
                                {editError}
                            </div>
                        )}
                        <div className="grid gap-3">
                            <div>
                                <label className="text-xs text-gray-400">Title</label>
                                <Input
                                    type="text"
                                    value={editTitle}
                                    onChange={(e) => setEditTitle(e.target.value)}
                                    className="mt-2"
                                    placeholder="Song title"
                                />
                            </div>
                            <div>
                                <label className="text-xs text-gray-400">Artist</label>
                                <Input
                                    type="text"
                                    value={editArtist}
                                    onChange={(e) => setEditArtist(e.target.value)}
                                    className="mt-2"
                                    placeholder="Artist name"
                                />
                            </div>
                            <div>
                                <label className="text-xs text-gray-400">Album</label>
                                <Input
                                    type="text"
                                    value={editAlbum}
                                    onChange={(e) => setEditAlbum(e.target.value)}
                                    className="mt-2"
                                    placeholder="Album name"
                                />
                            </div>
                            <div className="flex flex-col gap-3">
                                <label className="text-xs text-gray-400">Cover art</label>
                                <div className="flex flex-wrap items-center gap-3">
                                    <div
                                        className="w-20 h-20 overflow-hidden bg-gray-700 border border-gray-600 flex items-center justify-center"
                                        style={{ borderRadius: themeColors.borderRadius.component }}
                                    >
                                        {editCoverPreview ? (
                                            <img src={editCoverPreview} alt="Cover preview" className="w-full h-full object-cover" />
                                        ) : (
                                            <svg className="w-8 h-8 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
                                            </svg>
                                        )}
                                    </div>
                                    <div className="flex flex-col gap-2">
                                        <input
                                            ref={coverInputRef}
                                            type="file"
                                            accept="image/*"
                                            className="hidden"
                                            onChange={(e) => handleCoverSelect(e.currentTarget.files?.[0])}
                                        />
                                        <Button.Text
                                            type="button"
                                            size={Button.Sizes.Small}
                                            onClick={() => coverInputRef.current?.click()}
                                        >
                                            Choose cover
                                        </Button.Text>
                                        <Button.Danger
                                            type="button"
                                            size={Button.Sizes.Small}
                                            onClick={handleRemoveCover}
                                            disabled={coverMode === 'none'}
                                        >
                                            Remove cover
                                        </Button.Danger>
                                    </div>
                                </div>
                            </div>
                            {editSaving && (
                                <div className="mt-2">
                                    <div className="flex justify-between text-xs text-neutral-300 mb-1">
                                        <span>Uploading...</span>
                                        <span>{Math.round(editProgress)}%</span>
                                    </div>
                                    <div
                                        className="w-full bg-gray-700 h-2 overflow-hidden"
                                        style={{ borderRadius: themeColors.borderRadius.component }}
                                    >
                                        <div
                                            className="h-full bg-primary-500 transition-all duration-200"
                                            style={{ width: `${editProgress}%` }}
                                        />
                                    </div>
                                </div>
                            )}
                        </div>
                    </div>
                    <Dialog.Footer>
                        <Button.Text onClick={closeEditModal} disabled={editSaving}>
                            Cancel
                        </Button.Text>
                        <Button onClick={handleSaveMetadata} disabled={!canEditMetadata || editSaving}>
                            Save Metadata
                        </Button>
                    </Dialog.Footer>
                </Dialog>

                <Dialog open={showLyricsModal} onClose={closeLyricsModal} title={metadata?.lyrics ? 'Edit Lyrics' : 'Add Lyrics'}>
                    <div className='space-y-4'>
                        <p className='text-xs text-neutral-300'>
                            Lyrics are embedded into the MP3 metadata using one line per timestamp. Effects are optional.
                        </p>
                        <div className='rounded border border-neutral-600 bg-neutral-800 p-3'>
                            <p className='font-mono text-xs text-neutral-300'>[00:01:12] First lyric line</p>
                            <p className='mt-1 font-mono text-xs text-neutral-300'>[00:01:18.250][shake] Drop line</p>
                            <p className='mt-1 text-xs text-neutral-400'>Milliseconds are optional. Effects: shake, zoom, pulse, flash. You can also write {'{zoom}'} or | effect=flash after the lyric.</p>
                        </div>
                        {needsFullDownload && (
                            <p className='text-xs text-yellow-300'>
                                Downloading full file to enable lyrics metadata edits...
                            </p>
                        )}
                        {lyricsError && (
                            <div
                                className='border border-red-500 bg-gray-700 p-3 text-sm text-red-400'
                                style={{ borderRadius: themeColors.borderRadius.component }}
                            >
                                {lyricsError}
                            </div>
                        )}
                        <label className='block'>
                            <span className='mb-2 block text-xs text-gray-400'>Synced lyrics</span>
                            <Textarea
                                rows={10}
                                spellCheck={false}
                                value={lyricsDraft}
                                onChange={(event) => setLyricsDraft(event.currentTarget.value)}
                                disabled={readOnly}
                                placeholder={'[00:00:12] Lyric line\n[00:00:18.250][zoom] Big chorus'}
                            />
                        </label>
                        {lyricsSaving && (
                            <div className='mt-2'>
                                <div className='mb-1 flex justify-between text-xs text-neutral-300'>
                                    <span>Uploading...</span>
                                    <span>{Math.round(lyricsProgress)}%</span>
                                </div>
                                <div
                                    className='h-2 w-full overflow-hidden bg-gray-700'
                                    style={{ borderRadius: themeColors.borderRadius.component }}
                                >
                                    <div
                                        className='h-full bg-primary-500 transition-all duration-200'
                                        style={{ width: `${lyricsProgress}%` }}
                                    />
                                </div>
                            </div>
                        )}
                    </div>
                    <Dialog.Footer>
                        <Button.Text onClick={closeLyricsModal} disabled={lyricsSaving}>
                            Cancel
                        </Button.Text>
                        {metadata?.lyrics && (
                            <Button.Text
                                onClick={() => setLyricsDraft('')}
                                disabled={readOnly || lyricsSaving}
                            >
                                Clear
                            </Button.Text>
                        )}
                        <Button onClick={handleSaveLyrics} disabled={readOnly || !canEditMetadata || lyricsSaving}>
                            Attach Lyrics
                        </Button>
                    </Dialog.Footer>
                </Dialog>
            </AudioContent>
        </AudioPlayerContainer>
    );
};

export default AudioPlayer;
